|
| 1 | +#!/bin/bash |
| 2 | +# ============================================================ |
| 3 | +# Document-Level RAG Benchmark |
| 4 | +# ============================================================ |
| 5 | +# |
| 6 | +# Compares Chunk-RAG vs Full-Document-Context for QA accuracy. |
| 7 | +# |
| 8 | +# Methodology: |
| 9 | +# 1. Create a synthetic document with facts spread across sections |
| 10 | +# 2. Ask questions requiring single-hop and multi-hop reasoning |
| 11 | +# 3. Chunk-RAG: split into 512-word chunks, keyword-search, feed top-1 |
| 12 | +# 4. Full-Document: feed entire document to model |
| 13 | +# 5. Score: does the answer contain the correct key fact? |
| 14 | +# |
| 15 | +# Usage: bash bench/document_level_rag_test.sh [model.gguf] |
| 16 | + |
| 17 | +set -e |
| 18 | + |
| 19 | +MODEL="${1:-models/Llama-3.2-3B-Instruct-Q8_0.gguf}" |
| 20 | +TQ="./build/quant" |
| 21 | +THREADS=8 |
| 22 | +PASS=0 |
| 23 | +FAIL=0 |
| 24 | +TOTAL=0 |
| 25 | + |
| 26 | +if [ ! -f "$TQ" ]; then echo "Error: build first"; exit 1; fi |
| 27 | +if [ ! -f "$MODEL" ]; then echo "SKIP: model not found: $MODEL"; exit 0; fi |
| 28 | + |
| 29 | +# ============================================================ |
| 30 | +# Synthetic Document: "Acme Corp Annual Report 2025" |
| 31 | +# Facts are deliberately spread across distant sections |
| 32 | +# ============================================================ |
| 33 | +SECTION1="Section 1: Financial Overview. |
| 34 | +Acme Corporation reported total revenue of 847 million dollars in fiscal year 2025, representing a 15 percent increase over the previous year. Operating margins improved to 23 percent. The company opened 12 new offices globally. Net income reached 195 million dollars. The stock price increased by 34 percent during the fiscal year." |
| 35 | + |
| 36 | +SECTION2="Section 2: Product Development. |
| 37 | +The engineering team launched three major products this year. Project Atlas delivered a new cloud infrastructure platform used by 400 enterprise customers. The mobile division released version 5.0 of the flagship application with 20 million downloads in the first quarter. Research and development spending increased to 120 million dollars, representing 14 percent of total revenue." |
| 38 | + |
| 39 | +SECTION3="Section 3: Growth Strategy. |
| 40 | +The Southeast Asia expansion initiative was the primary driver of revenue growth in 2025. The company established offices in Singapore, Jakarta, and Bangkok, capturing 8 percent market share within 6 months. This regional strategy was originally proposed by Executive Vice President James Park during the 2023 strategic planning retreat in Kyoto." |
| 41 | + |
| 42 | +SECTION4="Section 4: Human Resources. |
| 43 | +The company grew its workforce to 5200 employees across 28 countries. Dr. Maria Santos was appointed as Chief Technology Officer in January 2025, replacing the retiring Dr. Robert Kim. The employee satisfaction score reached 4.2 out of 5.0. The company invested 15 million dollars in employee training programs." |
| 44 | + |
| 45 | +SECTION5="Section 5: Risk Factors. |
| 46 | +Currency fluctuations in Southeast Asian markets posed a 3 percent headwind to reported revenue. Supply chain disruptions affected the hardware division in Q2 but were resolved by Q3. The company maintains a cybersecurity insurance policy valued at 50 million dollars. Regulatory changes in the European Union required additional compliance spending of 8 million dollars." |
| 47 | + |
| 48 | +FULL_DOC="${SECTION1} |
| 49 | +
|
| 50 | +${SECTION2} |
| 51 | +
|
| 52 | +${SECTION3} |
| 53 | +
|
| 54 | +${SECTION4} |
| 55 | +
|
| 56 | +${SECTION5}" |
| 57 | + |
| 58 | +echo "============================================================" |
| 59 | +echo " Document-Level RAG Benchmark" |
| 60 | +echo " Model: $MODEL" |
| 61 | +echo "============================================================" |
| 62 | +echo "" |
| 63 | +echo " Document: Acme Corp Annual Report (5 sections, ~300 words)" |
| 64 | +echo "" |
| 65 | + |
| 66 | +# ============================================================ |
| 67 | +# Test questions: single-hop and multi-hop |
| 68 | +# ============================================================ |
| 69 | +# Format: "question|correct_keyword|type" |
| 70 | +QUESTIONS=( |
| 71 | + "What was Acme's total revenue in 2025?|847|single-hop" |
| 72 | + "Who was appointed as CTO in January 2025?|Santos|single-hop" |
| 73 | + "What was the primary driver of revenue growth?|Southeast Asia|single-hop" |
| 74 | + "Who originally proposed the Southeast Asia expansion strategy?|James Park|multi-hop" |
| 75 | + "How much did R&D spending represent as a percentage of total revenue?|14 percent|single-hop" |
| 76 | + "The revenue growth was driven by a strategy proposed at what event?|Kyoto|multi-hop" |
| 77 | + "What risk factor was related to the same region that drove growth?|Currency fluctuations|multi-hop" |
| 78 | +) |
| 79 | + |
| 80 | +# ============================================================ |
| 81 | +# Method 1: Chunk-RAG (keyword search on chunks) |
| 82 | +# ============================================================ |
| 83 | +echo "[Method 1] Chunk-RAG (split into sections, keyword-search, feed top-1)" |
| 84 | +echo "---" |
| 85 | + |
| 86 | +chunk_pass=0 |
| 87 | +chunk_total=0 |
| 88 | + |
| 89 | +for q_entry in "${QUESTIONS[@]}"; do |
| 90 | + IFS='|' read -r question keyword qtype <<< "$q_entry" |
| 91 | + chunk_total=$((chunk_total + 1)) |
| 92 | + |
| 93 | + # Simple keyword search: find which section contains the most words from the question |
| 94 | + best_section="" |
| 95 | + best_score=0 |
| 96 | + for section_var in SECTION1 SECTION2 SECTION3 SECTION4 SECTION5; do |
| 97 | + section_text="${!section_var}" |
| 98 | + score=0 |
| 99 | + for word in $question; do |
| 100 | + if echo "$section_text" | grep -qi "$word" 2>/dev/null; then |
| 101 | + score=$((score + 1)) |
| 102 | + fi |
| 103 | + done |
| 104 | + if [ $score -gt $best_score ]; then |
| 105 | + best_score=$score |
| 106 | + best_section="$section_text" |
| 107 | + fi |
| 108 | + done |
| 109 | + |
| 110 | + # Feed best chunk + question to model |
| 111 | + prompt="Context: ${best_section} |
| 112 | +
|
| 113 | +Question: ${question} |
| 114 | +Answer briefly:" |
| 115 | + |
| 116 | + answer=$($TQ "$MODEL" -p "$prompt" -n 40 -T 0.0 -j $THREADS -k fp32 --chat 2>/dev/null) |
| 117 | + |
| 118 | + if echo "$answer" | grep -qi "$keyword"; then |
| 119 | + echo " [PASS] ($qtype) $question → found '$keyword'" |
| 120 | + chunk_pass=$((chunk_pass + 1)) |
| 121 | + else |
| 122 | + echo " [FAIL] ($qtype) $question → missing '$keyword'" |
| 123 | + fi |
| 124 | +done |
| 125 | + |
| 126 | +echo "" |
| 127 | +echo " Chunk-RAG: ${chunk_pass}/${chunk_total} correct" |
| 128 | +echo "" |
| 129 | + |
| 130 | +# ============================================================ |
| 131 | +# Method 2: Full-Document Context |
| 132 | +# ============================================================ |
| 133 | +echo "[Method 2] Full-Document Context (entire document in prompt)" |
| 134 | +echo "---" |
| 135 | + |
| 136 | +doc_pass=0 |
| 137 | +doc_total=0 |
| 138 | + |
| 139 | +for q_entry in "${QUESTIONS[@]}"; do |
| 140 | + IFS='|' read -r question keyword qtype <<< "$q_entry" |
| 141 | + doc_total=$((doc_total + 1)) |
| 142 | + |
| 143 | + prompt="Based on this document, answer the question. |
| 144 | +
|
| 145 | +Document: ${FULL_DOC} |
| 146 | +
|
| 147 | +Question: ${question}" |
| 148 | + |
| 149 | + answer=$($TQ "$MODEL" -p "$prompt" -n 40 -T 0.0 -j $THREADS -k fp32 --chat 2>/dev/null) |
| 150 | + |
| 151 | + if echo "$answer" | grep -qi "$keyword"; then |
| 152 | + echo " [PASS] ($qtype) $question → found '$keyword'" |
| 153 | + doc_pass=$((doc_pass + 1)) |
| 154 | + else |
| 155 | + echo " [FAIL] ($qtype) $question → missing '$keyword'" |
| 156 | + fi |
| 157 | +done |
| 158 | + |
| 159 | +echo "" |
| 160 | +echo " Full-Document: ${doc_pass}/${doc_total} correct" |
| 161 | +echo "" |
| 162 | + |
| 163 | +# ============================================================ |
| 164 | +# Method 3: Full-Document + KV Compression (6.4x) |
| 165 | +# ============================================================ |
| 166 | +echo "[Method 3] Full-Document + KV Compression (6.4x)" |
| 167 | +echo "---" |
| 168 | + |
| 169 | +comp_pass=0 |
| 170 | +comp_total=0 |
| 171 | + |
| 172 | +for q_entry in "${QUESTIONS[@]}"; do |
| 173 | + IFS='|' read -r question keyword qtype <<< "$q_entry" |
| 174 | + comp_total=$((comp_total + 1)) |
| 175 | + |
| 176 | + prompt="Based on this document, answer the question. |
| 177 | +
|
| 178 | +Document: ${FULL_DOC} |
| 179 | +
|
| 180 | +Question: ${question}" |
| 181 | + |
| 182 | + answer=$($TQ "$MODEL" -p "$prompt" -n 40 -T 0.0 -j $THREADS \ |
| 183 | + -k turbo_kv_4b -v q4 --k-window 128 --chat 2>/dev/null) |
| 184 | + |
| 185 | + if echo "$answer" | grep -qi "$keyword"; then |
| 186 | + echo " [PASS] ($qtype) $question → found '$keyword'" |
| 187 | + comp_pass=$((comp_pass + 1)) |
| 188 | + else |
| 189 | + echo " [FAIL] ($qtype) $question → missing '$keyword'" |
| 190 | + fi |
| 191 | +done |
| 192 | + |
| 193 | +echo "" |
| 194 | +echo " Compressed: ${comp_pass}/${comp_total} correct" |
| 195 | +echo "" |
| 196 | + |
| 197 | +# ============================================================ |
| 198 | +# Summary |
| 199 | +# ============================================================ |
| 200 | +echo "============================================================" |
| 201 | +echo " Results Summary" |
| 202 | +echo "============================================================" |
| 203 | +echo "" |
| 204 | +printf " %-30s %s\n" "Method" "Accuracy" |
| 205 | +printf " %-30s %s\n" "------------------------------" "--------" |
| 206 | +printf " %-30s %d/%d\n" "Chunk-RAG (top-1 section)" $chunk_pass $chunk_total |
| 207 | +printf " %-30s %d/%d\n" "Full-Document (FP32 KV)" $doc_pass $doc_total |
| 208 | +printf " %-30s %d/%d\n" "Full-Document (6.4x compressed)" $comp_pass $comp_total |
| 209 | +echo "" |
| 210 | + |
| 211 | +if [ $doc_pass -gt $chunk_pass ]; then |
| 212 | + echo " → Full-document context outperforms chunk-RAG by $((doc_pass - chunk_pass)) questions" |
| 213 | +fi |
| 214 | +if [ $comp_pass -eq $doc_pass ]; then |
| 215 | + echo " → KV compression preserves full-document accuracy (zero quality loss)" |
| 216 | +fi |
| 217 | +echo "" |
| 218 | +echo " Key insight: multi-hop questions requiring cross-section reasoning" |
| 219 | +echo " are where full-document context provides the most advantage." |
| 220 | +echo "" |
0 commit comments