<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Benchmark on Tarragon</title><link>https://tarrragon.github.io/blog/tags/benchmark/</link><description>Recent content in Benchmark on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 01 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/benchmark/index.xml" rel="self" type="application/rss+xml"/><item><title>LLM Benchmarks（MMLU / HumanEval / SWE-bench 等）</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/llm-benchmarks/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/llm-benchmarks/</guid><description>&lt;p>LLM benchmarks 的核心概念是「&lt;strong>用標準化任務集合衡量 LLM 各維度能力的評估工具&lt;/strong>」。不同 benchmark 衡量不同維度（知識、reasoning、code、對話、math 等）、選錯 benchmark 看模型會誤判。本卡列主流 benchmark 跟它們的覆蓋面、失效情境。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>主流 LLM benchmark 一覽：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Benchmark&lt;/th>
 &lt;th>衡量維度&lt;/th>
 &lt;th>任務形式&lt;/th>
 &lt;th>失效情境&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>MMLU&lt;/strong>&lt;/td>
 &lt;td>通用知識（57 學科多選題）&lt;/td>
 &lt;td>4 選 1 選擇題&lt;/td>
 &lt;td>訓練資料污染（題目可能在 pretrain corpus）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>GSM8K&lt;/strong>&lt;/td>
 &lt;td>小學數學 word problem&lt;/td>
 &lt;td>文字 + 數字、需 reasoning&lt;/td>
 &lt;td>飽和（前沿模型 95%+）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>MATH&lt;/strong>&lt;/td>
 &lt;td>高中 / 競賽數學&lt;/td>
 &lt;td>自由作答&lt;/td>
 &lt;td>訓練污染、reasoning model 表現遠超 instruct&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>HumanEval&lt;/strong>&lt;/td>
 &lt;td>Python function 補完&lt;/td>
 &lt;td>寫一個 function 通過 unit test&lt;/td>
 &lt;td>飽和、僅覆蓋初級 coding&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>MBPP&lt;/strong>&lt;/td>
 &lt;td>Python coding 任務&lt;/td>
 &lt;td>同上、規模較大&lt;/td>
 &lt;td>同 HumanEval&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/swe-bench/" data-link-title="SWE-bench" data-link-desc="用真實 GitHub issue 量化 LLM coding 能力的 benchmark">&lt;strong>SWE-bench&lt;/strong>&lt;/a>&lt;/td>
 &lt;td>真實 GitHub issue 修復&lt;/td>
 &lt;td>給 repo + issue、生 patch、跑 test&lt;/td>
 &lt;td>仍是 LLM 主要 coding 差距、不易飽和&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>MT-Bench&lt;/strong>&lt;/td>
 &lt;td>多輪對話品質&lt;/td>
 &lt;td>80 題 prompt、LLM-as-judge 評分&lt;/td>
 &lt;td>LLM-as-judge bias、judge 模型本身能力影響評分&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Chatbot Arena&lt;/strong>&lt;/td>
 &lt;td>開放對話偏好（眾人投票）&lt;/td>
 &lt;td>A/B 對戰、Elo 排名&lt;/td>
 &lt;td>文化偏好、prompt 設計影響&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>HELM&lt;/strong>&lt;/td>
 &lt;td>多 dimension comprehensive&lt;/td>
 &lt;td>22 scenarios × 多 metrics&lt;/td>
 &lt;td>計算昂貴、不易追蹤每代新模型&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>AlpacaEval&lt;/strong>&lt;/td>
 &lt;td>指令跟隨能力&lt;/td>
 &lt;td>LLM-as-judge 對比 GPT-4&lt;/td>
 &lt;td>Judge bias、易被「verbose」攻擊&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>RULER&lt;/strong>&lt;/td>
 &lt;td>Long context 真實任務&lt;/td>
 &lt;td>Multi-needle、aggregation、reasoning&lt;/td>
 &lt;td>較新、覆蓋仍在演化&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>&lt;strong>事實查核註&lt;/strong>：各 benchmark 的飽和狀態、前沿模型 score 持續變動、上述為 2026/5 主流觀察。引用前以 &lt;a href="https://paperswithcode.com/">Papers with Code&lt;/a> 或 &lt;a href="https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard">HuggingFace Open LLM Leaderboard&lt;/a> 當前狀態為準。&lt;/p>&lt;/blockquote>
&lt;h2 id="benchmark-的常見陷阱">Benchmark 的常見陷阱&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>訓練資料污染（Contamination）&lt;/strong>：benchmark 題目本身在 pretrain corpus 出現過、模型「記得」答案、看似強實際是 memorization&lt;/li>
&lt;li>&lt;strong>飽和（Saturation）&lt;/strong>：前沿模型 score 接近上限、無法區分模型品質差距（HumanEval 80%→95% 看似進步、實際 5% 多半是 lucky 而非實質提升）&lt;/li>
&lt;li>&lt;strong>LLM-as-judge bias&lt;/strong>：用 LLM（如 GPT-4）評其他 LLM、judge 的偏好（如「冗長 = 好」）會 bias 評分&lt;/li>
&lt;li>&lt;strong>Single-task overfitting&lt;/strong>：模型廠商針對 benchmark 特別 fine-tune、benchmark 高分但通用能力沒提升&lt;/li>
&lt;li>&lt;strong>Prompt sensitivity&lt;/strong>：同個 benchmark 用不同 prompt format、score 差幾個百分點&lt;/li>
&lt;/ol>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>讀 model card / paper 看到 benchmark 數字、判讀框架：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>看 multiple benchmarks、不只一個&lt;/strong>：如挑 coding 模型、看 HumanEval + MBPP + SWE-bench、不只看 HumanEval&lt;/li>
&lt;li>&lt;strong>跟自己任務對齊的 benchmark 才重要&lt;/strong>：你做 RAG 應用、看 retrieval benchmark；你做 chat、看 MT-Bench / Arena&lt;/li>
&lt;li>&lt;strong>看「相對」、不只看「絕對」&lt;/strong>：「Model A 在 MMLU 比 Model B 高 2%」可能 noise；「A 比 B 高 10%」更可信&lt;/li>
&lt;li>&lt;strong>In-house benchmark 是最後檢驗&lt;/strong>：自己的真實工作流案例 &amp;gt; 任何公開 benchmark&lt;/li>
&lt;/ol></description><content:encoded><![CDATA[<p>LLM benchmarks 的核心概念是「<strong>用標準化任務集合衡量 LLM 各維度能力的評估工具</strong>」。不同 benchmark 衡量不同維度（知識、reasoning、code、對話、math 等）、選錯 benchmark 看模型會誤判。本卡列主流 benchmark 跟它們的覆蓋面、失效情境。</p>
<h2 id="概念位置">概念位置</h2>
<p>主流 LLM benchmark 一覽：</p>
<table>
  <thead>
      <tr>
          <th>Benchmark</th>
          <th>衡量維度</th>
          <th>任務形式</th>
          <th>失效情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>MMLU</strong></td>
          <td>通用知識（57 學科多選題）</td>
          <td>4 選 1 選擇題</td>
          <td>訓練資料污染（題目可能在 pretrain corpus）</td>
      </tr>
      <tr>
          <td><strong>GSM8K</strong></td>
          <td>小學數學 word problem</td>
          <td>文字 + 數字、需 reasoning</td>
          <td>飽和（前沿模型 95%+）</td>
      </tr>
      <tr>
          <td><strong>MATH</strong></td>
          <td>高中 / 競賽數學</td>
          <td>自由作答</td>
          <td>訓練污染、reasoning model 表現遠超 instruct</td>
      </tr>
      <tr>
          <td><strong>HumanEval</strong></td>
          <td>Python function 補完</td>
          <td>寫一個 function 通過 unit test</td>
          <td>飽和、僅覆蓋初級 coding</td>
      </tr>
      <tr>
          <td><strong>MBPP</strong></td>
          <td>Python coding 任務</td>
          <td>同上、規模較大</td>
          <td>同 HumanEval</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/swe-bench/" data-link-title="SWE-bench" data-link-desc="用真實 GitHub issue 量化 LLM coding 能力的 benchmark"><strong>SWE-bench</strong></a></td>
          <td>真實 GitHub issue 修復</td>
          <td>給 repo + issue、生 patch、跑 test</td>
          <td>仍是 LLM 主要 coding 差距、不易飽和</td>
      </tr>
      <tr>
          <td><strong>MT-Bench</strong></td>
          <td>多輪對話品質</td>
          <td>80 題 prompt、LLM-as-judge 評分</td>
          <td>LLM-as-judge bias、judge 模型本身能力影響評分</td>
      </tr>
      <tr>
          <td><strong>Chatbot Arena</strong></td>
          <td>開放對話偏好（眾人投票）</td>
          <td>A/B 對戰、Elo 排名</td>
          <td>文化偏好、prompt 設計影響</td>
      </tr>
      <tr>
          <td><strong>HELM</strong></td>
          <td>多 dimension comprehensive</td>
          <td>22 scenarios × 多 metrics</td>
          <td>計算昂貴、不易追蹤每代新模型</td>
      </tr>
      <tr>
          <td><strong>AlpacaEval</strong></td>
          <td>指令跟隨能力</td>
          <td>LLM-as-judge 對比 GPT-4</td>
          <td>Judge bias、易被「verbose」攻擊</td>
      </tr>
      <tr>
          <td><strong>RULER</strong></td>
          <td>Long context 真實任務</td>
          <td>Multi-needle、aggregation、reasoning</td>
          <td>較新、覆蓋仍在演化</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p><strong>事實查核註</strong>：各 benchmark 的飽和狀態、前沿模型 score 持續變動、上述為 2026/5 主流觀察。引用前以 <a href="https://paperswithcode.com/">Papers with Code</a> 或 <a href="https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard">HuggingFace Open LLM Leaderboard</a> 當前狀態為準。</p></blockquote>
<h2 id="benchmark-的常見陷阱">Benchmark 的常見陷阱</h2>
<ol>
<li><strong>訓練資料污染（Contamination）</strong>：benchmark 題目本身在 pretrain corpus 出現過、模型「記得」答案、看似強實際是 memorization</li>
<li><strong>飽和（Saturation）</strong>：前沿模型 score 接近上限、無法區分模型品質差距（HumanEval 80%→95% 看似進步、實際 5% 多半是 lucky 而非實質提升）</li>
<li><strong>LLM-as-judge bias</strong>：用 LLM（如 GPT-4）評其他 LLM、judge 的偏好（如「冗長 = 好」）會 bias 評分</li>
<li><strong>Single-task overfitting</strong>：模型廠商針對 benchmark 特別 fine-tune、benchmark 高分但通用能力沒提升</li>
<li><strong>Prompt sensitivity</strong>：同個 benchmark 用不同 prompt format、score 差幾個百分點</li>
</ol>
<h2 id="設計責任">設計責任</h2>
<p>讀 model card / paper 看到 benchmark 數字、判讀框架：</p>
<ol>
<li><strong>看 multiple benchmarks、不只一個</strong>：如挑 coding 模型、看 HumanEval + MBPP + SWE-bench、不只看 HumanEval</li>
<li><strong>跟自己任務對齊的 benchmark 才重要</strong>：你做 RAG 應用、看 retrieval benchmark；你做 chat、看 MT-Bench / Arena</li>
<li><strong>看「相對」、不只看「絕對」</strong>：「Model A 在 MMLU 比 Model B 高 2%」可能 noise；「A 比 B 高 10%」更可信</li>
<li><strong>In-house benchmark 是最後檢驗</strong>：自己的真實工作流案例 &gt; 任何公開 benchmark</li>
</ol>
]]></content:encoded></item><item><title>Case Study：Blog 語意搜尋從 pickle 到 production</title><link>https://tarrragon.github.io/blog/llm/04-applications/hands-on/blog-vector-search/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/hands-on/blog-vector-search/</guid><description>&lt;p>本案例記錄一個技術 blog（2,738 篇 markdown、24,216 chunks）的語意搜尋工具從 demo 到 production 的完整過程。每段標出對應 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 RAG storage 工程&lt;/a> 的哪個判讀步驟，讓讀者看到原理章的框架怎麼落到具體決策。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>實測日期&lt;/strong>：2026-07-01
&lt;strong>環境&lt;/strong>：macOS Apple Silicon、Ollama 0.7.x、&lt;code>nomic-embed-text&lt;/code>（768 維）
&lt;strong>Corpus&lt;/strong>：&lt;code>content/&lt;/code> 全量 2,738 個 markdown 檔、24,216 chunks
&lt;strong>前置 demo&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/hands-on/rag-demo/" data-link-title="Hands-on：用 blog content 當 corpus 跑 RAG" data-link-desc="200 行 Python：embedding &amp;#43; cosine retrieval &amp;#43; Ollama chat、validating 4.0 RAG 原理">rag-demo&lt;/a>（pickle、463 chunks）&lt;/p>&lt;/blockquote>
&lt;h3 id="讀法建議">讀法建議&lt;/h3>
&lt;p>本案例用 Go 重寫了 RAG storage 層，Go 實作細節佔不少篇幅。依你的背景選讀法：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Python 開發者、想選自己專案的 storage 方案&lt;/strong>：先跳到「通用可複製流程」（語言無關的五步驟）→「四方案 benchmark」→「二次選型評估」（結論/理由/前提三層框架），這三段跨語言可遷移。Go 實作段（架構、效能優化）可 skim。&lt;/li>
&lt;li>&lt;strong>Go 開發者、想做類似工具&lt;/strong>：從頭讀，每段都跟你相關。&lt;/li>
&lt;li>&lt;strong>只想看選型框架、不管實作&lt;/strong>：直接跳「二次選型評估」。&lt;/li>
&lt;/ul>
&lt;h2 id="從-demo-到-production-的重寫動機">從 demo 到 production 的重寫動機&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/01-local-llm-services/hands-on/rag-demo/" data-link-title="Hands-on：用 blog content 當 corpus 跑 RAG" data-link-desc="200 行 Python：embedding &amp;#43; cosine retrieval &amp;#43; Ollama chat、validating 4.0 RAG 原理">rag-demo&lt;/a> 用 Python pickle 跑通了 RAG 概念驗證：71 篇 → 463 chunks → pickle 儲存 → cosine retrieval → Ollama 生成。概念層完全正確（4.1 的 retrieval + augmentation 骨架），但作為這個 blog 的日常工具有三個&lt;strong>專案特有的&lt;/strong>限制：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>工具鏈語言不同&lt;/strong>：blog 的核心工具是 Go（lint / fmt / cards），加 Python dependency 讓其他維護者 clone 後多一步環境設定。Python 專案不會有這個問題 — pickle 綁 Python 對 Python 專案是優點而非缺點。&lt;/li>
&lt;li>&lt;strong>只索引部分 corpus&lt;/strong>：rag-demo 只跑 &lt;code>content/llm/&lt;/code>（71 篇），blog 全量有 2,738 篇、24 個 section。&lt;/li>
&lt;li>&lt;strong>Demo 定位&lt;/strong>：ingest.py / query.py 是教學程式碼，不是維護工具（沒有 status、沒有 section filter）。&lt;/li>
&lt;/ol>
&lt;p>這是一次&lt;strong>完整重寫&lt;/strong>、不是漸進升級 — rag-demo 的 Python 程式碼不會被修改或遷移，而是用 Go 重新實作相同的 RAG pipeline（chunk → embed → store → search）、保留相同的概念架構。rag-demo 作為教學 demo 繼續存在。&lt;/p>
&lt;p>升級目標：一個跟 &lt;code>mdtools&lt;/code> 同級的 Go CLI 工具，能對全量 content 做語意搜尋，其他維護者 clone 後 &lt;code>go build&lt;/code> 即可用。完整原始碼在 &lt;code>scripts/blogsearch/&lt;/code>。&lt;/p></description><content:encoded><![CDATA[<p>本案例記錄一個技術 blog（2,738 篇 markdown、24,216 chunks）的語意搜尋工具從 demo 到 production 的完整過程。每段標出對應 <a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 RAG storage 工程</a> 的哪個判讀步驟，讓讀者看到原理章的框架怎麼落到具體決策。</p>
<blockquote>
<p><strong>實測日期</strong>：2026-07-01
<strong>環境</strong>：macOS Apple Silicon、Ollama 0.7.x、<code>nomic-embed-text</code>（768 維）
<strong>Corpus</strong>：<code>content/</code> 全量 2,738 個 markdown 檔、24,216 chunks
<strong>前置 demo</strong>：<a href="/blog/llm/01-local-llm-services/hands-on/rag-demo/" data-link-title="Hands-on：用 blog content 當 corpus 跑 RAG" data-link-desc="200 行 Python：embedding &#43; cosine retrieval &#43; Ollama chat、validating 4.0 RAG 原理">rag-demo</a>（pickle、463 chunks）</p></blockquote>
<h3 id="讀法建議">讀法建議</h3>
<p>本案例用 Go 重寫了 RAG storage 層，Go 實作細節佔不少篇幅。依你的背景選讀法：</p>
<ul>
<li><strong>Python 開發者、想選自己專案的 storage 方案</strong>：先跳到「通用可複製流程」（語言無關的五步驟）→「四方案 benchmark」→「二次選型評估」（結論/理由/前提三層框架），這三段跨語言可遷移。Go 實作段（架構、效能優化）可 skim。</li>
<li><strong>Go 開發者、想做類似工具</strong>：從頭讀，每段都跟你相關。</li>
<li><strong>只想看選型框架、不管實作</strong>：直接跳「二次選型評估」。</li>
</ul>
<h2 id="從-demo-到-production-的重寫動機">從 demo 到 production 的重寫動機</h2>
<p><a href="/blog/llm/01-local-llm-services/hands-on/rag-demo/" data-link-title="Hands-on：用 blog content 當 corpus 跑 RAG" data-link-desc="200 行 Python：embedding &#43; cosine retrieval &#43; Ollama chat、validating 4.0 RAG 原理">rag-demo</a> 用 Python pickle 跑通了 RAG 概念驗證：71 篇 → 463 chunks → pickle 儲存 → cosine retrieval → Ollama 生成。概念層完全正確（4.1 的 retrieval + augmentation 骨架），但作為這個 blog 的日常工具有三個<strong>專案特有的</strong>限制：</p>
<ol>
<li><strong>工具鏈語言不同</strong>：blog 的核心工具是 Go（lint / fmt / cards），加 Python dependency 讓其他維護者 clone 後多一步環境設定。Python 專案不會有這個問題 — pickle 綁 Python 對 Python 專案是優點而非缺點。</li>
<li><strong>只索引部分 corpus</strong>：rag-demo 只跑 <code>content/llm/</code>（71 篇），blog 全量有 2,738 篇、24 個 section。</li>
<li><strong>Demo 定位</strong>：ingest.py / query.py 是教學程式碼，不是維護工具（沒有 status、沒有 section filter）。</li>
</ol>
<p>這是一次<strong>完整重寫</strong>、不是漸進升級 — rag-demo 的 Python 程式碼不會被修改或遷移，而是用 Go 重新實作相同的 RAG pipeline（chunk → embed → store → search）、保留相同的概念架構。rag-demo 作為教學 demo 繼續存在。</p>
<p>升級目標：一個跟 <code>mdtools</code> 同級的 Go CLI 工具，能對全量 content 做語意搜尋，其他維護者 clone 後 <code>go build</code> 即可用。完整原始碼在 <code>scripts/blogsearch/</code>。</p>
<h2 id="選型過程對應-422-演化階梯--工程約束">選型過程（對應 4.22 演化階梯 + 工程約束）</h2>
<h3 id="第一軸規模判讀">第一軸：規模判讀</h3>
<p>全量 content 產生 24,216 chunks（原本估計 ~1,500）。按 4.22 判讀樹，24K 落在「10K-100K → HNSW 或 brute-force」區間。預估 vs 實際的 16 倍落差揭露一個教訓：<strong>估計 chunk 數不能用篇數乘以常數</strong>，要看每篇的實際長度跟 chunking 策略。</p>
<h3 id="第二軸工程約束本專案特有">第二軸：工程約束（本專案特有）</h3>
<p>以下四個 constraint 反映<strong>這個 blog 專案的偏好</strong>、不是通用判準。換一組 constraint 會篩出完全不同的方案 — Python 專案不會有「Go 單 binary」constraint、已有 Docker 的團隊不會排斥外部 server。讀者套用時應先列出自己專案的 constraint、不是照搬這張表。</p>
<table>
  <thead>
      <tr>
          <th>Constraint</th>
          <th>砍掉什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Go 單 binary</td>
          <td>Python-only 方案（pickle / FAISS）</td>
      </tr>
      <tr>
          <td>不要 CGo</td>
          <td>sqlite-vec（需要 <code>mattn/go-sqlite3</code>）</td>
      </tr>
      <tr>
          <td>不要外部 server</td>
          <td>Qdrant / Weaviate / Pinecone</td>
      </tr>
      <tr>
          <td>Ollama 原生</td>
          <td>OpenAI / Cohere embedding（多一個 API key）</td>
      </tr>
  </tbody>
</table>
<p>剩餘選項：<strong>Go + flat file + brute-force</strong>。</p>
<h3 id="第三軸延遲容忍">第三軸：延遲容忍</h3>
<p>CLI 工具、每天用幾次、不是 API server。&lt; 500ms 可接受。</p>
<p>結論：選階段二（flat file），brute-force cosine。</p>
<h2 id="實作架構">實作架構</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">scripts/blogsearch/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">├── main.go                     # CLI: ingest / query / status
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">├── cmd/
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│   ├── ingest.go               # walk content/ → chunk → embed → store
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│   ├── query.go                # load → embed query → cosine top-K → lazy load text
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│   └── status.go               # index stats
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">└── internal/
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    ├── chunk/chunk.go           # paragraph-aware markdown chunking
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    ├── embed/embed.go           # Ollama HTTP API wrapper
</span></span><span class="line"><span class="ln">10</span><span class="cl">    ├── search/search.go         # brute-force cosine similarity
</span></span><span class="line"><span class="ln">11</span><span class="cl">    └── store/store.go           # 三檔案 binary store</span></span></code></pre></div><h3 id="日常使用">日常使用</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 語意搜尋</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">./bin/blogsearch query <span class="s2">&#34;retry 策略&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 只搜特定 section</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">./bin/blogsearch query -section backend <span class="s2">&#34;connection pool 設定&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 查 index 狀態</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">./bin/blogsearch status</span></span></code></pre></div><h3 id="storage-格式三檔案分離">Storage 格式（三檔案分離）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">.blogsearch/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── vectors.bin    # float32 binary（70.9 MB）— bulk read + unsafe.Slice 零拷貝
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── meta.json      # compact metadata 不含 text（7.3 MB）
</span></span><span class="line"><span class="ln">4</span><span class="cl">└── texts.bin      # length-prefixed chunk text（19.2 MB）— top-K 才 lazy load</span></span></code></pre></div><p>分離 text 的設計理由：query 時只需要 vectors + metadata 做 cosine search（78 MB），top-K 結果才從 texts.bin 按 offset 讀取 5 筆 text。省掉 19 MB 的 JSON 解析。</p>
<h2 id="效能優化歷程">效能優化歷程</h2>
<h3 id="初版95-秒">初版：9.5 秒</h3>
<p>初版用逐 4-byte Read 載入 vectors.bin（17.5M 次 <code>f.Read(buf)</code>），加上 27MB 的 index.json（含所有 chunk text）一次 JSON 解析。</p>
<h3 id="優化版034-秒28x">優化版：0.34 秒（28x）</h3>
<p>三項改動：</p>
<table>
  <thead>
      <tr>
          <th>改動</th>
          <th>從</th>
          <th>到</th>
          <th>效果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>vectors.bin 讀法</td>
          <td>逐 4-byte Read</td>
          <td><code>os.ReadFile</code> + <code>unsafe.Slice</code></td>
          <td>I/O call 17.5M → 1</td>
      </tr>
      <tr>
          <td>metadata 格式</td>
          <td>含 text（27 MB）</td>
          <td>不含 text（7.3 MB）</td>
          <td>JSON parse 快 4x</td>
      </tr>
      <tr>
          <td>text 載入</td>
          <td>全量</td>
          <td>top-K lazy load（只讀 5 筆）</td>
          <td>省 19 MB 讀取</td>
      </tr>
  </tbody>
</table>
<p>瓶頸分析：0.34 秒裡、embedding API call（Ollama）約 77ms、file I/O + JSON parse 約 200ms、cosine 計算約 50ms。cosine 計算只佔 15%。</p>
<h2 id="通用可複製流程抽掉-goblog">通用可複製流程（抽掉 Go/blog）</h2>
<p>本案例的 Go 實作細節（<code>unsafe.Slice</code>、<code>os.ReadFile</code>）是語言特定的、但背後的流程步驟跨語言通用：</p>
<ol>
<li><strong>Walk corpus</strong>：遞迴掃描目標目錄的所有文件（markdown / code / 任意文字）</li>
<li><strong>Chunk</strong>：段落感知分割、soft token cap、保留語意邊界（原理見 <a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 Chunking</a>）</li>
<li><strong>Embed</strong>：對每個 chunk 呼叫 embedding API（本地 Ollama 或 cloud API），得到固定維度向量</li>
<li><strong>Store</strong>：向量 + metadata + text 分離存檔（binary vectors / compact JSON / lazy-load text）</li>
<li><strong>Search</strong>：embed query → brute-force cosine → top-K → lazy load text for display</li>
</ol>
<p>Python 實作同流程只是把第 4 步的 binary 檔換成 pickle / FAISS index / SQLite DB、第 5 步的 cosine 換成 numpy / FAISS / sqlite-vec query。Node.js / Rust 同理。</p>
<p>關鍵優化原則也跨語言：「分離向量與文字、query 時只載入向量、top-K 才載入文字」讓 I/O 量從 ~98MB 降到 ~78MB、JSON parse 從 27MB 降到 7MB。這個原則用什麼語言實作都有效。</p>
<h2 id="四方案同-corpus-benchmark">四方案同 corpus Benchmark</h2>
<p>用同一個 corpus（24,216 chunks、768 維、nomic-embed-text）比較四種 storage 方案。Benchmark 腳本在 <code>scripts/blogsearch-bench/bench.py</code>。</p>
<h3 id="前置依賴">前置依賴</h3>
<p>Benchmark 腳本讀 Go 工具產生的 index（<code>.blogsearch/</code> 下的 <code>vectors.bin</code> + <code>meta.json</code>）。完整指令鏈：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">cd</span> scripts/blogsearch <span class="o">&amp;&amp;</span> go build -o ../../bin/blogsearch .   <span class="c1"># build Go 工具</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ollama serve <span class="p">&amp;</span>                                                  <span class="c1"># 啟動 Ollama</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">ollama pull nomic-embed-text                                    <span class="c1"># pull embedding model</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">./bin/blogsearch ingest -content content -out .blogsearch       <span class="c1"># 建 index（~4 分鐘）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">uv run --with sqlite-vec --with faiss-cpu --with numpy <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  scripts/blogsearch-bench/bench.py --index .blogsearch         <span class="c1"># 跑 benchmark</span></span></span></code></pre></div><p>若無 Go 環境，可用自己的 Python embedding 腳本產生相同格式的 <code>vectors.bin</code>（little-endian float32、n × dim 連續排列）+ <code>meta.json</code>（<code>{&quot;dim&quot;: 768, &quot;count&quot;: n, &quot;metas&quot;: [...]}</code>），benchmark 腳本只讀這兩個檔案、不依賴 Go binary 本身。Corpus 格式無硬性要求，任何目錄下的 <code>.md</code> 檔案都可索引。</p>
<h3 id="方法論">方法論</h3>
<ul>
<li><strong>Embedding</strong>：四方案共用同一組 embedding（從 Go index 載入），排除 embedding model 差異</li>
<li><strong>Query</strong>：同一句 query（&ldquo;RAG storage 選型&rdquo;），跑 5 次取 median</li>
<li><strong>Ingest 時間</strong>：只計 storage 操作（不含 embedding），Go 方案含 embedding 不可分離故標 —</li>
<li><strong>環境</strong>：macOS Apple Silicon、Python 3.12、Go 1.25</li>
</ul>
<h3 id="結果">結果</h3>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>Ingest（純 storage）</th>
          <th>Query（median）</th>
          <th>Index 大小</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Go + flat file</td>
          <td>—</td>
          <td>151ms</td>
          <td>97.4 MB</td>
      </tr>
      <tr>
          <td>Python sqlite-vec</td>
          <td>2.9s</td>
          <td>19ms</td>
          <td>75.3 MB</td>
      </tr>
      <tr>
          <td>Python FAISS flat</td>
          <td>40ms</td>
          <td>1.8ms</td>
          <td>in-memory</td>
      </tr>
      <tr>
          <td>Python FAISS HNSW</td>
          <td>23.3s</td>
          <td>0.5ms</td>
          <td>in-memory</td>
      </tr>
  </tbody>
</table>
<h3 id="三個關鍵發現">三個關鍵發現</h3>
<p><strong>延遲瓶頸在 I/O 和實作、不在演算法</strong>。Go flat file 的 151ms 裡、cosine 計算約 50ms、file I/O 約 100ms。FAISS flat 用 numpy BLAS 做同樣的 brute-force cosine、純計算 1.8ms — 計算層差約 28 倍（Go pure loop vs BLAS 向量化指令），加上 I/O 後端到端差 84 倍。</p>
<p><strong>HNSW 的 query 加速在此規模 ROI 低</strong>。FAISS HNSW query 0.5ms vs flat 1.8ms、每次省 1.3ms。但 HNSW build 要 23.3s。每天查 100 次、要 179 天才回本 build 成本（23.3s ÷ 0.13s/天）。4.22 的判讀結論（「此規模 brute-force 夠用」）被數據驗證。</p>
<p><strong>sqlite-vec 的 19ms 是「DB overhead 換功能」</strong>。比 FAISS flat 慢 10 倍、但多了 SQL metadata filter、transaction 保護、disk persistence。對「需要 filter 但不想維運 server」的場景有意義。</p>
<h3 id="讀數據的注意事項">讀數據的注意事項</h3>
<ul>
<li>Go 151ms 含 file I/O（每次 query 重載 78MB）；如果做 daemon mode（常駐、載入一次），query 會降到 ~50ms（純 cosine + overhead）</li>
<li>FAISS 數字是 in-memory baseline（index 已載入），不含 index 檔案的載入時間</li>
<li>sqlite-vec 數字含 disk I/O（每次 query 從 SQLite 讀取），是 persistent storage 的真實代價</li>
<li>四方案都不含 Ollama embedding call 時間（~77ms），實際端到端延遲要加上</li>
</ul>
<h2 id="二次選型評估同結論理由鏈翻轉">二次選型評估：同結論、理由鏈翻轉</h2>
<p>Benchmark 數據出來後，80 倍效能差距讓原始選型（Go + flat file）受到質疑：「是否該換 Python + FAISS 或 sqlite-vec？」重新用 WRAP 框架評估，結論相同（維持 Go），但理由鏈完全不同。</p>
<h3 id="第一次選型的理由事前">第一次選型的理由（事前）</h3>
<p>「Go 工具鏈統一（mdtools 是 Go）+ 單 binary 分發（clone 後 <code>go build</code> 即可）。」</p>
<h3 id="實測推翻的前提">實測推翻的前提</h3>
<table>
  <thead>
      <tr>
          <th>原始假設</th>
          <th>實測</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Corpus ~1,500 chunks</td>
          <td>24,216 chunks（16 倍）</td>
      </tr>
      <tr>
          <td>Brute-force &lt; 10ms</td>
          <td>Go 151ms（I/O 瓶頸、不是計算）</td>
      </tr>
      <tr>
          <td>語言效能差異不大</td>
          <td>Go pure cosine vs numpy BLAS 差 80 倍</td>
      </tr>
      <tr>
          <td>「工具鏈統一」很重要</td>
          <td>mdtools（pre-commit、延遲敏感）跟 blogsearch（手動 CLI、每天幾次）使用模式不同，強制統一語言是用「同一棟建築」邏輯要求「不同用途房間用同一種建材」</td>
      </tr>
  </tbody>
</table>
<p>第一次的理由鏈幾乎全數被推翻。如果只看理由，應該換方案。</p>
<h3 id="第二次選型的理由事後">第二次選型的理由（事後）</h3>
<p>重新評估時加入三個第一次沒有的變數：</p>
<p><strong>端到端延遲 vs in-memory benchmark</strong>。84 倍是端到端的數字（Go 151ms 含 I/O vs FAISS 1.8ms in-memory）。但 FAISS 從 disk 載入 index 也要 ~100-200ms，端到端差距縮小到 2 倍。sqlite-vec 是唯一不需要全量載入的方案（disk-based HNSW、端到端 19ms），差距從「84 倍」變成「8 倍」。</p>
<p><strong>使用頻率決定 ROI</strong>。CLI 工具、每天 ~10 次手動 query。每次省 130ms（151 vs 19），一天省 1.3 秒。重寫投入 2-3 小時，回本時間 ≈ 19 年。注意這個計算對頻率極敏感：每天 100 次（如被整合進 MCP server 當 agent 工具）回本縮短到 1.9 年、每天 1000 次則 69 天。上方 HNSW ROI 也用每天 100 次計算 — 兩處頻率假設不同是因為比較對象不同（HNSW build 成本 vs 語言重寫成本），但讀者套到自己場景時應先確定自己的查詢頻率。</p>
<p><strong>Ingest 瓶頸在 Ollama API、跟語言無關</strong>。~4 分鐘的 ingest 裡、embedding API call 佔 95% 以上。換 Python 不會改善 ingest 速度。</p>
<h3 id="維持的理由是痛點不存在">維持的理由是「痛點不存在」</h3>
<p>維持 Go 的理由是<strong>改善的絕對收益太小、投入回不了本</strong> — 151ms 對 CLI 使用模式不構成痛點，與「Go 好」或「工具鏈統一」無關。</p>
<h3 id="這個翻轉的教學意義">這個翻轉的教學意義</h3>
<p>正確的結論配錯誤的理由是脆弱的。第一次 WRAP 的結論（選 Go）在當時是對的，但理由鏈（工具鏈統一、&lt; 10ms）被實測推翻後，如果不重新建立正確的理由鏈，下次環境變動（比如 blogsearch 從 CLI 變成 API server）就會用已失效的理由做出錯誤判斷。</p>
<p>判讀工具選型時，要區分三層：</p>
<ol>
<li><strong>結論</strong>：選什麼方案</li>
<li><strong>理由</strong>：為什麼選（可能被推翻）</li>
<li><strong>前提</strong>：理由依賴的假設（規模、使用模式、效能數字）</li>
</ol>
<p>前提變了、理由就要重建，即使結論沒變。寫進決策紀錄時，三層都要記 — 只記結論的話，下次重新評估時沒有判讀基礎。</p>
<p>區分「正當理由重建」跟「動機性推理」（先有結論再找理由）的判準：新理由是否在看到數據之前也能成立？本例的「130ms 對 CLI 不痛」在實測前也成立（CLI 使用模式本來就低頻），所以是正當重建。如果新理由只能在看到特定數字之後才講得通（如「151ms 剛好在 200ms 閾值內」——但閾值是事後設的），就是 post-hoc rationalization。</p>
<h3 id="觸發換方案的訊號">觸發換方案的訊號</h3>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>門檻</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Query 延遲不可接受</td>
          <td>&gt; 500ms</td>
          <td>先加 mmap（最小改動）</td>
      </tr>
      <tr>
          <td>使用模式改變</td>
          <td>從 CLI 變 API server</td>
          <td>換 Python sqlite-vec</td>
      </tr>
      <tr>
          <td>查詢頻率跳增</td>
          <td>被整合進 MCP server / agent 工具</td>
          <td>評估 daemon mode 或換 sqlite-vec</td>
      </tr>
      <tr>
          <td>Corpus 規模跳增</td>
          <td>&gt; 50K chunks</td>
          <td>重跑 benchmark</td>
      </tr>
      <tr>
          <td>需要原生 metadata filter</td>
          <td>code filter 維護成本過高</td>
          <td>換 Python sqlite-vec</td>
      </tr>
  </tbody>
</table>
<h2 id="embedding-model-選型對應-412-constraint-優先序">Embedding model 選型（對應 4.12 constraint 優先序）</h2>
<p>選 <code>nomic-embed-text</code> 的理由鏈：</p>
<ol>
<li><strong>Ollama 原生支援</strong>：<code>ollama pull</code> 一行、不需要額外 Python library 或 API key</li>
<li><strong>體積小</strong>：274 MB、跟 chat model 共用記憶體不打架</li>
<li><strong>已有驗證基線</strong>：rag-demo 用同一個模型跑過 463 chunks、retrieval 命中率確認可用</li>
<li><strong>768 維 sweet spot</strong>：24K chunks × 768 dim × 4 bytes = 70.9 MB，brute-force 可行</li>
</ol>
<p>未來如果 CJK retrieval 品質不夠（目前可用但未做系統性評估），<code>multilingual-e5-large</code> 或 <code>bge-m3</code> 是備選。換模型只需改 <code>embed.go</code> 的 Model 變數 + 重新 <code>blogsearch ingest</code>（4.22 的「四層可替換」設計）。</p>
<h2 id="cjk-混合-chunking-觀察">CJK 混合 Chunking 觀察</h2>
<p>Blog 內容是繁體中文 + 英文術語混合。Chunking 策略沿用 rag-demo 的 paragraph-aware split（空白行切段、soft token cap 400）。</p>
<p>Token 估算用 <code>len(s) / 2</code> 的 heuristic（CJK 字元多算一次）。不精確但 chunking 只需要粗略估算。跟 tokenizer 精確計算的差異在 ±20%、對 chunking 品質影響小於 chunk 邊界選擇的影響。</p>
<p>實際觀察：24,216 chunks 的 retrieval 品質在語意搜尋場景（「哪些文章跟 retry 有關」「RAG storage 選型」）表現良好。keyword 精確搜尋場景（「找 RFC 7807」）表現較弱 — 這是 embedding-only retrieval 的已知限制（見 4.1 的語意 vs 字面相似度對比），未來可加 BM25 做 hybrid search。</p>
<h2 id="跟其他章節的對應">跟其他章節的對應</h2>
<table>
  <thead>
      <tr>
          <th>本案例的段落</th>
          <th>對應原理章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>選型過程</td>
          <td><a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 演化階梯 + 工程約束</a></td>
      </tr>
      <tr>
          <td>二次選型評估</td>
          <td><a href="/blog/llm/04-applications/vector-storage-engineering/" data-link-title="4.22 RAG storage 工程：從 pickle 到 vector database 的選型判讀" data-link-desc="RAG storage backend 選型：規模到哪個階段該從 in-memory 升級到 vector DB、dependency chain 如何收窄選項">4.22 同 corpus 實測比較</a></td>
      </tr>
      <tr>
          <td>Embedding 選型</td>
          <td><a href="/blog/llm/04-applications/embedding-model-internals/" data-link-title="4.12 Embedding model 內部：訓練、選型、in-domain fine-tune" data-link-desc="Embedding model 怎麼訓練（contrastive learning &#43; hard negative mining）、怎麼挑（MTEB / 大小 / domain）、何時該自己 fine-tune">4.12 實務選型 constraint 優先序</a></td>
      </tr>
      <tr>
          <td>Chunking</td>
          <td><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 Chunking 策略對比</a></td>
      </tr>
      <tr>
          <td>Benchmark 方法論</td>
          <td><a href="/blog/llm/04-applications/benchmarking-and-evaluation/" data-link-title="4.14 Benchmarking 與評估方法論" data-link-desc="判讀 model card benchmark 數字、做自己工作流的 in-house benchmark、量測本地推論速度的完整方法論">4.14 Benchmarking 方法論</a></td>
      </tr>
      <tr>
          <td>Storage 格式設計</td>
          <td><a href="/blog/llm/04-applications/artifact-management/" data-link-title="4.10 衍生產物管理原理：什麼進 git、什麼不該" data-link-desc="LLM 應用的 source / derived / external 三類產物對應 git / build cache / registry、與 production 部署的 reproducibility / cost / share 取捨">4.10 衍生產物管理</a></td>
      </tr>
      <tr>
          <td>Retrieval 品質</td>
          <td><a href="/blog/llm/04-applications/rag-principles/" data-link-title="4.1 RAG 原理：retrieval &#43; augmentation 模式" data-link-desc="為什麼模型需要外掛知識、語意相似 vs 字面相似、chunking 的本質取捨、retrieval 失敗的根本原因">4.1 Retrieval 失敗根因</a></td>
      </tr>
  </tbody>
</table>
]]></content:encoded></item><item><title>SQLite Backend 效能基準</title><link>https://tarrragon.github.io/blog/monitoring/04-collector/sqlite-performance-baseline/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/04-collector/sqlite-performance-baseline/</guid><description>&lt;p>SQLite Backend 的效能受三個因素影響：儲存裝置（SSD vs HDD vs SD card）、Go driver 選擇（modernc.org/sqlite pure Go vs mattn/go-sqlite3 CGO）、並發模型（WAL mode + single-writer）。本章根據 SQLite 的技術特性和業界基準推導預期效能範圍，並提供實測方法讓使用者在自己的環境驗證。所有數字是預期範圍而非實測值 — 實際效能依硬體和 workload 而定。&lt;/p>
&lt;h2 id="寫入吞吐">寫入吞吐&lt;/h2>
&lt;p>寫入吞吐決定 collector 每秒能消化多少事件。SQLite 的寫入效能主要受 fsync 頻率和 WAL checkpoint 影響。&lt;/p>
&lt;h3 id="單筆-insert">單筆 INSERT&lt;/h3>
&lt;p>每筆 INSERT 獨立一個 transaction 時，每次 commit 都會 fsync。WAL mode 的 fsync 成本比 journal mode 低（append-only），但仍是寫入的主要瓶頸。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>儲存裝置&lt;/th>
 &lt;th>單筆 INSERT 延遲&lt;/th>
 &lt;th>理論上限&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>NVMe SSD&lt;/td>
 &lt;td>10-30 μs&lt;/td>
 &lt;td>30,000-100,000 inserts/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SATA SSD&lt;/td>
 &lt;td>30-50 μs&lt;/td>
 &lt;td>20,000-30,000 inserts/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>HDD&lt;/td>
 &lt;td>50-200 μs&lt;/td>
 &lt;td>5,000-20,000 inserts/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SD card&lt;/td>
 &lt;td>500-2000 μs&lt;/td>
 &lt;td>500-2,000 inserts/sec&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>modernc.org/sqlite（pure Go）的效能約為 CGO driver（mattn/go-sqlite3）的 60-80%。上表數字基於 CGO driver，pure Go 需打八折。Go HTTP handler 的開銷（JSON 解碼、schema 驗證、goroutine 調度）再扣 10-20%。&lt;/p>
&lt;h3 id="批次-insert">批次 INSERT&lt;/h3>
&lt;p>一個 transaction 包裹多筆 INSERT，只做一次 fsync。Collector 接收 SDK 的 flush batch（一個 HTTP request 帶一批事件）天然適合批次寫入。&lt;/p>
&lt;p>吞吐提升幅度和批次大小的關係：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>批次大小&lt;/th>
 &lt;th>相對單筆的吞吐提升&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>10 筆/tx&lt;/td>
 &lt;td>3-5x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>100 筆/tx&lt;/td>
 &lt;td>5-10x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1000 筆/tx&lt;/td>
 &lt;td>8-15x&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>提升來自 fsync 次數從「每筆一次」降到「每批一次」。超過 100 筆/tx 後邊際收益遞減。&lt;/p>
&lt;h3 id="實際預期">實際預期&lt;/h3>
&lt;p>結合 pure Go driver、HTTP handler 開銷和批次寫入，不同環境下的預期吞吐：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>環境&lt;/th>
 &lt;th>單筆&lt;/th>
 &lt;th>批次（100/tx）&lt;/th>
 &lt;th>適合場景&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Mac M1/M2 NVMe + pure Go&lt;/td>
 &lt;td>~5,000/sec&lt;/td>
 &lt;td>~30,000/sec&lt;/td>
 &lt;td>開發機&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Linux VPS SATA SSD&lt;/td>
 &lt;td>~3,000/sec&lt;/td>
 &lt;td>~20,000/sec&lt;/td>
 &lt;td>小型部署&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Raspberry Pi 4 SD card&lt;/td>
 &lt;td>~200/sec&lt;/td>
 &lt;td>~1,000/sec&lt;/td>
 &lt;td>邊緣設備&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="和事件產生速率的對照">和事件產生速率的對照&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>預估 events/sec&lt;/th>
 &lt;th>SQLite 批次能撐嗎&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>自用 1 個 app&lt;/td>
 &lt;td>&amp;lt; 10&lt;/td>
 &lt;td>遠超需求&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>小團隊 5 人各跑 1 個 app&lt;/td>
 &lt;td>&amp;lt; 50&lt;/td>
 &lt;td>綽綽有餘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>10 SDK 同時 flush&lt;/td>
 &lt;td>100-1000 burst&lt;/td>
 &lt;td>批次 INSERT 撐得住&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>100+ 使用者持續活躍&lt;/td>
 &lt;td>500+ 持續&lt;/td>
 &lt;td>邊界 — 觀察 database is locked&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>burst 和持續的差異在於：burst 是短暫的高峰（flush batch 到達後數秒內消化完），持續是長時間的穩定高流量。SQLite 的 WAL mode 對 burst 容忍度高（write lock 等待時間短），對持續高流量容忍度有限（write lock 等待累積）。&lt;/p></description><content:encoded><![CDATA[<p>SQLite Backend 的效能受三個因素影響：儲存裝置（SSD vs HDD vs SD card）、Go driver 選擇（modernc.org/sqlite pure Go vs mattn/go-sqlite3 CGO）、並發模型（WAL mode + single-writer）。本章根據 SQLite 的技術特性和業界基準推導預期效能範圍，並提供實測方法讓使用者在自己的環境驗證。所有數字是預期範圍而非實測值 — 實際效能依硬體和 workload 而定。</p>
<h2 id="寫入吞吐">寫入吞吐</h2>
<p>寫入吞吐決定 collector 每秒能消化多少事件。SQLite 的寫入效能主要受 fsync 頻率和 WAL checkpoint 影響。</p>
<h3 id="單筆-insert">單筆 INSERT</h3>
<p>每筆 INSERT 獨立一個 transaction 時，每次 commit 都會 fsync。WAL mode 的 fsync 成本比 journal mode 低（append-only），但仍是寫入的主要瓶頸。</p>
<table>
  <thead>
      <tr>
          <th>儲存裝置</th>
          <th>單筆 INSERT 延遲</th>
          <th>理論上限</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>NVMe SSD</td>
          <td>10-30 μs</td>
          <td>30,000-100,000 inserts/sec</td>
      </tr>
      <tr>
          <td>SATA SSD</td>
          <td>30-50 μs</td>
          <td>20,000-30,000 inserts/sec</td>
      </tr>
      <tr>
          <td>HDD</td>
          <td>50-200 μs</td>
          <td>5,000-20,000 inserts/sec</td>
      </tr>
      <tr>
          <td>SD card</td>
          <td>500-2000 μs</td>
          <td>500-2,000 inserts/sec</td>
      </tr>
  </tbody>
</table>
<p>modernc.org/sqlite（pure Go）的效能約為 CGO driver（mattn/go-sqlite3）的 60-80%。上表數字基於 CGO driver，pure Go 需打八折。Go HTTP handler 的開銷（JSON 解碼、schema 驗證、goroutine 調度）再扣 10-20%。</p>
<h3 id="批次-insert">批次 INSERT</h3>
<p>一個 transaction 包裹多筆 INSERT，只做一次 fsync。Collector 接收 SDK 的 flush batch（一個 HTTP request 帶一批事件）天然適合批次寫入。</p>
<p>吞吐提升幅度和批次大小的關係：</p>
<table>
  <thead>
      <tr>
          <th>批次大小</th>
          <th>相對單筆的吞吐提升</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>10 筆/tx</td>
          <td>3-5x</td>
      </tr>
      <tr>
          <td>100 筆/tx</td>
          <td>5-10x</td>
      </tr>
      <tr>
          <td>1000 筆/tx</td>
          <td>8-15x</td>
      </tr>
  </tbody>
</table>
<p>提升來自 fsync 次數從「每筆一次」降到「每批一次」。超過 100 筆/tx 後邊際收益遞減。</p>
<h3 id="實際預期">實際預期</h3>
<p>結合 pure Go driver、HTTP handler 開銷和批次寫入，不同環境下的預期吞吐：</p>
<table>
  <thead>
      <tr>
          <th>環境</th>
          <th>單筆</th>
          <th>批次（100/tx）</th>
          <th>適合場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Mac M1/M2 NVMe + pure Go</td>
          <td>~5,000/sec</td>
          <td>~30,000/sec</td>
          <td>開發機</td>
      </tr>
      <tr>
          <td>Linux VPS SATA SSD</td>
          <td>~3,000/sec</td>
          <td>~20,000/sec</td>
          <td>小型部署</td>
      </tr>
      <tr>
          <td>Raspberry Pi 4 SD card</td>
          <td>~200/sec</td>
          <td>~1,000/sec</td>
          <td>邊緣設備</td>
      </tr>
  </tbody>
</table>
<h3 id="和事件產生速率的對照">和事件產生速率的對照</h3>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>預估 events/sec</th>
          <th>SQLite 批次能撐嗎</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>自用 1 個 app</td>
          <td>&lt; 10</td>
          <td>遠超需求</td>
      </tr>
      <tr>
          <td>小團隊 5 人各跑 1 個 app</td>
          <td>&lt; 50</td>
          <td>綽綽有餘</td>
      </tr>
      <tr>
          <td>10 SDK 同時 flush</td>
          <td>100-1000 burst</td>
          <td>批次 INSERT 撐得住</td>
      </tr>
      <tr>
          <td>100+ 使用者持續活躍</td>
          <td>500+ 持續</td>
          <td>邊界 — 觀察 database is locked</td>
      </tr>
  </tbody>
</table>
<p>burst 和持續的差異在於：burst 是短暫的高峰（flush batch 到達後數秒內消化完），持續是長時間的穩定高流量。SQLite 的 WAL mode 對 burst 容忍度高（write lock 等待時間短），對持續高流量容忍度有限（write lock 等待累積）。</p>
<h2 id="查詢延遲">查詢延遲</h2>
<p>查詢延遲決定 dashboard 的刷新體驗。SQLite 的查詢效能取決於索引覆蓋和掃描行數。</p>
<h3 id="有索引的查詢">有索引的查詢</h3>
<p>建議的索引（見 <a href="/blog/monitoring/04-collector/scaling-evolution/" data-link-title="規模演進" data-link-desc="可插拔 Storage Backend 架構 — SQLite 預設、PostgreSQL 觸發切換、時間序列 DB 長期演進">規模演進</a> 的建議索引段）覆蓋 dashboard 的核心查詢模式。有索引時的預期延遲：</p>
<table>
  <thead>
      <tr>
          <th>查詢模式</th>
          <th>10 萬筆</th>
          <th>50 萬筆</th>
          <th>100 萬筆</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>等值查詢（WHERE session_id = ?）</td>
          <td>&lt; 1ms</td>
          <td>&lt; 1ms</td>
          <td>&lt; 1ms</td>
      </tr>
      <tr>
          <td>範圍查詢（WHERE ts BETWEEN ? AND ?）</td>
          <td>&lt; 10ms</td>
          <td>10-50ms</td>
          <td>50-100ms</td>
      </tr>
      <tr>
          <td>GROUP BY name</td>
          <td>10-50ms</td>
          <td>50-200ms</td>
          <td>200-500ms</td>
      </tr>
      <tr>
          <td>COUNT DISTINCT session_id</td>
          <td>50-100ms</td>
          <td>200-500ms</td>
          <td>500ms-1s</td>
      </tr>
      <tr>
          <td>JOIN + window function</td>
          <td>100ms-1s</td>
          <td>1-3s</td>
          <td>3-10s</td>
      </tr>
  </tbody>
</table>
<h3 id="無索引的查詢">無索引的查詢</h3>
<p>無索引時 SQLite 做全表掃描。掃描速度約 50-100 MB/sec（SSD）、10-30 MB/sec（HDD）。</p>
<table>
  <thead>
      <tr>
          <th>資料量</th>
          <th>預估大小</th>
          <th>SSD 全掃延遲</th>
          <th>HDD 全掃延遲</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>10 萬筆</td>
          <td>~40 MB</td>
          <td>200-500ms</td>
          <td>1-3s</td>
      </tr>
      <tr>
          <td>100 萬筆</td>
          <td>~400 MB</td>
          <td>2-5s</td>
          <td>10-30s</td>
      </tr>
      <tr>
          <td>300 萬筆</td>
          <td>~1.2 GB</td>
          <td>5-15s</td>
          <td>30-90s</td>
      </tr>
  </tbody>
</table>
<p>超過 100 萬筆無索引查詢會超出 dashboard 可接受的刷新延遲 — 這是 day-one 就建索引的理由。</p>
<h3 id="dashboard-刷新頻率-vs-查詢延遲">Dashboard 刷新頻率 vs 查詢延遲</h3>
<p>Dashboard 的每個視圖有不同的刷新間隔和可接受延遲。查詢延遲超過可接受值時，dashboard 體驗變差（等待轉圈、資料過時）。</p>
<table>
  <thead>
      <tr>
          <th>Dashboard 視圖</th>
          <th>刷新間隔</th>
          <th>可接受延遲</th>
          <th>10 萬筆有索引</th>
          <th>100 萬筆有索引</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>即時狀態卡</td>
          <td>1-5 秒</td>
          <td>&lt; 100ms</td>
          <td>滿足</td>
          <td>滿足</td>
      </tr>
      <tr>
          <td>Error 列表</td>
          <td>5-10 秒</td>
          <td>&lt; 500ms</td>
          <td>滿足</td>
          <td>滿足</td>
      </tr>
      <tr>
          <td>趨勢圖（最近 24h）</td>
          <td>30 秒</td>
          <td>&lt; 1s</td>
          <td>滿足</td>
          <td>邊界</td>
      </tr>
      <tr>
          <td>長期聚合（最近 30 天）</td>
          <td>5 分鐘</td>
          <td>&lt; 3s</td>
          <td>滿足</td>
          <td>需要預聚合</td>
      </tr>
  </tbody>
</table>
<p>「需要預聚合」代表原始事件的聚合查詢超過可接受延遲，應該依賴分層保留策略中的 hourly_summary / daily_summary 表（見 <a href="/blog/monitoring/04-collector/scaling-evolution/" data-link-title="規模演進" data-link-desc="可插拔 Storage Backend 架構 — SQLite 預設、PostgreSQL 觸發切換、時間序列 DB 長期演進">規模演進</a> 的分層保留段）。</p>
<h2 id="資源消耗">資源消耗</h2>
<h3 id="記憶體">記憶體</h3>
<table>
  <thead>
      <tr>
          <th>元件</th>
          <th>佔用</th>
          <th>備註</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Go HTTP server</td>
          <td>20-50 MB</td>
          <td>基礎開銷</td>
      </tr>
      <tr>
          <td>SQLite page cache</td>
          <td>2 MB（預設）</td>
          <td><code>PRAGMA cache_size</code> 可調</td>
      </tr>
      <tr>
          <td>寫入 buffer（channel）</td>
          <td>1-10 MB</td>
          <td>取決於 channel 容量和事件大小</td>
      </tr>
      <tr>
          <td>查詢結果暫存</td>
          <td>和結果集成正比</td>
          <td>GROUP BY 10 萬筆 ~10 MB</td>
      </tr>
      <tr>
          <td><strong>Collector 整體</strong></td>
          <td><strong>50-100 MB</strong></td>
          <td>自用場景</td>
      </tr>
  </tbody>
</table>
<p>Raspberry Pi（1 GB RAM）上建議把 page cache 調小（<code>PRAGMA cache_size = -512</code> = 512 KB），避免大結果集查詢（加 LIMIT），dashboard 刷新頻率降低。</p>
<h3 id="cpu">CPU</h3>
<table>
  <thead>
      <tr>
          <th>操作</th>
          <th>CPU 使用</th>
          <th>備註</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>INSERT（寫入）</td>
          <td>可忽略</td>
          <td>I/O bound，CPU 不是瓶頸</td>
      </tr>
      <tr>
          <td>SELECT（查詢）</td>
          <td>和掃描行數正比</td>
          <td>有索引時可忽略</td>
      </tr>
      <tr>
          <td>Downsample（每小時）</td>
          <td>短暫 spike &lt; 1s</td>
          <td>處理最近一小時的事件</td>
      </tr>
      <tr>
          <td>Purge（每天）</td>
          <td>短暫 spike 1-3s</td>
          <td>分批 DELETE</td>
      </tr>
      <tr>
          <td><strong>整體</strong></td>
          <td><strong>&lt; 5%</strong></td>
          <td>自用場景</td>
      </tr>
  </tbody>
</table>
<h3 id="磁碟">磁碟</h3>
<table>
  <thead>
      <tr>
          <th>日事件量</th>
          <th>原始資料/天</th>
          <th>原始資料/月</th>
          <th>含索引/月</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1,000（極低）</td>
          <td>0.3-0.5 MB</td>
          <td>9-15 MB</td>
          <td>11-18 MB</td>
      </tr>
      <tr>
          <td>10,000（自用）</td>
          <td>3-5 MB</td>
          <td>90-150 MB</td>
          <td>110-180 MB</td>
      </tr>
      <tr>
          <td>100,000（小團隊）</td>
          <td>30-50 MB</td>
          <td>0.9-1.5 GB</td>
          <td>1.1-1.8 GB</td>
      </tr>
  </tbody>
</table>
<p>WAL 檔案通常 &lt; 10 MB（auto-checkpoint 在 WAL 達到 1000 pages 時觸發）。分層保留策略下，原始事件只保留 7 天，長期佔用由聚合摘要表決定（遠小於原始事件）。</p>
<h2 id="邊緣設備場景">邊緣設備場景</h2>
<p>Raspberry Pi、低配 VPS（1 核 / 1 GB RAM）、甚至 NAS 上跑 collector 時的特殊考量：</p>
<p><strong>SD card 的隨機寫入</strong>：SD card 的隨機寫入 IOPS 極低（100-500 IOPS），WAL mode 的 checkpoint（把 WAL 內容合併回主資料庫檔案）可能卡住 1-5 秒。期間新的寫入等待 checkpoint 完成。建議調高 <code>wal_autocheckpoint</code> 的閾值（如 5000 pages），讓 checkpoint 頻率降低但每次時間更長 — 在非活躍時段（凌晨）手動觸發 <code>PRAGMA wal_checkpoint(TRUNCATE)</code>。</p>
<p><strong>1 GB RAM</strong>：cache_size 調小（512 KB）、避免 <code>SELECT *</code> 不帶 LIMIT、GROUP BY 的結果集用 HAVING 條件過濾減少暫存。Dashboard 的長期聚合直接查 hourly_summary 表而非原始事件。</p>
<p><strong>ARM CPU</strong>：pure Go SQLite driver（modernc.org/sqlite）在 ARM 上的效能差距可能比 x86 更大（pure Go 的 C-to-Go 翻譯在 ARM 的指令最佳化較少）。實測確認。</p>
<p><strong>建議配置</strong>：邊緣設備上 collector 的 dashboard 刷新頻率從預設值降低（即時狀態卡 5 秒 → 30 秒，趨勢圖 30 秒 → 5 分鐘），降採樣 job 頻率從每小時改為每 6 小時。</p>
<h2 id="實測方法指引">實測方法指引</h2>
<p>教學的預期數字是推導值，實際效能取決於使用者的硬體和 workload。Collector 提供內建的 benchmark 命令讓使用者在自己的環境實測。</p>
<h3 id="寫入-benchmark">寫入 benchmark</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 單筆寫入：10000 筆，每筆獨立 transaction</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">./collector benchmark write --events<span class="o">=</span><span class="m">10000</span> --batch<span class="o">=</span><span class="m">1</span> --storage<span class="o">=</span>sqlite
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 批次寫入：10000 筆，每 100 筆一個 transaction</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">./collector benchmark write --events<span class="o">=</span><span class="m">10000</span> --batch<span class="o">=</span><span class="m">100</span> --storage<span class="o">=</span>sqlite</span></span></code></pre></div><p>輸出：total duration、events/sec、p50/p95/p99 latency per event。</p>
<h3 id="查詢-benchmark">查詢 benchmark</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 先灌入測試資料</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">./collector benchmark seed --events<span class="o">=</span><span class="m">100000</span> --storage<span class="o">=</span>sqlite
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 跑查詢 benchmark</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">./collector benchmark query --type<span class="o">=</span>error --group-by<span class="o">=</span>name --storage<span class="o">=</span>sqlite
</span></span><span class="line"><span class="ln">6</span><span class="cl">./collector benchmark query --session-id<span class="o">=</span>random --storage<span class="o">=</span>sqlite</span></span></code></pre></div><p>輸出：query duration、rows scanned、rows returned。</p>
<h3 id="production-觀察指標">Production 觀察指標</h3>
<p>部署後用 DevOps dashboard（見 <a href="/blog/monitoring/04-collector/dashboard-devops/" data-link-title="DevOps Dashboard 設計" data-link-desc="Collector 和 SDK 是否健康 — 日常監控的服務狀態卡、吞吐量曲線、儲存用量，以及告警觸發後的排障視圖">DevOps Dashboard 設計</a>）觀察 collector 自身的效能 metric：</p>
<ul>
<li><code>collector.storage.write_duration_ms</code>：每次寫入的延遲。P95 超過 100ms 是瓶頸訊號。</li>
<li><code>collector.storage.query_duration_ms</code>：每次查詢的延遲。P95 超過 dashboard 刷新間隔是瓶頸訊號。</li>
<li><code>collector.storage.db_size_bytes</code>：資料庫大小。接近磁碟可用空間的 80% 時觸發 purge 或擴容。</li>
<li><code>collector.storage.wal_size_bytes</code>：WAL 檔案大小。持續 &gt; 50 MB 代表 checkpoint 跟不上寫入速度。</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>切換到 PostgreSQL 的觸發條件 → <a href="/blog/monitoring/04-collector/scaling-evolution/" data-link-title="規模演進" data-link-desc="可插拔 Storage Backend 架構 — SQLite 預設、PostgreSQL 觸發切換、時間序列 DB 長期演進">規模演進</a></li>
<li>SQLite 和 PostgreSQL 的功能分層 → <a href="/blog/monitoring/04-collector/feature-tier-boundary/" data-link-title="功能分層與 Backend 選擇" data-link-desc="SQLite 層和 PostgreSQL 層各自承載哪些功能 — 分界線是查詢模式而非資料量、觸發升級的是功能需求而非規模成長">功能分層與 Backend 選擇</a></li>
<li>Ingestion 端的擴展設計 → <a href="/blog/monitoring/04-collector/ingestion-scaling/" data-link-title="Ingestion Scaling" data-link-desc="四層防線應對 ingestion 端的流量擴展 — SDK 取樣、Collector 背壓、水平擴展、Queue 解耦">Ingestion Scaling</a></li>
</ul>
]]></content:encoded></item><item><title>4.14 Benchmarking 與評估方法論</title><link>https://tarrragon.github.io/blog/llm/04-applications/benchmarking-and-evaluation/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/04-applications/benchmarking-and-evaluation/</guid><description>&lt;p>讀 model card 看到「MMLU 78.5」「HumanEval 82.3」「SWE-bench 12.6」等數字、要能判讀對自己場景的意義；自己跑本地 LLM、要能量化「tok/s、TTFT、實際品質」；想對比不同 model / 量化等級、要有可重現的 evaluation 方法。本章把「LLM 能力評估」跟「本地推論性能評估」兩條軸拆成可操作的方法論。&lt;/p>
&lt;p>本章是 eval 設計的&lt;strong>具體實作層&lt;/strong>——meta 層的 eval 軸選擇（先看軸再看工具的三軸座標）見 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 Eval 設計座標系&lt;/a>、subjective eval 的核心工具見 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge&lt;/a>。三章合起來才是 production AI app 的完整 eval pipeline。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>看 model card benchmark 數字、判讀對自己場景的相關性。&lt;/li>
&lt;li>區分 capability benchmark（MMLU 等）跟 performance benchmark（tok/s 等）。&lt;/li>
&lt;li>跑 &lt;code>llama-bench&lt;/code> 量測自己硬體 + 模型的真實速度。&lt;/li>
&lt;li>設計 in-house benchmark 評估自己工作流的真實品質。&lt;/li>
&lt;li>看到 benchmark 異常數字時、知道可能的陷阱。&lt;/li>
&lt;/ol>
&lt;h2 id="capability-benchmarks衡量模型會什麼">Capability benchmarks：衡量模型「會什麼」&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/llm-benchmarks/" data-link-title="LLM Benchmarks（MMLU / HumanEval / SWE-bench 等）" data-link-desc="LLM 能力評估的標準 benchmark 集合：MMLU / HumanEval / MBPP / SWE-bench / MT-Bench 等的覆蓋範圍與失效情境">LLM benchmarks&lt;/a> 卡片列了主流 benchmark 的覆蓋面。本節展開對寫 code 場景最相關的幾個：&lt;/p>
&lt;h3 id="coding-benchmarks-的演化">Coding benchmarks 的演化&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Benchmark&lt;/th>
 &lt;th>任務性質&lt;/th>
 &lt;th>適合衡量&lt;/th>
 &lt;th>飽和狀態&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>HumanEval&lt;/td>
 &lt;td>寫一個 Python function 通過簡單 unit test&lt;/td>
 &lt;td>初級 coding 能力&lt;/td>
 &lt;td>飽和（90%+）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>MBPP&lt;/td>
 &lt;td>同 HumanEval、規模較大&lt;/td>
 &lt;td>同上&lt;/td>
 &lt;td>飽和&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>HumanEval+&lt;/td>
 &lt;td>HumanEval + 更嚴格 test cases&lt;/td>
 &lt;td>排除 edge case 漏寫&lt;/td>
 &lt;td>部分飽和&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>BigCodeBench&lt;/td>
 &lt;td>真實 library use（pandas、numpy 等）&lt;/td>
 &lt;td>中級 coding&lt;/td>
 &lt;td>進行中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>LiveCodeBench&lt;/td>
 &lt;td>LeetCode 風格 problems、定期更新避免污染&lt;/td>
 &lt;td>Algorithm + reasoning&lt;/td>
 &lt;td>進行中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>SWE-bench&lt;/strong>&lt;/td>
 &lt;td>真實 GitHub issue 修復、要看懂 codebase&lt;/td>
 &lt;td>真實 coding agent 能力&lt;/td>
 &lt;td>仍有大空間（前沿 &amp;lt; 60%）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>SWE-bench Verified&lt;/strong>&lt;/td>
 &lt;td>SWE-bench 的人工 verify 子集&lt;/td>
 &lt;td>同上、更可靠&lt;/td>
 &lt;td>同上&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>判讀建議：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>看 SWE-bench、別只看 HumanEval&lt;/strong>：HumanEval 早飽和、無法區分前沿模型；SWE-bench 仍有大差距、可信度高&lt;/li>
&lt;li>&lt;strong>HumanEval 90% vs 95% 差異不大&lt;/strong>：飽和區間的 noise 大、判斷 coding 能力靠 SWE-bench / 真實任務測&lt;/li>
&lt;li>&lt;strong>LiveCodeBench 避免污染&lt;/strong>：定期出新題、模型訓練 cutoff 後的題目不在 pretrain corpus、更能反映真實能力&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>&lt;strong>事實查核註&lt;/strong>：本章所列 benchmark 飽和狀態（HumanEval 90%+、MMLU 85%+、GSM8K 90%+）、SOTA 數字（SWE-bench &amp;lt; 60%）、各模型在各 benchmark 的相對排名 — 都是 2026/5 估計、隨新模型推出快速變動、引用前以 &lt;a href="https://paperswithcode.com/">Papers with Code&lt;/a> 跟 &lt;a href="https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard">HuggingFace Open LLM Leaderboard&lt;/a> 當前狀態為準。&lt;/p></description><content:encoded><![CDATA[<p>讀 model card 看到「MMLU 78.5」「HumanEval 82.3」「SWE-bench 12.6」等數字、要能判讀對自己場景的意義；自己跑本地 LLM、要能量化「tok/s、TTFT、實際品質」；想對比不同 model / 量化等級、要有可重現的 evaluation 方法。本章把「LLM 能力評估」跟「本地推論性能評估」兩條軸拆成可操作的方法論。</p>
<p>本章是 eval 設計的<strong>具體實作層</strong>——meta 層的 eval 軸選擇（先看軸再看工具的三軸座標）見 <a href="/blog/llm/04-applications/eval-design-framework/" data-link-title="4.13 Eval 設計座標系：三軸、八象限、何時測什麼" data-link-desc="Eval 設計三軸（objective↔subjective / component↔end-to-end / quantitative↔qualitative）、八象限的對應 eval 工具、軸選錯的訊號、跟 benchmarking / LLM-as-judge / tracing 的關係">4.13 Eval 設計座標系</a>、subjective eval 的核心工具見 <a href="/blog/llm/04-applications/llm-as-judge/" data-link-title="4.21 LLM-as-Judge 評估方法" data-link-desc="LLM 評估 LLM 的 production eval 方法：rubric design、pairwise / direct scoring、三大 bias 緩解、跟 trace 串接的閉環、calibration">4.21 LLM-as-Judge</a>。三章合起來才是 production AI app 的完整 eval pipeline。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>看 model card benchmark 數字、判讀對自己場景的相關性。</li>
<li>區分 capability benchmark（MMLU 等）跟 performance benchmark（tok/s 等）。</li>
<li>跑 <code>llama-bench</code> 量測自己硬體 + 模型的真實速度。</li>
<li>設計 in-house benchmark 評估自己工作流的真實品質。</li>
<li>看到 benchmark 異常數字時、知道可能的陷阱。</li>
</ol>
<h2 id="capability-benchmarks衡量模型會什麼">Capability benchmarks：衡量模型「會什麼」</h2>
<p><a href="/blog/llm/knowledge-cards/llm-benchmarks/" data-link-title="LLM Benchmarks（MMLU / HumanEval / SWE-bench 等）" data-link-desc="LLM 能力評估的標準 benchmark 集合：MMLU / HumanEval / MBPP / SWE-bench / MT-Bench 等的覆蓋範圍與失效情境">LLM benchmarks</a> 卡片列了主流 benchmark 的覆蓋面。本節展開對寫 code 場景最相關的幾個：</p>
<h3 id="coding-benchmarks-的演化">Coding benchmarks 的演化</h3>
<table>
  <thead>
      <tr>
          <th>Benchmark</th>
          <th>任務性質</th>
          <th>適合衡量</th>
          <th>飽和狀態</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>HumanEval</td>
          <td>寫一個 Python function 通過簡單 unit test</td>
          <td>初級 coding 能力</td>
          <td>飽和（90%+）</td>
      </tr>
      <tr>
          <td>MBPP</td>
          <td>同 HumanEval、規模較大</td>
          <td>同上</td>
          <td>飽和</td>
      </tr>
      <tr>
          <td>HumanEval+</td>
          <td>HumanEval + 更嚴格 test cases</td>
          <td>排除 edge case 漏寫</td>
          <td>部分飽和</td>
      </tr>
      <tr>
          <td>BigCodeBench</td>
          <td>真實 library use（pandas、numpy 等）</td>
          <td>中級 coding</td>
          <td>進行中</td>
      </tr>
      <tr>
          <td>LiveCodeBench</td>
          <td>LeetCode 風格 problems、定期更新避免污染</td>
          <td>Algorithm + reasoning</td>
          <td>進行中</td>
      </tr>
      <tr>
          <td><strong>SWE-bench</strong></td>
          <td>真實 GitHub issue 修復、要看懂 codebase</td>
          <td>真實 coding agent 能力</td>
          <td>仍有大空間（前沿 &lt; 60%）</td>
      </tr>
      <tr>
          <td><strong>SWE-bench Verified</strong></td>
          <td>SWE-bench 的人工 verify 子集</td>
          <td>同上、更可靠</td>
          <td>同上</td>
      </tr>
  </tbody>
</table>
<p>判讀建議：</p>
<ol>
<li><strong>看 SWE-bench、別只看 HumanEval</strong>：HumanEval 早飽和、無法區分前沿模型；SWE-bench 仍有大差距、可信度高</li>
<li><strong>HumanEval 90% vs 95% 差異不大</strong>：飽和區間的 noise 大、判斷 coding 能力靠 SWE-bench / 真實任務測</li>
<li><strong>LiveCodeBench 避免污染</strong>：定期出新題、模型訓練 cutoff 後的題目不在 pretrain corpus、更能反映真實能力</li>
</ol>
<blockquote>
<p><strong>事實查核註</strong>：本章所列 benchmark 飽和狀態（HumanEval 90%+、MMLU 85%+、GSM8K 90%+）、SOTA 數字（SWE-bench &lt; 60%）、各模型在各 benchmark 的相對排名 — 都是 2026/5 估計、隨新模型推出快速變動、引用前以 <a href="https://paperswithcode.com/">Papers with Code</a> 跟 <a href="https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard">HuggingFace Open LLM Leaderboard</a> 當前狀態為準。</p></blockquote>
<h3 id="reasoning-benchmarks">Reasoning benchmarks</h3>
<table>
  <thead>
      <tr>
          <th>Benchmark</th>
          <th>任務性質</th>
          <th>主要 audience</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>MMLU</td>
          <td>通用知識多選</td>
          <td>Pretrain 能力</td>
      </tr>
      <tr>
          <td>MMLU-Pro</td>
          <td>MMLU 更困難版本、5 → 10 選 1</td>
          <td>同上、區分前沿模型</td>
      </tr>
      <tr>
          <td>GSM8K</td>
          <td>小學數學 word problem</td>
          <td>早期 reasoning</td>
      </tr>
      <tr>
          <td>MATH</td>
          <td>高中 / 競賽數學</td>
          <td>中級 reasoning</td>
      </tr>
      <tr>
          <td>AIME / GPQA</td>
          <td>競賽數學 / graduate-level science</td>
          <td><a href="/blog/llm/03-theoretical-foundations/reasoning-models/" data-link-title="3.8 Reasoning models：test-time compute paradigm" data-link-desc="Chain-of-thought 從 prompting 技巧演化成訓練 paradigm、reasoning model 的內部運作、本地可跑的選項與適用任務">Reasoning models</a></td>
      </tr>
      <tr>
          <td>ARC-AGI</td>
          <td>視覺 reasoning puzzle</td>
          <td>General reasoning</td>
      </tr>
  </tbody>
</table>
<p>判讀：</p>
<ol>
<li><strong>Reasoning model 在 AIME / GPQA 顯著領先 instruct model</strong>：這正是 reasoning model 的優勢區</li>
<li><strong>MMLU 飽和</strong>：85%+ 後差別意義不大、改看 MMLU-Pro</li>
<li><strong>GSM8K 接近飽和</strong>：90%+、改看 MATH / AIME</li>
</ol>
<h3 id="long-context-benchmarks">Long context benchmarks</h3>
<table>
  <thead>
      <tr>
          <th>Benchmark</th>
          <th>任務性質</th>
          <th>衡量</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/needle-in-haystack/" data-link-title="Needle in a Haystack" data-link-desc="把一個事實藏在 long context 不同位置、測試 LLM 能否抓出來的 benchmark 方法">Needle in haystack</a></td>
          <td>抓單一事實</td>
          <td>Lower bound effective context</td>
      </tr>
      <tr>
          <td>RULER</td>
          <td>Multi-needle、aggregation、reasoning</td>
          <td>真實 long context 能力</td>
      </tr>
      <tr>
          <td>LongBench</td>
          <td>QA、summarization、code 等真實任務</td>
          <td>全方面 long context</td>
      </tr>
      <tr>
          <td>∞Bench</td>
          <td>100K+ context tasks</td>
          <td>極長 context</td>
      </tr>
  </tbody>
</table>
<p>判讀：聲稱「128K context」要配 RULER / LongBench 分數才知道實用、見 <a href="/blog/llm/04-applications/long-context-engineering/" data-link-title="4.11 Long context engineering" data-link-desc="128K / 1M context 模型怎麼用：claimed vs effective context、lost-in-the-middle、context 設計策略、Long context vs RAG 取捨">4.11 Long context engineering</a>。</p>
<h2 id="performance-benchmarks衡量跑多快">Performance benchmarks：衡量「跑多快」</h2>
<p>跟 capability 並列的另一條軸 — 推論速度：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>定義</th>
          <th>影響使用者體感</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/tokens-per-second/" data-link-title="Tokens Per Second" data-link-desc="LLM 每秒能生成幾個 token：生字速度的標準量化指標">Tokens per second</a></td>
          <td>生成速度（tok/s）</td>
          <td>連續輸出感受</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">TTFT</a></td>
          <td>Time to first token</td>
          <td>「按下 enter 多久才看到字」</td>
      </tr>
      <tr>
          <td>Prefill speed</td>
          <td>Prompt 處理速度（tok/s）</td>
          <td>長 prompt 的等待時間</td>
      </tr>
      <tr>
          <td>Memory footprint</td>
          <td>推論記憶體佔用</td>
          <td>能不能塞進機器</td>
      </tr>
      <tr>
          <td>Energy consumption</td>
          <td>推論電力</td>
          <td>長期使用成本</td>
      </tr>
  </tbody>
</table>
<h3 id="llama-bench標準工具">llama-bench：標準工具</h3>
<p>llama.cpp 內建 benchmark 工具：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 基本測試：純 generation 速度</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">llama-bench -m model.gguf -p <span class="m">512</span> -n <span class="m">128</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># -p 512：prompt 512 token（測 prefill）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># -n 128：generate 128 token（測 decode）</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 不同 context 長度的影響</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">llama-bench -m model.gguf -p 512,2048,8192 -n <span class="m">128</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 開 flash attention</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">llama-bench -m model.gguf -p <span class="m">512</span> -n <span class="m">128</span> -fa <span class="m">1</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># Speculative decoding 對比</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">llama-bench -m target.gguf --draft-model drafter.gguf <span class="se">\
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="se"></span>            -p <span class="m">512</span> -n <span class="m">128</span> --speculative-draft <span class="m">5</span></span></span></code></pre></div><p>輸出範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">| model                |       size |     params | backend    | ngl |   test |              t/s |
</span></span><span class="line"><span class="ln">2</span><span class="cl">| -------------------- | ---------: | ---------: | ---------- | --: | -----: | ---------------: |
</span></span><span class="line"><span class="ln">3</span><span class="cl">| gemma3 31B Q4_K - M  |  18.45 GiB |    31.21 B | Metal      |  99 |  pp512 |    324.21 ± 1.27 |
</span></span><span class="line"><span class="ln">4</span><span class="cl">| gemma3 31B Q4_K - M  |  18.45 GiB |    31.21 B | Metal      |  99 |  tg128 |     28.43 ± 0.31 |</span></span></code></pre></div><p>讀法：</p>
<ul>
<li><code>pp512</code>：prefill 512 token 的 throughput（tok/s）</li>
<li><code>tg128</code>：generate 128 token 的 throughput（tok/s、即 tok/s）</li>
<li><code>± 0.31</code>：多次跑的 std deviation、&lt; 5% 是穩定基線</li>
</ul>
<h3 id="推論成本-vs-品質的-trade-off-矩陣">推論成本 vs 品質的 trade-off 矩陣</h3>
<p>對自己機器跑 <code>llama-bench</code> 後、可以建一個矩陣：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">                     tok/s 高           tok/s 中           tok/s 低
</span></span><span class="line"><span class="ln">2</span><span class="cl">品質（HumanEval）
</span></span><span class="line"><span class="ln">3</span><span class="cl">     高              [Q4 7B coder]      [Q4 14B coder]    [Q4 30B reasoning]
</span></span><span class="line"><span class="ln">4</span><span class="cl">     中              [Q4 14B instruct]  [Q4 30B instruct]
</span></span><span class="line"><span class="ln">5</span><span class="cl">     低              [Q4 30B base]      [unused]          [unused]</span></span></code></pre></div><p>對應到實際選型：</p>
<ul>
<li>自動補完（高頻、低品質需求）：左上 tok/s 高的小模型</li>
<li>對話（中頻、中品質需求）：中段</li>
<li>複雜 reasoning（低頻、高品質需求）：右下大 reasoning model</li>
</ul>
<h2 id="in-house-benchmark自己工作流的真實評估">In-house benchmark：自己工作流的真實評估</h2>
<p>最重要的 benchmark 是「自己真實任務上的表現」、公開 benchmark 是粗略 filter。</p>
<h3 id="建立-in-house-benchmark-的步驟">建立 in-house benchmark 的步驟</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">1. 蒐集真實案例
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   - 從過往工作流挑 30-100 個有代表性的任務
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">   - 含「容易任務」「中等任務」「困難任務」三類
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   - 每個任務記錄 (input prompt, expected output 或評分標準)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">2. 定義評分機制
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   - Objective（最理想）：unit test、exact match、能機械驗證
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">   - Semi-objective：rubric 評分、人工或 LLM-as-judge
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   - Subjective（最後手段）：人工 A/B 偏好
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">3. 跑 candidate models
</span></span><span class="line"><span class="ln">12</span><span class="cl">   - 對每個模型、每個任務都跑、記錄輸出
</span></span><span class="line"><span class="ln">13</span><span class="cl">   - 注意推論參數一致（temperature、top-p、max_tokens 一樣）
</span></span><span class="line"><span class="ln">14</span><span class="cl">   - 注意 prompt 一致（chat template、system prompt）
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">4. 評分
</span></span><span class="line"><span class="ln">17</span><span class="cl">   - Objective：跑 test、算 pass rate
</span></span><span class="line"><span class="ln">18</span><span class="cl">   - Semi-objective：建 rubric、評分
</span></span><span class="line"><span class="ln">19</span><span class="cl">   - Subjective：人工 / LLM 評
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">5. 看分佈、不只看平均
</span></span><span class="line"><span class="ln">22</span><span class="cl">   - 平均 80% 可能來自「20 題滿分 + 80 題 70%」或「100 題 80%」
</span></span><span class="line"><span class="ln">23</span><span class="cl">   - 看 std、看哪些任務崩、針對性 debug</span></span></code></pre></div><h3 id="llm-as-judge-的注意點">LLM-as-judge 的注意點</h3>
<p>用 LLM（如 GPT-4、Claude）評其他 LLM 是省人力的方法、但有 bias：</p>
<ol>
<li><strong>Verbosity bias</strong>：judge 傾向給「答得長」的高分、即使內容沒提升</li>
<li><strong>Position bias</strong>：A/B 比較時、judge 對 A、B 位置敏感、要做 swap 平均</li>
<li><strong>Self-preference bias</strong>：judge 模型偏好自己風格的答案</li>
<li><strong>Judge 能力上限</strong>：judge 模型本身不夠強、評不出兩個強模型的差距</li>
</ol>
<p>緩解：</p>
<ol>
<li><strong>用結構化 rubric</strong>：給 judge 明確評分標準、不只「哪個好」</li>
<li><strong>多 judge 取共識</strong>：用 2-3 個不同 judge model 各評、取一致 / 平均</li>
<li><strong>Critical task 仍要人工 review</strong>：高 stake 任務不能全靠 LLM-as-judge</li>
</ol>
<h2 id="常見陷阱跟反例">常見陷阱跟反例</h2>
<h3 id="陷阱-1訓練資料污染">陷阱 1：訓練資料污染</h3>
<p>模型在 benchmark 題目上「看似強」、實際是 memorization：</p>
<p>判讀訊號：</p>
<ul>
<li>benchmark cutoff date 之前的 dataset、新模型分數異常高</li>
<li>同模型在「同 dataset 變體（rephrase）」上分數顯著低</li>
</ul>
<p>緩解：用較新出題的 benchmark（如 LiveCodeBench 定期更新）。</p>
<h3 id="陷阱-2single-benchmark-過擬合">陷阱 2：Single benchmark 過擬合</h3>
<p>模型廠商針對特定 benchmark fine-tune、benchmark 高但通用能力沒提升：</p>
<p>判讀訊號：</p>
<ul>
<li>在 benchmark A 顯著領先、在 benchmark B（測類似能力）沒差</li>
<li>同模型實際使用後評價跟 benchmark 不符</li>
</ul>
<p>緩解：看多個 benchmark + in-house benchmark。</p>
<h3 id="陷阱-3prompt-sensitivity">陷阱 3：Prompt sensitivity</h3>
<p>同 benchmark 用不同 prompt 格式、score 差幾個百分點：</p>
<p>判讀訊號：</p>
<ul>
<li>model card 報的數字跟自己跑差很多</li>
<li>同模型不同 prompt template 結果差距大</li>
</ul>
<p>緩解：自己跑、用一致的 prompt template；report 時明確標 prompt 版本。</p>
<h3 id="陷阱-4sampling-設定不一致">陷阱 4：Sampling 設定不一致</h3>
<p>不同模型用不同 temperature / top-p、結果不可比：</p>
<p>判讀訊號：</p>
<ul>
<li>兩篇 paper 用同 benchmark 報不同數字、推論參數不同</li>
</ul>
<p>緩解：對 reproduction 用 temperature=0 + greedy decoding 確保一致。</p>
<h2 id="benchmark-之間的關係跟導讀路徑">Benchmark 之間的關係跟導讀路徑</h2>
<p>各 benchmark 在不同階段的角色：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">研究模型能力（paper 階段）：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  HELM / MT-Bench / Chatbot Arena → 通用能力 baseline
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  MMLU / GSM8K / AIME            → reasoning 能力
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  HumanEval / SWE-bench           → coding 能力
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  RULER / LongBench               → long context
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">挑選模型（user 階段）：
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  Open LLM Leaderboard            → 快速 filter
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  MTEB（若 RAG）                  → embedding model
</span></span><span class="line"><span class="ln">10</span><span class="cl">  In-house benchmark              → final 確認
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">監控模型（production 階段）：
</span></span><span class="line"><span class="ln">13</span><span class="cl">  自己工作流 KPI                  → 真實品質
</span></span><span class="line"><span class="ln">14</span><span class="cl">  A/B test                       → 部署前的決策
</span></span><span class="line"><span class="ln">15</span><span class="cl">  User feedback                  → 持續迭代</span></span></code></pre></div><h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Benchmark 跟自己任務對齊的必要性</li>
<li>訓練污染 / 飽和 / single-task overfit 的陷阱</li>
<li>LLM-as-judge bias 的存在</li>
<li>In-house benchmark 是最後 final test</li>
<li><code>llama-bench</code> 是量測本地推論的標準工具</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>各 benchmark 的飽和狀態跟前沿 score</li>
<li>主流 benchmark 的選擇（HumanEval → MBPP → SWE-bench → &hellip;）</li>
<li>LLM-as-judge model 的偏好（隨 judge model 更新而變）</li>
<li>新 benchmark 出現（特別是 reasoning / long-context 領域）</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/04-applications/vision-in-coding-workflow/" data-link-title="4.15 Vision in coding workflow：本地 VLM 怎麼接寫 code" data-link-desc="VLM 在 coding 工作流的 use cases、本地 VLM 選型、跟雲端 VLM 的分工、Continue.dev / Ollama 整合現狀">4.15 Vision in coding workflow</a>、把 vision 維度加進 coding 工作流的設計取捨。讀完 4.10、模組四覆蓋了 LLM 作為系統元件的設計取捨（RAG、tool use、agent、應用層協議、workflow、resource planning、long context、embedding、benchmarking、vision）、寫 code 場景需要的應用層概念完整、之後可進入 <a href="/blog/llm/05-discrete-gpu/" data-link-title="模組五：Windows / Linux &#43; 獨立 GPU" data-link-desc="消費級 PC（Windows / Linux &#43; NVIDIA / AMD 獨立 GPU）跑本地 LLM 的硬體判讀、MoE CPU 卸載、KV cache 量化與 llama.cpp 調參">模組五 PC 獨立 GPU</a> 或 <a href="/blog/llm/06-security/" data-link-title="模組六：本地 LLM 的安全與權限" data-link-desc="個人 dev 在自己機器上跑本地 LLM 的安全議題：模型供應鏈、推論伺服器綁定、tool use 副作用、prompt injection 在 IDE、跨雲端 / 本地資料邊界">模組六 安全</a>。</p>
]]></content:encoded></item></channel></rss>