<?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>Sampling on Tarragon</title><link>https://tarrragon.github.io/blog/tags/sampling/</link><description>Recent content in Sampling on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 24 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/sampling/index.xml" rel="self" type="application/rss+xml"/><item><title>Sampling Constraint</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/sampling-constraint/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/sampling-constraint/</guid><description>&lt;p>Sampling constraint（sampling 約束）的核心概念是「&lt;strong>在模型選下一個 token 時，限制哪些 token 可以被選到&lt;/strong>」。模型 forward pass 產生每個 token 的 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logit&lt;/a>，sampling 約束在取樣前調整候選集合或機率，讓輸出符合格式、選項或安全邊界。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Sampling 約束屬於推論階段，不修改模型權重，也不等於模型真的理解規則。常見控制手段有 temperature、top-p / top-k、logit bias、grammar mask、JSON mode 與 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding&lt;/a>；其中 grammar mask 是 structured output 最關鍵的一類。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">prompt → model forward pass → logits
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">sampling constraint：調整候選 token / logit / 機率
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">sample next token → append → 下一輪&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>看到「低 temperature 讓答案更穩」「top-p 過濾長尾 token」「logit bias 禁止某個 token」「grammar mask 只允許合法 JSON token」就是 sampling 約束。例子是 enum 分類：如果合法答案只有 &lt;code>billing&lt;/code>、&lt;code>technical&lt;/code>、&lt;code>other&lt;/code>，推論伺服器可以在輸出欄位值的位置只允許這幾組 token 的路徑。&lt;/p>
&lt;p>Sampling 約束的風險是把模型逼到錯誤但合法的輸出。當 grammar 太窄、enum 缺少 &lt;code>unknown&lt;/code>、schema 沒有容納例外狀態時，模型可能輸出看似可解析但語意不可信的值；這時要加 fallback、confidence 或人工覆核路由。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Sampling 約束適合處理格式合法性與候選空間控制，不適合單獨承擔事實正確性。設計時先問三件事：合法 token 集合能否完整表示業務狀態、約束失敗時要 retry 還是回退、下游 validator 如何分辨「格式合法但語意可疑」。下一步路由是 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/structured-output/" data-link-title="Structured Output" data-link-desc="讓 LLM 輸出可被 parser 穩定消費的推論階段設計：JSON mode、schema-guided decoding、grammar 約束都屬於這一層">Structured Output&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/top-p-sampling/" data-link-title="Top-K / Top-P / Min-P Sampling" data-link-desc="從機率分佈取樣前先過濾低機率 token 的三種策略、現代 LLM 推論主流">Top-K / Top-P / Min-P Sampling&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Sampling constraint（sampling 約束）的核心概念是「<strong>在模型選下一個 token 時，限制哪些 token 可以被選到</strong>」。模型 forward pass 產生每個 token 的 <a href="/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logit</a>，sampling 約束在取樣前調整候選集合或機率，讓輸出符合格式、選項或安全邊界。</p>
<h2 id="概念位置">概念位置</h2>
<p>Sampling 約束屬於推論階段，不修改模型權重，也不等於模型真的理解規則。常見控制手段有 temperature、top-p / top-k、logit bias、grammar mask、JSON mode 與 <a href="/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding</a>；其中 grammar mask 是 structured output 最關鍵的一類。</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">prompt → model forward pass → logits
</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">sampling constraint：調整候選 token / logit / 機率
</span></span><span class="line"><span class="ln">4</span><span class="cl">  ↓
</span></span><span class="line"><span class="ln">5</span><span class="cl">sample next token → append → 下一輪</span></span></code></pre></div><h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>看到「低 temperature 讓答案更穩」「top-p 過濾長尾 token」「logit bias 禁止某個 token」「grammar mask 只允許合法 JSON token」就是 sampling 約束。例子是 enum 分類：如果合法答案只有 <code>billing</code>、<code>technical</code>、<code>other</code>，推論伺服器可以在輸出欄位值的位置只允許這幾組 token 的路徑。</p>
<p>Sampling 約束的風險是把模型逼到錯誤但合法的輸出。當 grammar 太窄、enum 缺少 <code>unknown</code>、schema 沒有容納例外狀態時，模型可能輸出看似可解析但語意不可信的值；這時要加 fallback、confidence 或人工覆核路由。</p>
<h2 id="設計責任">設計責任</h2>
<p>Sampling 約束適合處理格式合法性與候選空間控制，不適合單獨承擔事實正確性。設計時先問三件事：合法 token 集合能否完整表示業務狀態、約束失敗時要 retry 還是回退、下游 validator 如何分辨「格式合法但語意可疑」。下一步路由是 <a href="/blog/llm/knowledge-cards/structured-output/" data-link-title="Structured Output" data-link-desc="讓 LLM 輸出可被 parser 穩定消費的推論階段設計：JSON mode、schema-guided decoding、grammar 約束都屬於這一層">Structured Output</a> 與 <a href="/blog/llm/knowledge-cards/top-p-sampling/" data-link-title="Top-K / Top-P / Min-P Sampling" data-link-desc="從機率分佈取樣前先過濾低機率 token 的三種策略、現代 LLM 推論主流">Top-K / Top-P / Min-P Sampling</a>。</p>
]]></content:encoded></item><item><title>Structured Output</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/structured-output/</link><pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/structured-output/</guid><description>&lt;p>Structured output 的核心概念是「&lt;strong>讓 LLM 輸出符合可機器解析的固定形狀&lt;/strong>」。它解的是應用層 parser 能不能穩定消費模型輸出的問題：輸出要能被 JSON parser、schema validator、dispatcher、workflow engine 確定性處理，而不是靠人類讀自然語言再猜意圖。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Structured output 位在推論與應用交界，常見實作包含 JSON mode、JSON Schema、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/grammar/" data-link-title="Grammar" data-link-desc="描述合法字串形狀的形式規則，在 structured output 中用來限制 LLM 每一步可輸出的 token">grammar&lt;/a> 約束、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding&lt;/a> 與 logit mask。它跟 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">function calling&lt;/a> 的差異在責任層：function calling 是模型訓練出的工具呼叫能力，structured output 是推論時讓輸出形狀穩定的約束。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">模型能力：知道是否該呼叫工具、該填什麼參數
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">推論約束：輸出必須符合 JSON / schema / grammar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">應用消費：parser 解析、validator 檢查、dispatcher 執行&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;p>看到「固定輸出 JSON」「把結果分類成 enum」「回傳符合 schema 的物件」「讓 parser 不再處理自由文字」就是 structured output 場景。例子是客服工單分類：模型輸出 &lt;code>{&amp;quot;category&amp;quot;:&amp;quot;billing&amp;quot;,&amp;quot;priority&amp;quot;:&amp;quot;high&amp;quot;}&lt;/code>，後端可以直接依欄位路由，而不是從一段自然語言裡抽關鍵字。&lt;/p>
&lt;p>Structured output 的成功訊號是合法率、schema 對位率與下游解析失敗率。JSON 合法率只代表文字可被 parser 讀，schema 對位率才代表欄位、型別、enum、required 都符合應用契約；兩者分開看，才能分辨是語法錯、schema 錯，還是模型語意判斷錯。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>Structured output 適合「下游要自動執行」的輸出：tool 參數、分類、抽取、workflow 狀態、查詢條件。它的邊界是語意品質：grammar 可以保證格式合法，但不能保證模型填的值正確。下一步路由是：需要理解 token mask 機制讀 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">Constrained Decoding&lt;/a>；需要判斷它跟工具呼叫的分工讀 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">Function Calling&lt;/a>；需要完整應用層組合讀 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Structured output 的核心概念是「<strong>讓 LLM 輸出符合可機器解析的固定形狀</strong>」。它解的是應用層 parser 能不能穩定消費模型輸出的問題：輸出要能被 JSON parser、schema validator、dispatcher、workflow engine 確定性處理，而不是靠人類讀自然語言再猜意圖。</p>
<h2 id="概念位置">概念位置</h2>
<p>Structured output 位在推論與應用交界，常見實作包含 JSON mode、JSON Schema、<a href="/blog/llm/knowledge-cards/grammar/" data-link-title="Grammar" data-link-desc="描述合法字串形狀的形式規則，在 structured output 中用來限制 LLM 每一步可輸出的 token">grammar</a> 約束、<a href="/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding</a> 與 logit mask。它跟 <a href="/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">function calling</a> 的差異在責任層：function calling 是模型訓練出的工具呼叫能力，structured output 是推論時讓輸出形狀穩定的約束。</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">模型能力：知道是否該呼叫工具、該填什麼參數
</span></span><span class="line"><span class="ln">2</span><span class="cl">推論約束：輸出必須符合 JSON / schema / grammar
</span></span><span class="line"><span class="ln">3</span><span class="cl">應用消費：parser 解析、validator 檢查、dispatcher 執行</span></span></code></pre></div><h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<p>看到「固定輸出 JSON」「把結果分類成 enum」「回傳符合 schema 的物件」「讓 parser 不再處理自由文字」就是 structured output 場景。例子是客服工單分類：模型輸出 <code>{&quot;category&quot;:&quot;billing&quot;,&quot;priority&quot;:&quot;high&quot;}</code>，後端可以直接依欄位路由，而不是從一段自然語言裡抽關鍵字。</p>
<p>Structured output 的成功訊號是合法率、schema 對位率與下游解析失敗率。JSON 合法率只代表文字可被 parser 讀，schema 對位率才代表欄位、型別、enum、required 都符合應用契約；兩者分開看，才能分辨是語法錯、schema 錯，還是模型語意判斷錯。</p>
<h2 id="設計責任">設計責任</h2>
<p>Structured output 適合「下游要自動執行」的輸出：tool 參數、分類、抽取、workflow 狀態、查詢條件。它的邊界是語意品質：grammar 可以保證格式合法，但不能保證模型填的值正確。下一步路由是：需要理解 token mask 機制讀 <a href="/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">Constrained Decoding</a>；需要判斷它跟工具呼叫的分工讀 <a href="/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">Function Calling</a>；需要完整應用層組合讀 <a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 應用層協議</a>。</p>
]]></content:encoded></item><item><title>Beam Search</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/beam-search/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/beam-search/</guid><description>&lt;p>Beam search 的核心概念是「&lt;strong>每步同時保留 K 條最有機率的候選 sequence（beam width = K）、最終挑一條總機率最高的當輸出&lt;/strong>」。相比 greedy decoding 只保一條、beam search 能探索更多可能、避免「貪心一時、累積失誤」；但對話 / coding 場景常出現副作用、是 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/top-p-sampling/" data-link-title="Top-K / Top-P / Min-P Sampling" data-link-desc="從機率分佈取樣前先過濾低機率 token 的三種策略、現代 LLM 推論主流">top-p sampling&lt;/a> 取代它的原因。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Beam search 跟其他 decoding 策略的對比：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>策略&lt;/th>
 &lt;th>機制&lt;/th>
 &lt;th>適合場景&lt;/th>
 &lt;th>LLM 常見性&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Greedy&lt;/td>
 &lt;td>每步選機率最大的 token&lt;/td>
 &lt;td>確定性任務、debugging&lt;/td>
 &lt;td>高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Beam search (K)&lt;/strong>&lt;/td>
 &lt;td>維護 K 條候選、最後挑總機率最高的&lt;/td>
 &lt;td>機器翻譯、summarization、有「正確答案」的任務&lt;/td>
 &lt;td>中（傳統 NLP 主流）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Top-k / top-p / min-p&lt;/td>
 &lt;td>從機率分佈隨機取樣（限制候選範圍）&lt;/td>
 &lt;td>對話、寫作、coding、創意輸出&lt;/td>
 &lt;td>高（LLM 主流）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Beam search 的算法直覺：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">beam_width = 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">Step 1：從機率分佈挑前 3 個 token、得到 3 條 partial sequence
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">Step 2：每條 partial 各自展開所有可能下個 token、組合機率排序、保留前 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">Step 3：重複 Step 2、直到所有 beam 都遇到 EOS 或達到 max_length
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">Final：選總 log-probability 最高的 beam 當輸出&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Beam search 在 LLM chat / coding 場景的副作用：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>輸出偏 boilerplate&lt;/strong>：K 個 beam 容易收斂到同樣的高頻開頭（「Sure!」「That&amp;rsquo;s a great question」）、各 beam 平均化掉原本該有的多樣性。&lt;/li>
&lt;li>&lt;strong>缺乏隨機性&lt;/strong>：給同 prompt 永遠生同輸出、缺乏寫作 / 創意任務需要的變化。&lt;/li>
&lt;li>&lt;strong>計算貴&lt;/strong>：K 倍記憶體 + K 倍 forward pass。&lt;/li>
&lt;/ol>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>讀 inference framework 看到 &lt;code>num_beams: 1&lt;/code> 預設值就是用 greedy/sampling、&lt;code>num_beams: 5&lt;/code> 才會開 beam search。寫 code 場景的判讀：日常用 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/top-p-sampling/" data-link-title="Top-K / Top-P / Min-P Sampling" data-link-desc="從機率分佈取樣前先過濾低機率 token 的三種策略、現代 LLM 推論主流">top-p sampling&lt;/a> 為主、需要確定性測試用 greedy、需要「在多個候選中挑最好的」用 best-of-N（每個獨立 sample、再選 reward 最高）而非 beam search。Beam search 在現代 LLM chat 場景已經少用、但在 translation / structured output 等「有正確答案」場景仍見。&lt;/p></description><content:encoded><![CDATA[<p>Beam search 的核心概念是「<strong>每步同時保留 K 條最有機率的候選 sequence（beam width = K）、最終挑一條總機率最高的當輸出</strong>」。相比 greedy decoding 只保一條、beam search 能探索更多可能、避免「貪心一時、累積失誤」；但對話 / coding 場景常出現副作用、是 <a href="/blog/llm/knowledge-cards/top-p-sampling/" data-link-title="Top-K / Top-P / Min-P Sampling" data-link-desc="從機率分佈取樣前先過濾低機率 token 的三種策略、現代 LLM 推論主流">top-p sampling</a> 取代它的原因。</p>
<h2 id="概念位置">概念位置</h2>
<p>Beam search 跟其他 decoding 策略的對比：</p>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>機制</th>
          <th>適合場景</th>
          <th>LLM 常見性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Greedy</td>
          <td>每步選機率最大的 token</td>
          <td>確定性任務、debugging</td>
          <td>高</td>
      </tr>
      <tr>
          <td><strong>Beam search (K)</strong></td>
          <td>維護 K 條候選、最後挑總機率最高的</td>
          <td>機器翻譯、summarization、有「正確答案」的任務</td>
          <td>中（傳統 NLP 主流）</td>
      </tr>
      <tr>
          <td>Top-k / top-p / min-p</td>
          <td>從機率分佈隨機取樣（限制候選範圍）</td>
          <td>對話、寫作、coding、創意輸出</td>
          <td>高（LLM 主流）</td>
      </tr>
  </tbody>
</table>
<p>Beam search 的算法直覺：</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">beam_width = 3
</span></span><span class="line"><span class="ln">2</span><span class="cl">Step 1：從機率分佈挑前 3 個 token、得到 3 條 partial sequence
</span></span><span class="line"><span class="ln">3</span><span class="cl">Step 2：每條 partial 各自展開所有可能下個 token、組合機率排序、保留前 3
</span></span><span class="line"><span class="ln">4</span><span class="cl">Step 3：重複 Step 2、直到所有 beam 都遇到 EOS 或達到 max_length
</span></span><span class="line"><span class="ln">5</span><span class="cl">Final：選總 log-probability 最高的 beam 當輸出</span></span></code></pre></div><p>Beam search 在 LLM chat / coding 場景的副作用：</p>
<ol>
<li><strong>輸出偏 boilerplate</strong>：K 個 beam 容易收斂到同樣的高頻開頭（「Sure!」「That&rsquo;s a great question」）、各 beam 平均化掉原本該有的多樣性。</li>
<li><strong>缺乏隨機性</strong>：給同 prompt 永遠生同輸出、缺乏寫作 / 創意任務需要的變化。</li>
<li><strong>計算貴</strong>：K 倍記憶體 + K 倍 forward pass。</li>
</ol>
<h2 id="設計責任">設計責任</h2>
<p>讀 inference framework 看到 <code>num_beams: 1</code> 預設值就是用 greedy/sampling、<code>num_beams: 5</code> 才會開 beam search。寫 code 場景的判讀：日常用 <a href="/blog/llm/knowledge-cards/top-p-sampling/" data-link-title="Top-K / Top-P / Min-P Sampling" data-link-desc="從機率分佈取樣前先過濾低機率 token 的三種策略、現代 LLM 推論主流">top-p sampling</a> 為主、需要確定性測試用 greedy、需要「在多個候選中挑最好的」用 best-of-N（每個獨立 sample、再選 reward 最高）而非 beam search。Beam search 在現代 LLM chat 場景已經少用、但在 translation / structured output 等「有正確答案」場景仍見。</p>
]]></content:encoded></item><item><title>Constrained Decoding</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/</guid><description>&lt;p>Constrained decoding（受限解碼）的核心概念是「&lt;strong>推論時用 grammar 動態算出每個位置的合法 token mask、把不合法 token 的 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logit&lt;/a> 設成 -∞、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/softmax/" data-link-title="Softmax" data-link-desc="把任意實數向量正規化成「總和為 1、每個分量 ∈ [0,1]」的機率分佈">softmax&lt;/a> 後機率為 0&lt;/strong>」。是 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">structured output&lt;/a>（JSON mode / function calling 的合法性保證）背後的 sampling 機制。代表實作：XGrammar、outlines、lm-format-enforcer、guidance、SGLang。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>跟既有 sampling 概念的層次：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">模型 forward pass → logits（每個 vocab token 一個分數）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓ apply temperature
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> ↓ apply grammar mask（constrained decoding） ← 本卡聚焦
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> - 算出當下位置的合法 token 集合
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> - 不合法 token 的 logit 設 -∞
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ↓ softmax → 機率分佈
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> ↓ sampling（greedy / top-p / top-k）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> ↓ next token&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>主要 grammar 類型：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Grammar 類型&lt;/th>
 &lt;th>描述&lt;/th>
 &lt;th>用例&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>JSON Schema&lt;/td>
 &lt;td>標準 JSON schema 定義合法 JSON 結構&lt;/td>
 &lt;td>Function calling、structured output&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Regex&lt;/td>
 &lt;td>Regular expression&lt;/td>
 &lt;td>受限文字格式（如 phone number、email）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CFG（Context-Free Grammar）&lt;/td>
 &lt;td>BNF 等 grammar 描述合法語法&lt;/td>
 &lt;td>Code generation、DSL、SQL&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Choice list&lt;/td>
 &lt;td>一組固定字串選項&lt;/td>
 &lt;td>Classification、enum 輸出&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>主流實作對比：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>實作&lt;/th>
 &lt;th>機制&lt;/th>
 &lt;th>推論伺服器整合&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>XGrammar&lt;/strong>&lt;/td>
 &lt;td>Pre-compile grammar → token mask cache、極快&lt;/td>
 &lt;td>vLLM / SGLang / TensorRT-LLM 預設&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>outlines&lt;/td>
 &lt;td>Python lib、JSON schema / regex / CFG&lt;/td>
 &lt;td>用 Transformers / vLLM&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>lm-format-enforcer&lt;/td>
 &lt;td>Lazy compile、適合動態 grammar&lt;/td>
 &lt;td>Hugging Face Transformers&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>guidance&lt;/td>
 &lt;td>Microsoft 系、API 較高階&lt;/td>
 &lt;td>自家 server&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>llama.cpp grammar&lt;/td>
 &lt;td>Built-in GBNF（GGML BNF）&lt;/td>
 &lt;td>llama.cpp 內建&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>讀 sampling / structured output / function calling 進階文件看到「constrained decoding」「grammar mask」「JSON schema enforcement」就是這 framing。寫 code 場景的判讀：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>何時值得用&lt;/strong>：需要 100% 合法 JSON / 特定格式、function calling spec 嚴格、structured output 不可有解析錯誤&lt;/li>
&lt;li>&lt;strong>不該用的情況&lt;/strong>：自由 / 創意輸出（會限制模型表達）、grammar 太嚴讓模型「該說的話說不出來」（如 enum 不含「unknown」、模型強制選錯）&lt;/li>
&lt;li>&lt;strong>跟 function calling 的關係&lt;/strong>：function calling 是「模型訓練 + structured output」、constrained decoding 是 sampling 層的工程實作、可獨立組合&lt;/li>
&lt;li>&lt;strong>加速 vs 拖慢&lt;/strong>：常見誤解是 grammar 拖慢 — 實測 XGrammar 等 pre-compiled 實作反而&lt;strong>加速&lt;/strong>生成（跳過 boilerplate token 直接生關鍵 token、節省 forward pass）&lt;/li>
&lt;li>&lt;strong>跟 &lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/constrained-decoding-internals/" data-link-title="3.10 Constrained decoding 內部：grammar mask 跟性能取捨" data-link-desc="Constrained decoding 的內部運作：token mask 計算、JSON schema / regex / CFG 三種 grammar、XGrammar pre-compile 機制、性能反而加速">3.10 constrained decoding 章節&lt;/a> 的關係&lt;/strong>：本卡是定義、章節是內部機制（token mask 計算、CFG 編譯、性能取捨）&lt;/li>
&lt;/ol></description><content:encoded><![CDATA[<p>Constrained decoding（受限解碼）的核心概念是「<strong>推論時用 grammar 動態算出每個位置的合法 token mask、把不合法 token 的 <a href="/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logit</a> 設成 -∞、<a href="/blog/llm/knowledge-cards/softmax/" data-link-title="Softmax" data-link-desc="把任意實數向量正規化成「總和為 1、每個分量 ∈ [0,1]」的機率分佈">softmax</a> 後機率為 0</strong>」。是 <a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">structured output</a>（JSON mode / function calling 的合法性保證）背後的 sampling 機制。代表實作：XGrammar、outlines、lm-format-enforcer、guidance、SGLang。</p>
<h2 id="概念位置">概念位置</h2>
<p>跟既有 sampling 概念的層次：</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">模型 forward pass → logits（每個 vocab token 一個分數）
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓ apply temperature
</span></span><span class="line"><span class="ln">3</span><span class="cl">   ↓ apply grammar mask（constrained decoding）  ← 本卡聚焦
</span></span><span class="line"><span class="ln">4</span><span class="cl">       - 算出當下位置的合法 token 集合
</span></span><span class="line"><span class="ln">5</span><span class="cl">       - 不合法 token 的 logit 設 -∞
</span></span><span class="line"><span class="ln">6</span><span class="cl">   ↓ softmax → 機率分佈
</span></span><span class="line"><span class="ln">7</span><span class="cl">   ↓ sampling（greedy / top-p / top-k）
</span></span><span class="line"><span class="ln">8</span><span class="cl">   ↓ next token</span></span></code></pre></div><p>主要 grammar 類型：</p>
<table>
  <thead>
      <tr>
          <th>Grammar 類型</th>
          <th>描述</th>
          <th>用例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>JSON Schema</td>
          <td>標準 JSON schema 定義合法 JSON 結構</td>
          <td>Function calling、structured output</td>
      </tr>
      <tr>
          <td>Regex</td>
          <td>Regular expression</td>
          <td>受限文字格式（如 phone number、email）</td>
      </tr>
      <tr>
          <td>CFG（Context-Free Grammar）</td>
          <td>BNF 等 grammar 描述合法語法</td>
          <td>Code generation、DSL、SQL</td>
      </tr>
      <tr>
          <td>Choice list</td>
          <td>一組固定字串選項</td>
          <td>Classification、enum 輸出</td>
      </tr>
  </tbody>
</table>
<p>主流實作對比：</p>
<table>
  <thead>
      <tr>
          <th>實作</th>
          <th>機制</th>
          <th>推論伺服器整合</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>XGrammar</strong></td>
          <td>Pre-compile grammar → token mask cache、極快</td>
          <td>vLLM / SGLang / TensorRT-LLM 預設</td>
      </tr>
      <tr>
          <td>outlines</td>
          <td>Python lib、JSON schema / regex / CFG</td>
          <td>用 Transformers / vLLM</td>
      </tr>
      <tr>
          <td>lm-format-enforcer</td>
          <td>Lazy compile、適合動態 grammar</td>
          <td>Hugging Face Transformers</td>
      </tr>
      <tr>
          <td>guidance</td>
          <td>Microsoft 系、API 較高階</td>
          <td>自家 server</td>
      </tr>
      <tr>
          <td>llama.cpp grammar</td>
          <td>Built-in GBNF（GGML BNF）</td>
          <td>llama.cpp 內建</td>
      </tr>
  </tbody>
</table>
<h2 id="設計責任">設計責任</h2>
<p>讀 sampling / structured output / function calling 進階文件看到「constrained decoding」「grammar mask」「JSON schema enforcement」就是這 framing。寫 code 場景的判讀：</p>
<ol>
<li><strong>何時值得用</strong>：需要 100% 合法 JSON / 特定格式、function calling spec 嚴格、structured output 不可有解析錯誤</li>
<li><strong>不該用的情況</strong>：自由 / 創意輸出（會限制模型表達）、grammar 太嚴讓模型「該說的話說不出來」（如 enum 不含「unknown」、模型強制選錯）</li>
<li><strong>跟 function calling 的關係</strong>：function calling 是「模型訓練 + structured output」、constrained decoding 是 sampling 層的工程實作、可獨立組合</li>
<li><strong>加速 vs 拖慢</strong>：常見誤解是 grammar 拖慢 — 實測 XGrammar 等 pre-compiled 實作反而<strong>加速</strong>生成（跳過 boilerplate token 直接生關鍵 token、節省 forward pass）</li>
<li><strong>跟 <a href="/blog/llm/03-theoretical-foundations/constrained-decoding-internals/" data-link-title="3.10 Constrained decoding 內部：grammar mask 跟性能取捨" data-link-desc="Constrained decoding 的內部運作：token mask 計算、JSON schema / regex / CFG 三種 grammar、XGrammar pre-compile 機制、性能反而加速">3.10 constrained decoding 章節</a> 的關係</strong>：本卡是定義、章節是內部機制（token mask 計算、CFG 編譯、性能取捨）</li>
</ol>
]]></content:encoded></item><item><title>Logit</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/logit/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/logit/</guid><description>&lt;p>Logit 的核心概念是「&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/softmax/" data-link-title="Softmax" data-link-desc="把任意實數向量正規化成「總和為 1、每個分量 ∈ [0,1]」的機率分佈">softmax&lt;/a> 之前的原始分數」。LLM 每次 forward pass 的最後一步、會輸出長度為 vocab size 的實數向量（例如 vocab size = 128K、輸出就是 128K 個浮點數）、這個向量就是 logits。Logit 可正可負、無上下界、要經過 softmax 才變成機率分佈。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Logit 在 LLM 輸出 pipeline 的位置：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">最後一層 Transformer 輸出 hidden state
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓ output projection（linear layer）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">logits（shape: vocab_size、實數、可正可負）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓ logit warping / masking（可選、用於控制輸出）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> ↓ /temperature
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ↓ softmax
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">probability distribution
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> ↓ sampling（greedy / top-k / top-p）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">next token&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>操作 logit 的常見技巧：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>技巧&lt;/th>
 &lt;th>做法&lt;/th>
 &lt;th>用途&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Temperature&lt;/td>
 &lt;td>logit / T&lt;/td>
 &lt;td>控制輸出隨機度、T 越大越平&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Logit bias&lt;/td>
 &lt;td>對特定 token 的 logit 加 / 減 offset&lt;/td>
 &lt;td>強制 / 抑制特定 token（如禁用特定詞）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Grammar masking&lt;/td>
 &lt;td>把不合法 token 的 logit 設成 -∞&lt;/td>
 &lt;td>Structured output、確保輸出符合 grammar&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Repetition penalty&lt;/td>
 &lt;td>對最近出現過的 token logit 扣分&lt;/td>
 &lt;td>避免重複、改善生成多樣性&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>理解 logit 後可以判讀 sampling 階段的控制粒度：所有「不重訓模型、影響輸出」的技巧（temperature、structured output、constrained generation、logit bias）本質上都是「在 softmax 前後動 logit」、不是動模型權重。這也是為什麼同一個模型用不同 sampling 設定能產生差很多的輸出。&lt;/p></description><content:encoded><![CDATA[<p>Logit 的核心概念是「<a href="/blog/llm/knowledge-cards/softmax/" data-link-title="Softmax" data-link-desc="把任意實數向量正規化成「總和為 1、每個分量 ∈ [0,1]」的機率分佈">softmax</a> 之前的原始分數」。LLM 每次 forward pass 的最後一步、會輸出長度為 vocab size 的實數向量（例如 vocab size = 128K、輸出就是 128K 個浮點數）、這個向量就是 logits。Logit 可正可負、無上下界、要經過 softmax 才變成機率分佈。</p>
<h2 id="概念位置">概念位置</h2>
<p>Logit 在 LLM 輸出 pipeline 的位置：</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">最後一層 Transformer 輸出 hidden state
</span></span><span class="line"><span class="ln">2</span><span class="cl">   ↓ output projection（linear layer）
</span></span><span class="line"><span class="ln">3</span><span class="cl">logits（shape: vocab_size、實數、可正可負）
</span></span><span class="line"><span class="ln">4</span><span class="cl">   ↓ logit warping / masking（可選、用於控制輸出）
</span></span><span class="line"><span class="ln">5</span><span class="cl">   ↓ /temperature
</span></span><span class="line"><span class="ln">6</span><span class="cl">   ↓ softmax
</span></span><span class="line"><span class="ln">7</span><span class="cl">probability distribution
</span></span><span class="line"><span class="ln">8</span><span class="cl">   ↓ sampling（greedy / top-k / top-p）
</span></span><span class="line"><span class="ln">9</span><span class="cl">next token</span></span></code></pre></div><p>操作 logit 的常見技巧：</p>
<table>
  <thead>
      <tr>
          <th>技巧</th>
          <th>做法</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Temperature</td>
          <td>logit / T</td>
          <td>控制輸出隨機度、T 越大越平</td>
      </tr>
      <tr>
          <td>Logit bias</td>
          <td>對特定 token 的 logit 加 / 減 offset</td>
          <td>強制 / 抑制特定 token（如禁用特定詞）</td>
      </tr>
      <tr>
          <td>Grammar masking</td>
          <td>把不合法 token 的 logit 設成 -∞</td>
          <td>Structured output、確保輸出符合 grammar</td>
      </tr>
      <tr>
          <td>Repetition penalty</td>
          <td>對最近出現過的 token logit 扣分</td>
          <td>避免重複、改善生成多樣性</td>
      </tr>
  </tbody>
</table>
<h2 id="設計責任">設計責任</h2>
<p>理解 logit 後可以判讀 sampling 階段的控制粒度：所有「不重訓模型、影響輸出」的技巧（temperature、structured output、constrained generation、logit bias）本質上都是「在 softmax 前後動 logit」、不是動模型權重。這也是為什麼同一個模型用不同 sampling 設定能產生差很多的輸出。</p>
]]></content:encoded></item><item><title>Softmax</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/softmax/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/softmax/</guid><description>&lt;p>Softmax 的核心概念是「把一串實數轉成機率分佈」。公式是 &lt;code>softmax(x_i) = exp(x_i) / sum(exp(x_j))&lt;/code>、輸出總和為 1、每個值 ∈ [0, 1]。它是 LLM 兩個關鍵環節的常駐元件：&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">attention&lt;/a> 的權重計算、跟 sampling 階段把 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logit&lt;/a> 轉成「下個 token 的機率分佈」。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>LLM 中 softmax 出現的兩個位置：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">位置 1：Attention 內部
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> Q · K^T → 一堆 score
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> softmax(scores) → attention weight（總和 1）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> weight · V → output
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">位置 2：每次 token 生成的最後一步
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> 最後一層 hidden → logit（每個 vocab token 一個實數分數）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> softmax(logits / temperature) → 機率分佈
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> 從這個分佈 sample 出下一個 token&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>兩個位置的關鍵差異：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>位置&lt;/th>
 &lt;th>softmax 的作用&lt;/th>
 &lt;th>影響&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Attention&lt;/td>
 &lt;td>把 attention score 正規化成「該關注多少」&lt;/td>
 &lt;td>影響模型怎麼整合 context 資訊&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Sampling 端&lt;/td>
 &lt;td>把 logit 變機率、配合 temperature 調分佈陡度&lt;/td>
 &lt;td>影響輸出的多樣性 / 確定性&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Temperature 在 sampling 端跟 softmax 結合：&lt;code>softmax(logits / T)&lt;/code>、T 越小分佈越尖（接近 greedy）、T 越大分佈越平（接近隨機）。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>理解 softmax 後可以判讀幾件事：temperature 為什麼影響輸出多樣性（改的是 softmax 前的縮放）、為什麼 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logit&lt;/a> bias / logit warping 等技巧能控制輸出（直接動 softmax 的輸入）、為什麼 &lt;a href="https://tarrragon.github.io/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">structured output&lt;/a> 的 grammar-constrained sampling 是「把不合法 token 的機率歸零」（在 softmax 後或前做 masking）。&lt;/p></description><content:encoded><![CDATA[<p>Softmax 的核心概念是「把一串實數轉成機率分佈」。公式是 <code>softmax(x_i) = exp(x_i) / sum(exp(x_j))</code>、輸出總和為 1、每個值 ∈ [0, 1]。它是 LLM 兩個關鍵環節的常駐元件：<a href="/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">attention</a> 的權重計算、跟 sampling 階段把 <a href="/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logit</a> 轉成「下個 token 的機率分佈」。</p>
<h2 id="概念位置">概念位置</h2>
<p>LLM 中 softmax 出現的兩個位置：</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">位置 1：Attention 內部
</span></span><span class="line"><span class="ln">2</span><span class="cl">  Q · K^T → 一堆 score
</span></span><span class="line"><span class="ln">3</span><span class="cl">  softmax(scores) → attention weight（總和 1）
</span></span><span class="line"><span class="ln">4</span><span class="cl">  weight · V → 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：每次 token 生成的最後一步
</span></span><span class="line"><span class="ln">7</span><span class="cl">  最後一層 hidden → logit（每個 vocab token 一個實數分數）
</span></span><span class="line"><span class="ln">8</span><span class="cl">  softmax(logits / temperature) → 機率分佈
</span></span><span class="line"><span class="ln">9</span><span class="cl">  從這個分佈 sample 出下一個 token</span></span></code></pre></div><p>兩個位置的關鍵差異：</p>
<table>
  <thead>
      <tr>
          <th>位置</th>
          <th>softmax 的作用</th>
          <th>影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Attention</td>
          <td>把 attention score 正規化成「該關注多少」</td>
          <td>影響模型怎麼整合 context 資訊</td>
      </tr>
      <tr>
          <td>Sampling 端</td>
          <td>把 logit 變機率、配合 temperature 調分佈陡度</td>
          <td>影響輸出的多樣性 / 確定性</td>
      </tr>
  </tbody>
</table>
<p>Temperature 在 sampling 端跟 softmax 結合：<code>softmax(logits / T)</code>、T 越小分佈越尖（接近 greedy）、T 越大分佈越平（接近隨機）。</p>
<h2 id="設計責任">設計責任</h2>
<p>理解 softmax 後可以判讀幾件事：temperature 為什麼影響輸出多樣性（改的是 softmax 前的縮放）、為什麼 <a href="/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logit</a> bias / logit warping 等技巧能控制輸出（直接動 softmax 的輸入）、為什麼 <a href="/blog/llm/04-applications/tool-use-principles/" data-link-title="4.3 Tool use 原理：LLM 跟外部世界互動" data-link-desc="Structured output 是 LLM 跨入工程系統的橋、function calling 取捨、為什麼本地小模型 tool use 表現崩潰">structured output</a> 的 grammar-constrained sampling 是「把不合法 token 的機率歸零」（在 softmax 後或前做 masking）。</p>
]]></content:encoded></item><item><title>Top-K / Top-P / Min-P Sampling</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/top-p-sampling/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/top-p-sampling/</guid><description>&lt;p>Top-K、Top-P（nucleus sampling）、Min-P 的核心概念是「&lt;strong>從 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/softmax/" data-link-title="Softmax" data-link-desc="把任意實數向量正規化成「總和為 1、每個分量 ∈ [0,1]」的機率分佈">softmax&lt;/a> 出來的機率分佈中、先過濾掉低機率 token、再從剩餘候選隨機取樣&lt;/strong>」。三者是 LLM 對話 / 寫 code 場景的主流 sampling 策略、跟 greedy 對比保留隨機多樣性、跟 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/beam-search/" data-link-title="Beam Search" data-link-desc="同時保留 K 條候選 sequence 的 decoding 策略、機器翻譯主流、chat / coding 場景慎用">beam search&lt;/a> 對比計算成本低。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>三種策略的篩選方式：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>策略&lt;/th>
 &lt;th>機制&lt;/th>
 &lt;th>直覺&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>Top-K&lt;/strong>&lt;/td>
 &lt;td>只保留機率前 K 個 token、其餘設 0&lt;/td>
 &lt;td>固定候選數量、簡單&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Top-P&lt;/strong>&lt;/td>
 &lt;td>把 token 依機率排序、保留「累積機率達到 P」的最小集合&lt;/td>
 &lt;td>動態候選數量、適應分佈尖銳度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Min-P&lt;/strong>&lt;/td>
 &lt;td>只保留機率 ≥ (P × max_probability) 的 token&lt;/td>
 &lt;td>相對閾值、避免低品質 token&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>範例（vocab 前 10 個 token 的機率）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">token: A B C D E F G H I J
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">prob: 0.45 0.30 0.12 0.05 0.03 0.02 0.01 0.01 0.005 0.005
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">Top-K=3：保留 A、B、C（前 3 個）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">Top-P=0.9：累積機率達 0.9、保留 A、B、C、D（0.45+0.30+0.12+0.05 = 0.92）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">Min-P=0.1：max=0.45、閾值=0.045、保留 A、B、C、D（≥ 0.045）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>三者實務上常組合使用（如 &lt;code>top_k=40, top_p=0.9, temperature=0.7&lt;/code>）、各自處理不同形狀的分佈。&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>分佈非常尖（模型很確定）&lt;/td>
 &lt;td>Top-P / Min-P 動態縮小、Top-K 可能太大&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>分佈平（模型不確定）&lt;/td>
 &lt;td>Top-K 限制最大候選、避免取到極低品質 token&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>寫 code / 嚴謹任務&lt;/td>
 &lt;td>低 temperature (0.2 ~ 0.5) + 較緊的 Top-P (0.8 ~ 0.9)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>創意 / 多樣寫作&lt;/td>
 &lt;td>高 temperature (0.7 ~ 1.0) + 寬鬆的 Top-P (0.95+)&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>讀 inference config / Continue.dev 設定看到 &lt;code>top_k&lt;/code>、&lt;code>top_p&lt;/code>、&lt;code>min_p&lt;/code>、&lt;code>temperature&lt;/code> 就是這組參數。寫 code 場景的判讀：嚴謹任務（code generation、structured output）用低 temperature + 緊 Top-P 取「最可能對的少數 token」；創意 / 對話用高 temperature + 寬 Top-P 取多樣性。Min-P 是 2023 後流行的新策略、實務上比 Top-P 更穩、避免「分佈很尖時 Top-P 仍納入長尾低品質 token」的問題。&lt;/p></description><content:encoded><![CDATA[<p>Top-K、Top-P（nucleus sampling）、Min-P 的核心概念是「<strong>從 <a href="/blog/llm/knowledge-cards/softmax/" data-link-title="Softmax" data-link-desc="把任意實數向量正規化成「總和為 1、每個分量 ∈ [0,1]」的機率分佈">softmax</a> 出來的機率分佈中、先過濾掉低機率 token、再從剩餘候選隨機取樣</strong>」。三者是 LLM 對話 / 寫 code 場景的主流 sampling 策略、跟 greedy 對比保留隨機多樣性、跟 <a href="/blog/llm/knowledge-cards/beam-search/" data-link-title="Beam Search" data-link-desc="同時保留 K 條候選 sequence 的 decoding 策略、機器翻譯主流、chat / coding 場景慎用">beam search</a> 對比計算成本低。</p>
<h2 id="概念位置">概念位置</h2>
<p>三種策略的篩選方式：</p>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>機制</th>
          <th>直覺</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Top-K</strong></td>
          <td>只保留機率前 K 個 token、其餘設 0</td>
          <td>固定候選數量、簡單</td>
      </tr>
      <tr>
          <td><strong>Top-P</strong></td>
          <td>把 token 依機率排序、保留「累積機率達到 P」的最小集合</td>
          <td>動態候選數量、適應分佈尖銳度</td>
      </tr>
      <tr>
          <td><strong>Min-P</strong></td>
          <td>只保留機率 ≥ (P × max_probability) 的 token</td>
          <td>相對閾值、避免低品質 token</td>
      </tr>
  </tbody>
</table>
<p>範例（vocab 前 10 個 token 的機率）：</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">token:     A     B     C     D     E     F     G     H     I     J
</span></span><span class="line"><span class="ln">2</span><span class="cl">prob:    0.45  0.30  0.12  0.05  0.03  0.02  0.01  0.01  0.005 0.005
</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">Top-K=3：保留 A、B、C（前 3 個）
</span></span><span class="line"><span class="ln">5</span><span class="cl">Top-P=0.9：累積機率達 0.9、保留 A、B、C、D（0.45+0.30+0.12+0.05 = 0.92）
</span></span><span class="line"><span class="ln">6</span><span class="cl">Min-P=0.1：max=0.45、閾值=0.045、保留 A、B、C、D（≥ 0.045）</span></span></code></pre></div><p>三者實務上常組合使用（如 <code>top_k=40, top_p=0.9, temperature=0.7</code>）、各自處理不同形狀的分佈。</p>
<table>
  <thead>
      <tr>
          <th>參數情境</th>
          <th>適合策略</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>分佈非常尖（模型很確定）</td>
          <td>Top-P / Min-P 動態縮小、Top-K 可能太大</td>
      </tr>
      <tr>
          <td>分佈平（模型不確定）</td>
          <td>Top-K 限制最大候選、避免取到極低品質 token</td>
      </tr>
      <tr>
          <td>寫 code / 嚴謹任務</td>
          <td>低 temperature (0.2 ~ 0.5) + 較緊的 Top-P (0.8 ~ 0.9)</td>
      </tr>
      <tr>
          <td>創意 / 多樣寫作</td>
          <td>高 temperature (0.7 ~ 1.0) + 寬鬆的 Top-P (0.95+)</td>
      </tr>
  </tbody>
</table>
<h2 id="設計責任">設計責任</h2>
<p>讀 inference config / Continue.dev 設定看到 <code>top_k</code>、<code>top_p</code>、<code>min_p</code>、<code>temperature</code> 就是這組參數。寫 code 場景的判讀：嚴謹任務（code generation、structured output）用低 temperature + 緊 Top-P 取「最可能對的少數 token」；創意 / 對話用高 temperature + 寬 Top-P 取多樣性。Min-P 是 2023 後流行的新策略、實務上比 Top-P 更穩、避免「分佈很尖時 Top-P 仍納入長尾低品質 token」的問題。</p>
]]></content:encoded></item><item><title>降級策略</title><link>https://tarrragon.github.io/blog/devops/07-burst-traffic/degradation-strategy/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/07-burst-traffic/degradation-strategy/</guid><description>&lt;p>降級策略的核心決策是「超載時犧牲什麼保住什麼」。犧牲的是精度、延遲或非核心功能；保住的是核心功能的可用性。沒有降級策略的系統在超載時整體崩潰 — 所有功能同時不可用。&lt;/p>
&lt;h2 id="動態取樣">動態取樣&lt;/h2>
&lt;p>流量超過閾值時自動降低取樣率。平時 100% 收集、超載時降到 10% — 仍有資料可分析，只是精度下降。&lt;/p>
&lt;h3 id="觸發條件">觸發條件&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>訊號&lt;/th>
 &lt;th>動作&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Collector 回 429 次數 &amp;gt; N / 分鐘&lt;/td>
 &lt;td>SDK 降低取樣率 50%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>連續 429 超過 M 分鐘&lt;/td>
 &lt;td>SDK 再降到 10%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>429 消失且 buffer 清空&lt;/td>
 &lt;td>SDK 恢復 100%&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="取樣的公平性">取樣的公平性&lt;/h3>
&lt;p>動態取樣不應該只丟新事件保留舊事件（FIFO 丟棄）— 這會讓取樣偏向「burst 初期的事件」。更好的策略是隨機取樣（每個事件有 sampling_rate 的機率被保留），讓取樣後的資料仍然能代表整體分佈。&lt;/p>
&lt;p>取樣後的事件帶 &lt;code>_sampling_rate&lt;/code> 欄位，分析時用 &lt;code>1 / sampling_rate&lt;/code> 做加權還原。&lt;/p>
&lt;h2 id="事件優先級">事件優先級&lt;/h2>
&lt;p>不同事件類型的 debug 價值不同。超載時先丟價值低的，保留價值高的。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>優先級&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;/td>
 &lt;td>error&lt;/td>
 &lt;td>debug 核心 — 丟了就查不到問題&lt;/td>
 &lt;td>全部保留&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>高&lt;/td>
 &lt;td>lifecycle&lt;/td>
 &lt;td>session 邊界 — 影響 session 分析&lt;/td>
 &lt;td>全部保留&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>中&lt;/td>
 &lt;td>metric&lt;/td>
 &lt;td>趨勢可從取樣還原&lt;/td>
 &lt;td>降低取樣率&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>低&lt;/td>
 &lt;td>event&lt;/td>
 &lt;td>行為分析可接受精度損失&lt;/td>
 &lt;td>降低取樣率或暫停&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>優先級的判斷原則：「這個事件丟了、要花多少時間從其他來源補回相同資訊」。Error 的 stack trace 丟了幾乎不可能從其他來源補回；event 的 click 計數可以從後續資料的趨勢推測。&lt;/p>
&lt;h2 id="功能降級">功能降級&lt;/h2>
&lt;p>非核心功能暫時關閉或降低更新頻率，把資源留給核心功能。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>功能&lt;/th>
 &lt;th>正常模式&lt;/th>
 &lt;th>降級模式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Dashboard 即時刷新&lt;/td>
 &lt;td>每秒查詢&lt;/td>
 &lt;td>每 30 秒查詢&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rule engine 評估&lt;/td>
 &lt;td>每筆事件即時評估&lt;/td>
 &lt;td>累積 10 筆批次評估&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>JSONL 匯出&lt;/td>
 &lt;td>隨時可匯出&lt;/td>
 &lt;td>暫停（避免 I/O 競爭）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>降採樣 job&lt;/td>
 &lt;td>每小時跑&lt;/td>
 &lt;td>延後到流量恢復後補跑&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>降級的觸發和恢復應該自動化 — 用 collector 的內部 metric（goroutine pool 使用率、寫入延遲）作為訊號。&lt;/p>
&lt;h2 id="聚合前移">聚合前移&lt;/h2>
&lt;p>讓 SDK 端做預聚合，減少送到 collector 的事件數量。&lt;/p>
&lt;p>平時：每次 click 送一筆 &lt;code>button.clicked&lt;/code> 事件 → 100 次 click = 100 筆事件。
聚合前移：SDK 累積 10 秒內的 click → 送一筆 &lt;code>button.clicked&lt;/code> 帶 &lt;code>count: 17&lt;/code> → 100 次 click = ~10 筆事件。&lt;/p>
&lt;p>聚合前移犧牲的是事件粒度（失去每次 click 的精確時間戳），換取的是 10x 的事件量減少。適用於高頻但單筆資訊量低的事件（click、scroll、mousemove）。&lt;/p>
&lt;p>聚合前移的觸發也可以是動態的 — collector 回 429 時 SDK 自動啟用聚合前移，流量恢復後關閉。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>突發流量的分類 → &lt;a href="https://tarrragon.github.io/blog/devops/07-burst-traffic/burst-classification/" data-link-title="突發流量的分類" data-link-desc="可預期 vs 不可預期的突發流量 — 不同來源、持續時間和倍率決定不同的應對策略">突發流量的分類&lt;/a>&lt;/li>
&lt;li>Queue 做更大規模的緩衝 → &lt;a href="https://tarrragon.github.io/blog/devops/07-burst-traffic/queue-buffering/" data-link-title="Queue 緩衝" data-link-desc="在 ingestion 和 processing 之間加 message queue 做 burst 緩衝 — Kafka / NATS / Redis Streams 的選型和引入條件">Queue 緩衝&lt;/a>&lt;/li>
&lt;li>不同規模的應對方案 → &lt;a href="https://tarrragon.github.io/blog/devops/07-burst-traffic/scale-tier-response/" data-link-title="規模分級應對表" data-link-desc="自用級 → 中型 → 大型 → 商業網站級的四級應對方案 — 每級的觸發條件、架構組成和成本">規模分級應對表&lt;/a>&lt;/li>
&lt;li>背壓和 rate limit 的基礎 → &lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/" data-link-title="模組三：流量管控" data-link-desc="收到的流量超過處理能力時怎麼辦 — 背壓、rate limit、熔斷、bulkhead 四種防護機制">模組三 流量管控&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>降級策略的核心決策是「超載時犧牲什麼保住什麼」。犧牲的是精度、延遲或非核心功能；保住的是核心功能的可用性。沒有降級策略的系統在超載時整體崩潰 — 所有功能同時不可用。</p>
<h2 id="動態取樣">動態取樣</h2>
<p>流量超過閾值時自動降低取樣率。平時 100% 收集、超載時降到 10% — 仍有資料可分析，只是精度下降。</p>
<h3 id="觸發條件">觸發條件</h3>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Collector 回 429 次數 &gt; N / 分鐘</td>
          <td>SDK 降低取樣率 50%</td>
      </tr>
      <tr>
          <td>連續 429 超過 M 分鐘</td>
          <td>SDK 再降到 10%</td>
      </tr>
      <tr>
          <td>429 消失且 buffer 清空</td>
          <td>SDK 恢復 100%</td>
      </tr>
  </tbody>
</table>
<h3 id="取樣的公平性">取樣的公平性</h3>
<p>動態取樣不應該只丟新事件保留舊事件（FIFO 丟棄）— 這會讓取樣偏向「burst 初期的事件」。更好的策略是隨機取樣（每個事件有 sampling_rate 的機率被保留），讓取樣後的資料仍然能代表整體分佈。</p>
<p>取樣後的事件帶 <code>_sampling_rate</code> 欄位，分析時用 <code>1 / sampling_rate</code> 做加權還原。</p>
<h2 id="事件優先級">事件優先級</h2>
<p>不同事件類型的 debug 價值不同。超載時先丟價值低的，保留價值高的。</p>
<table>
  <thead>
      <tr>
          <th>優先級</th>
          <th>事件類型</th>
          <th>理由</th>
          <th>超載時處理</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>最高</td>
          <td>error</td>
          <td>debug 核心 — 丟了就查不到問題</td>
          <td>全部保留</td>
      </tr>
      <tr>
          <td>高</td>
          <td>lifecycle</td>
          <td>session 邊界 — 影響 session 分析</td>
          <td>全部保留</td>
      </tr>
      <tr>
          <td>中</td>
          <td>metric</td>
          <td>趨勢可從取樣還原</td>
          <td>降低取樣率</td>
      </tr>
      <tr>
          <td>低</td>
          <td>event</td>
          <td>行為分析可接受精度損失</td>
          <td>降低取樣率或暫停</td>
      </tr>
  </tbody>
</table>
<p>優先級的判斷原則：「這個事件丟了、要花多少時間從其他來源補回相同資訊」。Error 的 stack trace 丟了幾乎不可能從其他來源補回；event 的 click 計數可以從後續資料的趨勢推測。</p>
<h2 id="功能降級">功能降級</h2>
<p>非核心功能暫時關閉或降低更新頻率，把資源留給核心功能。</p>
<table>
  <thead>
      <tr>
          <th>功能</th>
          <th>正常模式</th>
          <th>降級模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Dashboard 即時刷新</td>
          <td>每秒查詢</td>
          <td>每 30 秒查詢</td>
      </tr>
      <tr>
          <td>Rule engine 評估</td>
          <td>每筆事件即時評估</td>
          <td>累積 10 筆批次評估</td>
      </tr>
      <tr>
          <td>JSONL 匯出</td>
          <td>隨時可匯出</td>
          <td>暫停（避免 I/O 競爭）</td>
      </tr>
      <tr>
          <td>降採樣 job</td>
          <td>每小時跑</td>
          <td>延後到流量恢復後補跑</td>
      </tr>
  </tbody>
</table>
<p>降級的觸發和恢復應該自動化 — 用 collector 的內部 metric（goroutine pool 使用率、寫入延遲）作為訊號。</p>
<h2 id="聚合前移">聚合前移</h2>
<p>讓 SDK 端做預聚合，減少送到 collector 的事件數量。</p>
<p>平時：每次 click 送一筆 <code>button.clicked</code> 事件 → 100 次 click = 100 筆事件。
聚合前移：SDK 累積 10 秒內的 click → 送一筆 <code>button.clicked</code> 帶 <code>count: 17</code> → 100 次 click = ~10 筆事件。</p>
<p>聚合前移犧牲的是事件粒度（失去每次 click 的精確時間戳），換取的是 10x 的事件量減少。適用於高頻但單筆資訊量低的事件（click、scroll、mousemove）。</p>
<p>聚合前移的觸發也可以是動態的 — collector 回 429 時 SDK 自動啟用聚合前移，流量恢復後關閉。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>突發流量的分類 → <a href="/blog/devops/07-burst-traffic/burst-classification/" data-link-title="突發流量的分類" data-link-desc="可預期 vs 不可預期的突發流量 — 不同來源、持續時間和倍率決定不同的應對策略">突發流量的分類</a></li>
<li>Queue 做更大規模的緩衝 → <a href="/blog/devops/07-burst-traffic/queue-buffering/" data-link-title="Queue 緩衝" data-link-desc="在 ingestion 和 processing 之間加 message queue 做 burst 緩衝 — Kafka / NATS / Redis Streams 的選型和引入條件">Queue 緩衝</a></li>
<li>不同規模的應對方案 → <a href="/blog/devops/07-burst-traffic/scale-tier-response/" data-link-title="規模分級應對表" data-link-desc="自用級 → 中型 → 大型 → 商業網站級的四級應對方案 — 每級的觸發條件、架構組成和成本">規模分級應對表</a></li>
<li>背壓和 rate limit 的基礎 → <a href="/blog/devops/03-traffic-management/" data-link-title="模組三：流量管控" data-link-desc="收到的流量超過處理能力時怎麼辦 — 背壓、rate limit、熔斷、bulkhead 四種防護機制">模組三 流量管控</a></li>
</ul>
]]></content:encoded></item><item><title>3.5 Sampling 與 Decoding 策略</title><link>https://tarrragon.github.io/blog/llm/03-theoretical-foundations/sampling-and-decoding/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/03-theoretical-foundations/sampling-and-decoding/</guid><description>&lt;p>LLM 的輸出本質是「下一個 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/token/" data-link-title="Token" data-link-desc="LLM 處理文字時的最小單位：介於字元與單字之間">token&lt;/a> 的機率分佈」、不是直接的 token。從機率分佈挑下一個 token 的具體方法、就是 sampling / decoding 策略。同一個模型、同一個 prompt、不同 sampling 策略會給出顯著不同的輸出。&lt;/p>
&lt;p>本章拆開主流 sampling 策略的機制、各自適合的場景、以及 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/sampling-constraint/" data-link-title="Sampling Constraint" data-link-desc="推論時限制下一個 token 候選集合的控制手段，用來把模型生成導向合法格式或特定選項">&lt;code>temperature&lt;/code>&lt;/a>、&lt;code>top_p&lt;/code> 這些常見參數在這條鏈上的位置。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>解釋 &lt;code>temperature=0&lt;/code> 跟 &lt;code>temperature=0.8&lt;/code> 的具體差別。&lt;/li>
&lt;li>區分 top-k、top-p、min-p 三者的機制。&lt;/li>
&lt;li>看到 &lt;code>repetition_penalty=1.1&lt;/code> 設定時、知道它解什麼問題。&lt;/li>
&lt;li>解釋為什麼確定性測試要設 &lt;code>temperature=0&lt;/code> + &lt;code>seed&lt;/code>。&lt;/li>
&lt;/ol>
&lt;h2 id="從-logits-到下個-token">從 logits 到下個 token&lt;/h2>
&lt;p>複習一下 LLM 輸出端的鏈：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">final hidden states → output projection → logits → temperature → softmax → 機率分佈
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">→ sampling 策略 → 下個 token&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>各環節在 sampling 中的位置：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>環節&lt;/th>
 &lt;th>對 sampling 的影響&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logits&lt;/a>&lt;/td>
 &lt;td>模型給每個 token 的原始分數、還沒正規化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/sampling-constraint/" data-link-title="Sampling Constraint" data-link-desc="推論時限制下一個 token 候選集合的控制手段，用來把模型生成導向合法格式或特定選項">temperature&lt;/a>&lt;/td>
 &lt;td>在 softmax 前除以 T、調整分佈尖銳度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/softmax/" data-link-title="Softmax" data-link-desc="把任意實數向量正規化成「總和為 1、每個分量 ∈ [0,1]」的機率分佈">softmax&lt;/a>&lt;/td>
 &lt;td>把 logits 轉成機率分佈&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>top-k / top-p / min-p&lt;/td>
 &lt;td>過濾低機率 token、把候選集縮小&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>重新正規化&lt;/td>
 &lt;td>把過濾後的剩餘 token 重新正規化成機率分佈&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>取樣&lt;/td>
 &lt;td>從正規化分佈中隨機選一個 token&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>repetition penalty&lt;/td>
 &lt;td>對已出現的 token 降權、避免重複&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>實際參數順序視推論伺服器實作而異、但概念上是這條鏈。&lt;/p>
&lt;h2 id="greedy-decoding永遠選機率最大">Greedy Decoding：永遠選機率最大&lt;/h2>
&lt;p>Greedy decoding 的核心定義是「每步選 softmax 後機率最大的 token」：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">next_token = argmax(probabilities)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>特性：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>確定性&lt;/strong>：同 prompt 永遠生同樣輸出。&lt;/li>
&lt;li>&lt;strong>快&lt;/strong>：不用 sampling、不用算 cumulative probabilities。&lt;/li>
&lt;li>&lt;strong>缺點&lt;/strong>：傾向選最常見 pattern、輸出單調；常陷入 repetition loop。&lt;/li>
&lt;/ul>
&lt;p>實務用途：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Reproducible 評估&lt;/strong>：跑 benchmark、自動測試。&lt;/li>
&lt;li>&lt;strong>單元測試&lt;/strong>：確保模型輸出可預測。&lt;/li>
&lt;li>&lt;strong>某些 reasoning chain&lt;/strong>：選最有信心的下一步。&lt;/li>
&lt;/ul>
&lt;p>效果上等同 &lt;code>temperature=0&lt;/code>、許多推論伺服器把兩者當同義詞。&lt;/p>
&lt;h2 id="beam-search保留-top-k-條候選序列">Beam Search：保留 top-K 條候選序列&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/beam-search/" data-link-title="Beam Search" data-link-desc="同時保留 K 條候選 sequence 的 decoding 策略、機器翻譯主流、chat / coding 場景慎用">Beam search&lt;/a> 的核心想法是「每步保留累積機率最大的 K 條序列、每條繼續展開、最後選整體機率最高的」。K 叫 beam size。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Beam size&lt;/th>
 &lt;th>行為&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1&lt;/td>
 &lt;td>等同 greedy&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3 ~ 5&lt;/td>
 &lt;td>翻譯、摘要等任務常用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>10+&lt;/td>
 &lt;td>高品質生成、但計算成本高&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>特性：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>全局較優&lt;/strong>：不只看當步、考慮整段序列。&lt;/li>
&lt;li>&lt;strong>適合「有正確答案」的任務&lt;/strong>：翻譯、摘要、code 生成。&lt;/li>
&lt;li>&lt;strong>缺點&lt;/strong>：對 open-ended 生成（聊天、創意寫作）會 collapse 到平庸、缺乏多樣性。&lt;/li>
&lt;/ul>
&lt;p>具體失效症狀：K=5 在 chat 場景常產生「Sure!」「Thank you」「That&amp;rsquo;s a great question」這種高頻 boilerplate、各 beam 探索的方向都收斂到相似的平庸開頭、明明 logit 分佈本來該保留的多樣性被 beam 平均化掉。&lt;/p></description><content:encoded><![CDATA[<p>LLM 的輸出本質是「下一個 <a href="/blog/llm/knowledge-cards/token/" data-link-title="Token" data-link-desc="LLM 處理文字時的最小單位：介於字元與單字之間">token</a> 的機率分佈」、不是直接的 token。從機率分佈挑下一個 token 的具體方法、就是 sampling / decoding 策略。同一個模型、同一個 prompt、不同 sampling 策略會給出顯著不同的輸出。</p>
<p>本章拆開主流 sampling 策略的機制、各自適合的場景、以及 <a href="/blog/llm/knowledge-cards/sampling-constraint/" data-link-title="Sampling Constraint" data-link-desc="推論時限制下一個 token 候選集合的控制手段，用來把模型生成導向合法格式或特定選項"><code>temperature</code></a>、<code>top_p</code> 這些常見參數在這條鏈上的位置。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>解釋 <code>temperature=0</code> 跟 <code>temperature=0.8</code> 的具體差別。</li>
<li>區分 top-k、top-p、min-p 三者的機制。</li>
<li>看到 <code>repetition_penalty=1.1</code> 設定時、知道它解什麼問題。</li>
<li>解釋為什麼確定性測試要設 <code>temperature=0</code> + <code>seed</code>。</li>
</ol>
<h2 id="從-logits-到下個-token">從 logits 到下個 token</h2>
<p>複習一下 LLM 輸出端的鏈：</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">final hidden states → output projection → logits → temperature → softmax → 機率分佈
</span></span><span class="line"><span class="ln">2</span><span class="cl">→ sampling 策略 → 下個 token</span></span></code></pre></div><p>各環節在 sampling 中的位置：</p>
<table>
  <thead>
      <tr>
          <th>環節</th>
          <th>對 sampling 的影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/logit/" data-link-title="Logit" data-link-desc="softmax 之前的原始實數分數、每個 vocab token 一個值、可正可負">logits</a></td>
          <td>模型給每個 token 的原始分數、還沒正規化</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/sampling-constraint/" data-link-title="Sampling Constraint" data-link-desc="推論時限制下一個 token 候選集合的控制手段，用來把模型生成導向合法格式或特定選項">temperature</a></td>
          <td>在 softmax 前除以 T、調整分佈尖銳度</td>
      </tr>
      <tr>
          <td><a href="/blog/llm/knowledge-cards/softmax/" data-link-title="Softmax" data-link-desc="把任意實數向量正規化成「總和為 1、每個分量 ∈ [0,1]」的機率分佈">softmax</a></td>
          <td>把 logits 轉成機率分佈</td>
      </tr>
      <tr>
          <td>top-k / top-p / min-p</td>
          <td>過濾低機率 token、把候選集縮小</td>
      </tr>
      <tr>
          <td>重新正規化</td>
          <td>把過濾後的剩餘 token 重新正規化成機率分佈</td>
      </tr>
      <tr>
          <td>取樣</td>
          <td>從正規化分佈中隨機選一個 token</td>
      </tr>
      <tr>
          <td>repetition penalty</td>
          <td>對已出現的 token 降權、避免重複</td>
      </tr>
  </tbody>
</table>
<p>實際參數順序視推論伺服器實作而異、但概念上是這條鏈。</p>
<h2 id="greedy-decoding永遠選機率最大">Greedy Decoding：永遠選機率最大</h2>
<p>Greedy decoding 的核心定義是「每步選 softmax 後機率最大的 token」：</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">next_token = argmax(probabilities)</span></span></code></pre></div><p>特性：</p>
<ul>
<li><strong>確定性</strong>：同 prompt 永遠生同樣輸出。</li>
<li><strong>快</strong>：不用 sampling、不用算 cumulative probabilities。</li>
<li><strong>缺點</strong>：傾向選最常見 pattern、輸出單調；常陷入 repetition loop。</li>
</ul>
<p>實務用途：</p>
<ul>
<li><strong>Reproducible 評估</strong>：跑 benchmark、自動測試。</li>
<li><strong>單元測試</strong>：確保模型輸出可預測。</li>
<li><strong>某些 reasoning chain</strong>：選最有信心的下一步。</li>
</ul>
<p>效果上等同 <code>temperature=0</code>、許多推論伺服器把兩者當同義詞。</p>
<h2 id="beam-search保留-top-k-條候選序列">Beam Search：保留 top-K 條候選序列</h2>
<p><a href="/blog/llm/knowledge-cards/beam-search/" data-link-title="Beam Search" data-link-desc="同時保留 K 條候選 sequence 的 decoding 策略、機器翻譯主流、chat / coding 場景慎用">Beam search</a> 的核心想法是「每步保留累積機率最大的 K 條序列、每條繼續展開、最後選整體機率最高的」。K 叫 beam size。</p>
<table>
  <thead>
      <tr>
          <th>Beam size</th>
          <th>行為</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>等同 greedy</td>
      </tr>
      <tr>
          <td>3 ~ 5</td>
          <td>翻譯、摘要等任務常用</td>
      </tr>
      <tr>
          <td>10+</td>
          <td>高品質生成、但計算成本高</td>
      </tr>
  </tbody>
</table>
<p>特性：</p>
<ul>
<li><strong>全局較優</strong>：不只看當步、考慮整段序列。</li>
<li><strong>適合「有正確答案」的任務</strong>：翻譯、摘要、code 生成。</li>
<li><strong>缺點</strong>：對 open-ended 生成（聊天、創意寫作）會 collapse 到平庸、缺乏多樣性。</li>
</ul>
<p>具體失效症狀：K=5 在 chat 場景常產生「Sure!」「Thank you」「That&rsquo;s a great question」這種高頻 boilerplate、各 beam 探索的方向都收斂到相似的平庸開頭、明明 logit 分佈本來該保留的多樣性被 beam 平均化掉。</p>
<p>Chat / 對話場景多半不用 beam search、用 sampling 策略保留多樣性。</p>
<h2 id="temperature調分佈尖銳度">Temperature：調分佈尖銳度</h2>
<p>Temperature 的機制在 <a href="/blog/llm/02-math-foundations/probability-and-information/" data-link-title="2.1 機率與資訊論" data-link-desc="LLM 輸出的本質是機率分佈：softmax、cross-entropy、KL divergence、perplexity 在訓練與推論中的角色">模組二 2.1</a> 已經詳細展開。簡單回顧：</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">adjusted_logits = logits / temperature
</span></span><span class="line"><span class="ln">2</span><span class="cl">probabilities = softmax(adjusted_logits)</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>Temperature</th>
          <th>效果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>等同 greedy（argmax）</td>
      </tr>
      <tr>
          <td>0.2 ~ 0.4</td>
          <td>寫 code、回答事實問題、減少 hallucination</td>
      </tr>
      <tr>
          <td>0.7</td>
          <td>預設、平衡多樣性與品質</td>
      </tr>
      <tr>
          <td>0.9 ~ 1.0</td>
          <td>創意寫作、保留多樣性</td>
      </tr>
      <tr>
          <td>&gt; 1.5</td>
          <td>隨機性極高、輸出可能變混亂</td>
      </tr>
  </tbody>
</table>
<p>實務經驗：</p>
<ul>
<li>寫 code 場景設 0.2 ~ 0.4 較穩。</li>
<li>創意任務（寫故事、brainstorming）設 0.8 ~ 1.0。</li>
<li>Reproducible 測試設 0 + 固定 seed。</li>
<li>T &gt; 1.5 失效症狀：產出開始出現拼字錯誤、語法破洞、UTF-8 byte 混亂、甚至跨語言突然切換；極端 T 等同近 uniform 分佈、模型結構被當作 dice。</li>
</ul>
<p>Temperature 在 sampling 鏈上的位置（行 26 的流程鏈）跟其他過濾步驟有疊加順序、值得展開：</p>
<ol>
<li><strong>Temperature 先動分佈尖銳度</strong>：高 T 把分佈拉平、低 T 拉尖。</li>
<li><strong>再過 top-p / top-k / min-p</strong>：在拉平 / 拉尖後的分佈上做候選過濾。</li>
<li><strong>兩者相乘的常見坑</strong>：高 T（如 1.5）+ 低 top-p（如 0.5）= 「分佈被拉平、然後只挑前幾名」、實際出現的多樣性反而被壓縮、容易出現語義跳動。穩健做法：固定其中一個（多半是 top-p=0.9）、調另一個。</li>
<li><strong>Reasoning model 的特殊性</strong>：o1、DeepSeek-R1 等內建 chain-of-thought 的模型、官方建議 T=0 或 1.0、調 T 會破壞 reasoning trace 連貫性。</li>
</ol>
<h2 id="top-k-sampling">Top-K Sampling</h2>
<p><a href="/blog/llm/knowledge-cards/top-p-sampling/" data-link-title="Top-K / Top-P / Min-P Sampling" data-link-desc="從機率分佈取樣前先過濾低機率 token 的三種策略、現代 LLM 推論主流">Top-K sampling</a> 的核心定義是「只考慮機率最大的 K 個 token、其他設 0、重新正規化後取樣」：</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">1. 對機率排序、取最大的 K 個。
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 其他設 0。
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 重新正規化（讓總和為 1）。
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 從正規化分佈取樣。</span></span></code></pre></div><p>K 控制候選範圍：</p>
<table>
  <thead>
      <tr>
          <th>K</th>
          <th>行為</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>等同 greedy</td>
      </tr>
      <tr>
          <td>40</td>
          <td>預設常用值</td>
      </tr>
      <tr>
          <td>100+</td>
          <td>接近完全 sampling、限制較小</td>
      </tr>
  </tbody>
</table>
<p>缺點：K 是固定值、無法適應分佈尖銳度。當分佈尖銳時（一個 token 機率 90%）、K=40 包括很多近 0 機率的雜訊；當分佈平坦時（每個 token 機率 1%）、K=40 過於限制。具體失效症狀：在 code 生成情境、模型對「下一個 token 是 <code>)</code>」極度確定（95%+ 機率）時、K=40 把後面 39 個近零雜訊也納入候選、偶爾 sample 出語法錯的字元；改用 top-p 或 min-p 可避開。</p>
<h2 id="top-p--nucleus-sampling">Top-P / Nucleus Sampling</h2>
<p>Top-P sampling（也叫 nucleus sampling、Holtzman et al., 2019）的核心想法是「動態決定候選集大小」：</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">1. 對機率從大到小排序。
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 從大到小累加、直到累積機率 ≥ P（如 0.9）。
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 只保留這些 token、其他設 0。
</span></span><span class="line"><span class="ln">4</span><span class="cl">4. 重新正規化、取樣。</span></span></code></pre></div><p>例：</p>
<ul>
<li>分佈尖銳（一個 token 機率 95%）：P=0.9 可能只選 1 ~ 2 個 token。</li>
<li>分佈平坦（top 10 各 5%）：P=0.9 可能選 15 ~ 20 個 token。</li>
</ul>
<p>P 的常用值：</p>
<table>
  <thead>
      <tr>
          <th>P</th>
          <th>行為</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0.5</td>
          <td>較保守、傾向選機率高的</td>
      </tr>
      <tr>
          <td>0.9</td>
          <td>預設、保留合理多樣性</td>
      </tr>
      <tr>
          <td>0.95</td>
          <td>略放寬</td>
      </tr>
      <tr>
          <td>1.0</td>
          <td>等同關閉 top-p、用完整分佈</td>
      </tr>
  </tbody>
</table>
<p>在 chat / coding 場景下 top-p 是主流選擇、比 top-K 彈性；reproducible 評估則回到 greedy（T=0）、不用 sampling。多數推論伺服器預設 top_p=0.9。</p>
<h2 id="min-p自適應閾值-sampling">Min-P：自適應閾值 sampling</h2>
<p>Min-P sampling（2024 ~）的核心想法是「設一個機率閾值、最大機率 token × P_min 以下的全部去掉」：</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">1. 找出最大機率 p_max。
</span></span><span class="line"><span class="ln">2</span><span class="cl">2. 閾值 = p_max × P_min（如 0.1）。
</span></span><span class="line"><span class="ln">3</span><span class="cl">3. 機率 &lt; 閾值的 token 全部設 0、重新正規化。</span></span></code></pre></div><p>特性：</p>
<ul>
<li>自動適應分佈尖銳度（用比例而非絕對值）。</li>
<li>比 top-P 更穩定、近一兩年在開源社群興起。</li>
<li>LM Studio、llama.cpp 等支援。</li>
</ul>
<p>P_min 常用值：</p>
<table>
  <thead>
      <tr>
          <th>P_min</th>
          <th>行為</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0.05</td>
          <td>保留多樣性</td>
      </tr>
      <tr>
          <td>0.1</td>
          <td>平衡</td>
      </tr>
      <tr>
          <td>0.2</td>
          <td>較保守</td>
      </tr>
  </tbody>
</table>
<h2 id="repetition-penalty">Repetition Penalty</h2>
<p>Repetition penalty 的核心想法是「對已出現的 token 降低機率、避免無限重複」：</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">adjusted_logit(token) = logit(token) / repetition_penalty   if token 已出現
</span></span><span class="line"><span class="ln">2</span><span class="cl">                      = logit(token)                          if token 沒出現</span></span></code></pre></div><p>P 大於 1 時、已出現 token 的 logit 被降低、後續 sampling 較難選到。</p>
<table>
  <thead>
      <tr>
          <th>Penalty</th>
          <th>效果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1.0</td>
          <td>關閉</td>
      </tr>
      <tr>
          <td>1.05</td>
          <td>輕微抑制</td>
      </tr>
      <tr>
          <td>1.1</td>
          <td>預設常用</td>
      </tr>
      <tr>
          <td>1.3+</td>
          <td>強烈抑制、可能過度避免合理重複</td>
      </tr>
  </tbody>
</table>
<p>代價：寫 code 場景下、<code>if</code>、<code>for</code>、<code>return</code> 等關鍵字常出現、太高的 repetition penalty 會壞掉 code。寫 code 場景 penalty 設低（1.0 ~ 1.05）或關閉；creative writing 場景則設 1.1 ~ 1.2、避免段落 / 句子層級的重複。</p>
<h2 id="seed固定-sampling-的隨機性">Seed：固定 sampling 的隨機性</h2>
<p>Sampling 用 random number generator 取樣。<strong>設定 seed 讓 RNG 確定性</strong>、相同 prompt + 相同 seed 給相同輸出：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="s2">&#34;temperature&#34;</span><span class="p">:</span> <span class="mf">0.7</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="s2">&#34;top_p&#34;</span><span class="p">:</span> <span class="mf">0.9</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="s2">&#34;seed&#34;</span><span class="p">:</span> <span class="mi">42</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>實務用途：</p>
<ul>
<li><strong>Reproducible 評估</strong>：跑 benchmark 要可重複。</li>
<li><strong>A/B 測試</strong>：對比不同 prompt 在同 seed 下的差異。</li>
<li><strong>Debug</strong>：重現一個錯誤輸出。</li>
</ul>
<p>注意：seed 不是所有伺服器都支援、OpenAI API 是 best-effort（同 seed 不保證完全一致）、本地伺服器多半支援嚴格 seed 控制。</p>
<h2 id="logit-bias強制--排除特定-token">Logit Bias：強制 / 排除特定 token</h2>
<p>Logit bias 的機制是「對特定 token 的 logit 加減一個固定值」：</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">adjusted_logit(token) = logit(token) + bias(token)</span></span></code></pre></div><p>用途：</p>
<ul>
<li><strong>強制特定 token</strong>：bias = +100、softmax 後機率近 1。</li>
<li><strong>完全禁止</strong>：bias = -100、softmax 後機率近 0。</li>
<li><strong>微調傾向</strong>：bias = ±5、輕微傾斜。</li>
</ul>
<p>實務用例：</p>
<ul>
<li>強制輸出 JSON 格式：對 <code>{</code> 加 bias 在開頭。</li>
<li>避免特定詞：對敏感詞加負 bias。</li>
<li>約束輸出：限制只能用特定 vocabulary。</li>
</ul>
<p>OpenAI、Ollama 等多數推論伺服器支援 logit_bias 參數。</p>
<h2 id="structured-output--constrained-decoding">Structured Output / Constrained Decoding</h2>
<p><a href="/blog/llm/knowledge-cards/structured-output/" data-link-title="Structured Output" data-link-desc="讓 LLM 輸出可被 parser 穩定消費的推論階段設計：JSON mode、schema-guided decoding、grammar 約束都屬於這一層">Structured output</a> 的核心想法是「sampling 時加 grammar 約束、強制輸出符合特定結構（JSON、SQL、regex 等）」。實作方法：</p>
<ul>
<li><strong>JSON mode</strong>：每步只允許「能讓 JSON 仍合法」的 token。</li>
<li><strong>Grammar-based</strong>：用 <a href="/blog/llm/knowledge-cards/bnf/" data-link-title="BNF（Backus-Naur Form）" data-link-desc="用遞迴產生式描述語法的經典記法，是 CFG、parser 與 grammar-constrained sampling 常見的基礎表示">BNF</a> / lark / etc. 定義語法、sampling 時 reject 違反語法的 token。</li>
<li><strong>Token mask</strong>：依當前狀態決定哪些 token 合法、不合法的 logit 設 -∞。</li>
</ul>
<p>實務工具：</p>
<ul>
<li>llama.cpp 的 <code>grammar</code> 參數。</li>
<li>Outlines、LMQL 等 framework。</li>
<li>OpenAI 的 <code>response_format: { type: &quot;json_schema&quot; }</code>。</li>
</ul>
<p>寫 code 場景中、structured output 對「要可解析的輸出」（如 commit message 格式、structured API call）很有用。</p>
<h2 id="decoding-策略對體感的影響">Decoding 策略對體感的影響</h2>
<p>下表是寫 code 場景下、不同 decoding 配置的體感：</p>
<table>
  <thead>
      <tr>
          <th>配置</th>
          <th>體感</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>temperature=0、greedy</td>
          <td>確定、可重複、但可能單調</td>
      </tr>
      <tr>
          <td>temperature=0.2、top_p=0.95</td>
          <td>穩定、寫 code 主流</td>
      </tr>
      <tr>
          <td>temperature=0.7、top_p=0.9</td>
          <td>平衡、預設</td>
      </tr>
      <tr>
          <td>temperature=1.0、top_p=0.95、min_p=0.05</td>
          <td>創意、多樣</td>
      </tr>
      <tr>
          <td>temperature=1.5</td>
          <td>過於隨機、code 容易壞</td>
      </tr>
      <tr>
          <td>repetition_penalty=1.3、寫 code 場景</td>
          <td>抑制太強、會壞掉 keyword 重複用法</td>
      </tr>
  </tbody>
</table>
<p>實務建議：寫 code 場景下 temperature=0.2 ~ 0.4、top_p=0.9 ~ 0.95、其他保留預設就好。Continue.dev 等 IDE 整合多半自動調整。</p>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/03-theoretical-foundations/tokenization-algorithms/" data-link-title="3.6 Tokenization：BPE、SentencePiece、Tiktoken" data-link-desc="把文字切成 token 的算法：為什麼不同模型切出不同 token 數、tokenizer 選擇對能力的影響">3.6 tokenization 算法</a>、補完 input / output 端的細節。</p>
]]></content:encoded></item><item><title>前端感測器設計</title><link>https://tarrragon.github.io/blog/monitoring/03-sdk-design/frontend-sensor-design/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/03-sdk-design/frontend-sensor-design/</guid><description>&lt;p>感測器是 SDK 主動偵測使用者行為的元件。和 &lt;a href="https://tarrragon.github.io/blog/monitoring/03-sdk-design/auto-intercept/" data-link-title="自動攔截機制" data-link-desc="JS window.onerror / Flutter FlutterError.onError / Python sys.excepthook — 各平台攔截未捕獲例外的機制和限制">自動攔截機制&lt;/a> 的被動攔截不同 — auto-intercept 攔截的是系統級事件（uncaught exception、unhandled rejection），感測器偵測的是業務級行為（使用者點了什麼、看了哪個畫面、操作花了多久）。兩者互補：auto-intercept 提供 error 和 lifecycle 的基礎層，感測器提供 event 和 metric 的業務層。&lt;/p>
&lt;h2 id="點擊觸碰感測器">點擊/觸碰感測器&lt;/h2>
&lt;p>點擊感測器偵測使用者和 UI 元素的互動 — 按鈕點擊、連結觸碰、選單選擇。每次互動產生一個 event 類型的事件。&lt;/p>
&lt;h3 id="哪些元素值得追蹤">哪些元素值得追蹤&lt;/h3>
&lt;p>追蹤粒度的判斷依據是「這個互動是否對應一個有意義的使用者意圖」。&lt;/p>
&lt;p>有意義的互動（值得追蹤）：提交表單、點擊導航按鈕、觸發功能操作（連線、配對、匯出）。這些互動對應使用者的明確意圖，是 &lt;a href="https://tarrragon.github.io/blog/monitoring/08-business-analytics/funnel-analysis/" data-link-title="Funnel Analysis" data-link-desc="使用者在哪一步流失 — 從事件序列計算每步轉換率、找出流失最嚴重的步驟、區分設計問題和技術問題">funnel 分析&lt;/a> 的步驟候選。&lt;/p>
&lt;p>低價值的互動（通常不追蹤）：滾動、hover、重複的相同操作（每秒多次的按鈕連按）。這些互動要麼太頻繁（滾動每秒觸發數十次），要麼不代表新的使用者意圖。&lt;/p>
&lt;h3 id="實作方式">實作方式&lt;/h3>
&lt;p>&lt;strong>Web（JS/TS）&lt;/strong>：在 document 層級用 event delegation 攔截 click 事件，過濾出帶 &lt;code>data-track&lt;/code> attribute 的元素。開發者在需要追蹤的元素上加 &lt;code>data-track=&amp;quot;connect-button&amp;quot;&lt;/code>，感測器自動收集。不追蹤所有 click — 只追蹤被標記的。&lt;/p>
&lt;p>&lt;strong>Flutter&lt;/strong>：用 NavigatorObserver 或 custom GestureDetector wrapper。GestureDetector 包裝在需要追蹤的 widget 外層，onTap 觸發時送出事件。&lt;/p>
&lt;h3 id="效能影響">效能影響&lt;/h3>
&lt;p>Event delegation 在 document 層級只有一個 listener，效能影響接近零。瓶頸在事件產生頻率 — 如果追蹤了高頻操作（每秒多次的滑動），事件進入 buffer 的速度可能超過 flush 的速度。用取樣控制（見本章末段）。&lt;/p>
&lt;h2 id="導航路由感測器">導航/路由感測器&lt;/h2>
&lt;p>導航感測器偵測使用者在不同畫面之間的切換 — page view、screen view、route change。每次切換產生一個 lifecycle 類型的事件。&lt;/p>
&lt;h3 id="平台差異">平台差異&lt;/h3>
&lt;p>&lt;strong>Web SPA&lt;/strong>：SPA 的 route 變換不觸發頁面載入，需要主動偵測 URL 變化。兩種偵測方式：&lt;/p>
&lt;ul>
&lt;li>History API 攔截：覆寫 &lt;code>pushState&lt;/code> / &lt;code>replaceState&lt;/code>，攔截 &lt;code>popstate&lt;/code> 事件&lt;/li>
&lt;li>框架層級 Hook：React Router 的 &lt;code>useLocation&lt;/code>、Vue Router 的 &lt;code>afterEach&lt;/code> guard&lt;/li>
&lt;/ul>
&lt;p>History API 攔截是 SDK 層的通用做法（不依賴框架）；框架 Hook 更精確但需要使用者整合（見 &lt;a href="https://tarrragon.github.io/blog/monitoring/05-platform-adaptation/js-ts-platform/" data-link-title="JS/TS 平台適配" data-link-desc="CORS 限制、Service Worker 攔截、SPA 路由變換偵測 — 瀏覽器環境中 SDK 需要處理的平台特殊問題">JS/TS 平台&lt;/a> 的 SPA 路由段）。&lt;/p>
&lt;p>&lt;strong>Flutter&lt;/strong>：用 &lt;code>NavigatorObserver&lt;/code> 的 &lt;code>didPush&lt;/code> / &lt;code>didPop&lt;/code> / &lt;code>didReplace&lt;/code> 回呼。每次路由變化自動觸發，不需要使用者在每個頁面手動埋點。&lt;/p>
&lt;p>&lt;strong>Python CLI/Hook&lt;/strong>：沒有「畫面切換」的概念。對應的 lifecycle 事件是 &lt;code>hook.start&lt;/code> / &lt;code>hook.complete&lt;/code> — 每個 Hook 執行視為一個「畫面」。&lt;/p>
&lt;h3 id="事件-schema">事件 schema&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;lifecycle&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;screen.view&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;data&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;screen_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;TerminalScreen&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;previous_screen&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;HomeScreen&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;navigation_method&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;push&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>navigation_method&lt;/code>（push / pop / replace / go）記錄導航方式，和 &lt;a href="https://tarrragon.github.io/blog/ux-design/05-navigation-patterns/go-push-semantics/" data-link-title="go vs push vs pushReplacement 的 UX 語意表" data-link-desc="三種導航方法對堆疊、back 行為、使用者心理模型的影響 — 選擇依據是使用者的意圖而非技術方便">go vs push 的 UX 語意&lt;/a> 對應。&lt;/p>
&lt;h2 id="錯誤邊界感測器">錯誤邊界感測器&lt;/h2>
&lt;p>錯誤邊界感測器攔截元件級的 error — 和 auto-intercept 的全域 error 攔截互補。&lt;/p></description><content:encoded><![CDATA[<p>感測器是 SDK 主動偵測使用者行為的元件。和 <a href="/blog/monitoring/03-sdk-design/auto-intercept/" data-link-title="自動攔截機制" data-link-desc="JS window.onerror / Flutter FlutterError.onError / Python sys.excepthook — 各平台攔截未捕獲例外的機制和限制">自動攔截機制</a> 的被動攔截不同 — auto-intercept 攔截的是系統級事件（uncaught exception、unhandled rejection），感測器偵測的是業務級行為（使用者點了什麼、看了哪個畫面、操作花了多久）。兩者互補：auto-intercept 提供 error 和 lifecycle 的基礎層，感測器提供 event 和 metric 的業務層。</p>
<h2 id="點擊觸碰感測器">點擊/觸碰感測器</h2>
<p>點擊感測器偵測使用者和 UI 元素的互動 — 按鈕點擊、連結觸碰、選單選擇。每次互動產生一個 event 類型的事件。</p>
<h3 id="哪些元素值得追蹤">哪些元素值得追蹤</h3>
<p>追蹤粒度的判斷依據是「這個互動是否對應一個有意義的使用者意圖」。</p>
<p>有意義的互動（值得追蹤）：提交表單、點擊導航按鈕、觸發功能操作（連線、配對、匯出）。這些互動對應使用者的明確意圖，是 <a href="/blog/monitoring/08-business-analytics/funnel-analysis/" data-link-title="Funnel Analysis" data-link-desc="使用者在哪一步流失 — 從事件序列計算每步轉換率、找出流失最嚴重的步驟、區分設計問題和技術問題">funnel 分析</a> 的步驟候選。</p>
<p>低價值的互動（通常不追蹤）：滾動、hover、重複的相同操作（每秒多次的按鈕連按）。這些互動要麼太頻繁（滾動每秒觸發數十次），要麼不代表新的使用者意圖。</p>
<h3 id="實作方式">實作方式</h3>
<p><strong>Web（JS/TS）</strong>：在 document 層級用 event delegation 攔截 click 事件，過濾出帶 <code>data-track</code> attribute 的元素。開發者在需要追蹤的元素上加 <code>data-track=&quot;connect-button&quot;</code>，感測器自動收集。不追蹤所有 click — 只追蹤被標記的。</p>
<p><strong>Flutter</strong>：用 NavigatorObserver 或 custom GestureDetector wrapper。GestureDetector 包裝在需要追蹤的 widget 外層，onTap 觸發時送出事件。</p>
<h3 id="效能影響">效能影響</h3>
<p>Event delegation 在 document 層級只有一個 listener，效能影響接近零。瓶頸在事件產生頻率 — 如果追蹤了高頻操作（每秒多次的滑動），事件進入 buffer 的速度可能超過 flush 的速度。用取樣控制（見本章末段）。</p>
<h2 id="導航路由感測器">導航/路由感測器</h2>
<p>導航感測器偵測使用者在不同畫面之間的切換 — page view、screen view、route change。每次切換產生一個 lifecycle 類型的事件。</p>
<h3 id="平台差異">平台差異</h3>
<p><strong>Web SPA</strong>：SPA 的 route 變換不觸發頁面載入，需要主動偵測 URL 變化。兩種偵測方式：</p>
<ul>
<li>History API 攔截：覆寫 <code>pushState</code> / <code>replaceState</code>，攔截 <code>popstate</code> 事件</li>
<li>框架層級 Hook：React Router 的 <code>useLocation</code>、Vue Router 的 <code>afterEach</code> guard</li>
</ul>
<p>History API 攔截是 SDK 層的通用做法（不依賴框架）；框架 Hook 更精確但需要使用者整合（見 <a href="/blog/monitoring/05-platform-adaptation/js-ts-platform/" data-link-title="JS/TS 平台適配" data-link-desc="CORS 限制、Service Worker 攔截、SPA 路由變換偵測 — 瀏覽器環境中 SDK 需要處理的平台特殊問題">JS/TS 平台</a> 的 SPA 路由段）。</p>
<p><strong>Flutter</strong>：用 <code>NavigatorObserver</code> 的 <code>didPush</code> / <code>didPop</code> / <code>didReplace</code> 回呼。每次路由變化自動觸發，不需要使用者在每個頁面手動埋點。</p>
<p><strong>Python CLI/Hook</strong>：沒有「畫面切換」的概念。對應的 lifecycle 事件是 <code>hook.start</code> / <code>hook.complete</code> — 每個 Hook 執行視為一個「畫面」。</p>
<h3 id="事件-schema">事件 schema</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;lifecycle&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;screen.view&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nt">&#34;screen_name&#34;</span><span class="p">:</span> <span class="s2">&#34;TerminalScreen&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nt">&#34;previous_screen&#34;</span><span class="p">:</span> <span class="s2">&#34;HomeScreen&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="nt">&#34;navigation_method&#34;</span><span class="p">:</span> <span class="s2">&#34;push&#34;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>navigation_method</code>（push / pop / replace / go）記錄導航方式，和 <a href="/blog/ux-design/05-navigation-patterns/go-push-semantics/" data-link-title="go vs push vs pushReplacement 的 UX 語意表" data-link-desc="三種導航方法對堆疊、back 行為、使用者心理模型的影響 — 選擇依據是使用者的意圖而非技術方便">go vs push 的 UX 語意</a> 對應。</p>
<h2 id="錯誤邊界感測器">錯誤邊界感測器</h2>
<p>錯誤邊界感測器攔截元件級的 error — 和 auto-intercept 的全域 error 攔截互補。</p>
<h3 id="和-auto-intercept-的職責分工">和 auto-intercept 的職責分工</h3>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>機制</th>
          <th>攔截什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>全域</td>
          <td>auto-intercept（<code>window.onerror</code> / <code>FlutterError.onError</code>）</td>
          <td>uncaught exception、未處理的 Promise rejection</td>
      </tr>
      <tr>
          <td>元件</td>
          <td>錯誤邊界感測器（React ErrorBoundary / Flutter Widget error handler）</td>
          <td>元件渲染失敗、子樹 error</td>
      </tr>
  </tbody>
</table>
<p>全域攔截捕獲「逃逸到頂層的 error」，錯誤邊界捕獲「在元件層級就被攔住的 error」。如果一個 error 被元件的 ErrorBoundary 捕獲，它不會觸發 <code>window.onerror</code> — auto-intercept 看不到它。錯誤邊界感測器填補這個缺口。</p>
<h3 id="實作方式-1">實作方式</h3>
<p><strong>React</strong>：ErrorBoundary 元件的 <code>componentDidCatch</code> 回呼中呼叫 <code>monitor.error()</code>。</p>
<p><strong>Flutter</strong>：在 Widget 層用 <code>ErrorWidget.builder</code> 或自訂的 error handling widget。</p>
<h3 id="額外-context">額外 context</h3>
<p>錯誤邊界感測器比全域攔截多一個 context — 知道 error 發生在哪個元件（component name / widget name）。這個資訊在 error 的 data schema 中記錄為 <code>component</code> 欄位。</p>
<h2 id="效能標記感測器">效能標記感測器</h2>
<p>效能標記感測器量測操作的延遲和系統的渲染表現。產生 metric 類型的事件。</p>
<h3 id="web-core-vitals">Web Core Vitals</h3>
<p>Web 平台用 <code>PerformanceObserver</code> API 自動收集三個核心指標：</p>
<ul>
<li><strong>LCP</strong>（Largest Contentful Paint）：最大內容元素的載入時間</li>
<li><strong>FID</strong>（First Input Delay）：首次互動的延遲</li>
<li><strong>CLS</strong>（Cumulative Layout Shift）：累計佈局位移分數</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">new</span> <span class="nx">PerformanceObserver</span><span class="p">((</span><span class="nx">list</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">entry</span> <span class="k">of</span> <span class="nx">list</span><span class="p">.</span><span class="nx">getEntries</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">monitor</span><span class="p">.</span><span class="nx">metric</span><span class="p">(</span><span class="sb">`web.vitals.</span><span class="si">${</span><span class="nx">entry</span><span class="p">.</span><span class="nx">entryType</span><span class="si">}</span><span class="sb">`</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">      <span class="nx">value</span><span class="o">:</span> <span class="nx">entry</span><span class="p">.</span><span class="nx">startTime</span> <span class="o">||</span> <span class="nx">entry</span><span class="p">.</span><span class="nx">value</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">      <span class="nx">url</span><span class="o">:</span> <span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">}).</span><span class="nx">observe</span><span class="p">({</span> <span class="nx">type</span><span class="o">:</span> <span class="s1">&#39;largest-contentful-paint&#39;</span><span class="p">,</span> <span class="nx">buffered</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span></span></span></code></pre></div><p>實務上依 entryType 分別取值（LCP 用 <code>startTime</code>、CLS 用 <code>value</code>、FID 用 <code>processingStart - startTime</code>），上述範例簡化示意。</p>
<h3 id="flutter-frame-timing">Flutter frame timing</h3>
<p>Flutter 用 <code>SchedulerBinding.addTimingsCallback</code> 偵測掉幀：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">SchedulerBinding</span><span class="p">.</span><span class="n">instance</span><span class="p">.</span><span class="n">addTimingsCallback</span><span class="p">((</span><span class="n">timings</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kd">final</span> <span class="n">t</span> <span class="k">in</span> <span class="n">timings</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">totalSpan</span> <span class="o">&gt;</span> <span class="kd">const</span> <span class="n">Duration</span><span class="p">(</span><span class="nl">milliseconds:</span> <span class="m">16</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">      <span class="n">monitor</span><span class="p">.</span><span class="n">metric</span><span class="p">(</span><span class="s1">&#39;render.frame_drop&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s1">&#39;build_ms&#39;</span><span class="o">:</span> <span class="n">t</span><span class="p">.</span><span class="n">buildDuration</span><span class="p">.</span><span class="n">inMilliseconds</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="s1">&#39;raster_ms&#39;</span><span class="o">:</span> <span class="n">t</span><span class="p">.</span><span class="n">rasterDuration</span><span class="p">.</span><span class="n">inMilliseconds</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>16ms 是 60fps 的單幀預算。超過代表掉幀。</p>
<h3 id="自訂-duration-量測">自訂 duration 量測</h3>
<p>業務操作的延遲用手動標記量測：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">final</span> <span class="n">stopwatch</span> <span class="o">=</span> <span class="n">Stopwatch</span><span class="p">()..</span><span class="n">start</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">await</span> <span class="n">connectToTerminal</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">stopwatch</span><span class="p">.</span><span class="n">stop</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">monitor</span><span class="p">.</span><span class="n">metric</span><span class="p">(</span><span class="s1">&#39;terminal.connect.duration&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="s1">&#39;duration_ms&#39;</span><span class="o">:</span> <span class="n">stopwatch</span><span class="p">.</span><span class="n">elapsedMilliseconds</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><h2 id="輸入敏感度感測器">輸入敏感度感測器</h2>
<p>輸入敏感度感測器偵測使用者正在輸入敏感資料 — 密碼欄位、API key 輸入、信用卡號碼。這個感測器的責任是<strong>觸發 redaction，而非記錄輸入內容</strong>。</p>
<h3 id="偵測邏輯">偵測邏輯</h3>
<p><strong>Web</strong>：偵測 <code>&lt;input type=&quot;password&quot;&gt;</code>、帶有 <code>autocomplete=&quot;cc-number&quot;</code> 或 <code>data-sensitive</code> attribute 的欄位。當使用者 focus 這些欄位時，標記當前 session 進入「敏感輸入模式」— 後續的事件自動加嚴 <a href="/blog/monitoring/knowledge-cards/redaction/" data-link-title="Redaction" data-link-desc="說明在事件資料離開 client 之前把敏感欄位的值替換成遮罩或移除的機制">redaction</a> 規則（例如暫停記錄按鍵事件）。</p>
<p><strong>Flutter</strong>：偵測 <code>TextField</code> 的 <code>obscureText: true</code> 或 <code>enableIMEPersonalizedLearning: false</code>（見 <a href="/blog/ux-design/03-input-mechanism/ime-security-checklist/" data-link-title="安全敏感輸入框的 IME 控制 checklist" data-link-desc="處理密碼、API key、伺服器路徑等 secret 的輸入框需要關閉 IME 的個人化學習和自動校正 — 安全要求而非 UX 偏好">安全敏感輸入框的 IME 控制</a>）。</p>
<h3 id="不記錄的原則">不記錄的原則</h3>
<p>輸入敏感度感測器偵測「使用者正在輸入敏感內容」這個事實，但不記錄輸入的內容本身。送出的事件只包含：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;lifecycle&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;input.sensitive_mode.entered&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;field_type&#34;</span><span class="p">:</span> <span class="s2">&#34;password&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="取樣策略設計">取樣策略設計</h2>
<p>感測器產生的事件量可能很大（效能標記每 30 秒一筆 × 活躍使用者數）。取樣控制事件量、避免 SDK 和 collector 的資源壓力。</p>
<h3 id="三種取樣模式">三種取樣模式</h3>
<p><strong>全收</strong>：每筆事件都送出。適合事件量低且每筆都有價值的類型 — error（每筆都可能是新 bug）、lifecycle 狀態轉換（量低）、認證失敗（安全敏感）。</p>
<p><strong>百分比取樣</strong>：隨機丟棄一定比例的事件。適合高頻的效能和行為事件。取樣率由 SDK config 控制：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">sensors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span><span class="nt">metric</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">    </span><span class="nt">render.frame_drop</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">sampling</span><span class="p">:</span><span class="w"> </span><span class="m">0.1</span><span class="w"> </span>}<span class="w">    </span><span class="c"># 只收 10%</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="nt">resource.memory</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">sampling</span><span class="p">:</span><span class="w"> </span><span class="m">0.5</span><span class="w"> </span>}<span class="w">       </span><span class="c"># 收 50%</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">  </span><span class="nt">event</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span><span class="nt">feature.*.used</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">sampling</span><span class="p">:</span><span class="w"> </span><span class="m">1.0</span><span class="w"> </span>}<span class="w">        </span><span class="c"># 全收</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">    </span><span class="nt">click.*</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">sampling</span><span class="p">:</span><span class="w"> </span><span class="m">0.1</span><span class="w"> </span>}<span class="w">               </span><span class="c"># 只收 10%</span></span></span></code></pre></div><p>百分比取樣的代價是低機率事件可能被漏掉（取樣 10% 時、發生 5 次的事件可能一次都沒收到）。</p>
<p><strong>條件取樣</strong>：正常情況下取樣、特定條件下全收。適合「平時不需要全量但問題發生時需要完整資料」的場景。例：正常 session 取樣 10%、但 session 內發生 error 後、該 session 剩餘事件全收（error session 的完整 context 比正常 session 更有價值）。</p>
<h3 id="取樣率的管理">取樣率的管理</h3>
<p>取樣率可以從三個層級設定：</p>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>設定方式</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SDK 本地 config</td>
          <td>隨 app 版本部署</td>
          <td>固定的基線取樣率</td>
      </tr>
      <tr>
          <td>Collector 下發</td>
          <td>SDK 啟動時從 collector 取得 config</td>
          <td>動態調整、不需要重新部署 app</td>
      </tr>
      <tr>
          <td>Feature flag 服務</td>
          <td>整合 LaunchDarkly / Unleash</td>
          <td>實驗期間對特定群組調整取樣</td>
      </tr>
  </tbody>
</table>
<p>三個層級由上到下優先順序遞增 — feature flag 覆蓋 collector config、collector config 覆蓋本地 config。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>動機驅動的事件設計（哪些動機需要哪些感測器） → <a href="/blog/monitoring/01-mental-model/motivation-to-event-mapping/" data-link-title="動機驅動的事件設計" data-link-desc="Debug / 商業 / 資安 / 效能四個動機各自需要什麼事件 — 從「為什麼收」反推「收什麼」和「什麼階段啟用」">動機驅動的事件設計</a></li>
<li>感測器的啟停控制和生命週期 → <a href="/blog/monitoring/03-sdk-design/sensor-lifecycle-management/" data-link-title="感測器生命週期管理" data-link-desc="產品生命週期的五個階段各啟用什麼感測器 — feature flag 整合、取樣率動態調整、感測器開關的可觀察性">感測器生命週期管理</a></li>
<li>被動攔截機制（和感測器互補） → <a href="/blog/monitoring/03-sdk-design/auto-intercept/" data-link-title="自動攔截機制" data-link-desc="JS window.onerror / Flutter FlutterError.onError / Python sys.excepthook — 各平台攔截未捕獲例外的機制和限制">自動攔截機制</a></li>
<li>安全敏感輸入的完整 checklist → <a href="/blog/ux-design/03-input-mechanism/ime-security-checklist/" data-link-title="安全敏感輸入框的 IME 控制 checklist" data-link-desc="處理密碼、API key、伺服器路徑等 secret 的輸入框需要關閉 IME 的個人化學習和自動校正 — 安全要求而非 UX 偏好">安全敏感輸入框的 IME 控制</a></li>
</ul>
]]></content:encoded></item><item><title>Sampling</title><link>https://tarrragon.github.io/blog/monitoring/knowledge-cards/sampling/</link><pubDate>Wed, 24 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/knowledge-cards/sampling/</guid><description>&lt;p>取樣（sampling）的通用概念見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/sampling/" data-link-title="Sampling" data-link-desc="說明觀測資料如何抽樣以控制成本並保留診斷能力">Backend 知識卡：Sampling&lt;/a> — 只保留部分觀測資料以控制成本。本卡聚焦監控 SDK 中的具體實作：在事件產生階段按比例丟棄部分事件，降低後續管線（buffer → transport → collector → storage）的負載。取樣是設計內的損失 — 取樣率是明確的 config 參數，損失量可預測。可先對照 &lt;a href="https://tarrragon.github.io/blog/monitoring/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="下游處理能力不足時向上游回傳「慢下來」訊號的流量控制機制 — 監控系統中 collector 用 HTTP 429 向 SDK 傳遞背壓">backpressure&lt;/a>（觸發動態取樣的訊號來源）和 &lt;a href="https://tarrragon.github.io/blog/monitoring/knowledge-cards/rate-limiting/" data-link-title="Rate Limiting" data-link-desc="限制每個 client 在單位時間內可送出的事件數量 — 防止單一 SDK bug 或偽造流量消耗整個 collector 的處理能力">rate limiting&lt;/a>（collector 端的 per-client 限制）。&lt;/p>
&lt;h2 id="兩種取樣">兩種取樣&lt;/h2>
&lt;p>&lt;strong>靜態取樣&lt;/strong>：SDK config 中設定固定比例（例如 metric 類 0.1 = 每 10 筆只收 1 筆），在 SDK 整個生命週期保持不變。適合已知高頻但單筆 debug 價值低的事件（render.frame_time、scroll.position）。&lt;/p>
&lt;p>&lt;strong>動態取樣&lt;/strong>：SDK 在收到 collector 的 HTTP 429 後自動降低取樣率，collector 恢復正常後逐步回升。動態取樣在正常情況下不生效（取樣率 = 1.0），只在 collector 過載時啟用。和靜態取樣互補 — 靜態控制基線負載，動態應對突發。&lt;/p>
&lt;h2 id="取樣校正">取樣校正&lt;/h2>
&lt;p>分析時用取樣率還原原始量級。取樣率 0.1 時收到 100 筆事件，推估原始量為 100 / 0.1 = 1000 筆。SDK 端的 &lt;code>sdk.sampling.rate&lt;/code> 指標記錄當前取樣率，讓下游分析知道如何校正。取樣校正對 funnel 和 cohort 分析有效（趨勢和比例不變），對個別事件追蹤無效（被丟棄的事件無法回復）。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>取樣承擔的設計責任是「在可觀測性覆蓋率和系統負載之間找到平衡」。Error 類事件不做取樣（每筆都可能是需要修的 bug），metric 類事件適合高比例取樣（丟幾筆不影響趨勢），event 類和 lifecycle 類取決於分析需求。&lt;/p>
&lt;h2 id="完整章節">完整章節&lt;/h2>
&lt;p>靜態取樣率的設定 → &lt;a href="https://tarrragon.github.io/blog/monitoring/03-sdk-design/sensor-lifecycle-management/" data-link-title="感測器生命週期管理" data-link-desc="產品生命週期的五個階段各啟用什麼感測器 — feature flag 整合、取樣率動態調整、感測器開關的可觀察性">感測器生命週期管理&lt;/a>。動態取樣在四層防線中的位置 → &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/ingestion-scaling/" data-link-title="Ingestion Scaling" data-link-desc="四層防線應對 ingestion 端的流量擴展 — SDK 取樣、Collector 背壓、水平擴展、Queue 解耦">Ingestion Scaling&lt;/a>。取樣造成的損失量化和控制 → &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/data-integrity/" data-link-title="端到端資料完整性" data-link-desc="從 SDK 到 storage 的資料損失地圖 — 每個環節的損失類型、控制策略、完整性指標、被自己 SDK DDoS 的防護">端到端資料完整性&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>取樣（sampling）的通用概念見 <a href="/blog/backend/knowledge-cards/sampling/" data-link-title="Sampling" data-link-desc="說明觀測資料如何抽樣以控制成本並保留診斷能力">Backend 知識卡：Sampling</a> — 只保留部分觀測資料以控制成本。本卡聚焦監控 SDK 中的具體實作：在事件產生階段按比例丟棄部分事件，降低後續管線（buffer → transport → collector → storage）的負載。取樣是設計內的損失 — 取樣率是明確的 config 參數，損失量可預測。可先對照 <a href="/blog/monitoring/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="下游處理能力不足時向上游回傳「慢下來」訊號的流量控制機制 — 監控系統中 collector 用 HTTP 429 向 SDK 傳遞背壓">backpressure</a>（觸發動態取樣的訊號來源）和 <a href="/blog/monitoring/knowledge-cards/rate-limiting/" data-link-title="Rate Limiting" data-link-desc="限制每個 client 在單位時間內可送出的事件數量 — 防止單一 SDK bug 或偽造流量消耗整個 collector 的處理能力">rate limiting</a>（collector 端的 per-client 限制）。</p>
<h2 id="兩種取樣">兩種取樣</h2>
<p><strong>靜態取樣</strong>：SDK config 中設定固定比例（例如 metric 類 0.1 = 每 10 筆只收 1 筆），在 SDK 整個生命週期保持不變。適合已知高頻但單筆 debug 價值低的事件（render.frame_time、scroll.position）。</p>
<p><strong>動態取樣</strong>：SDK 在收到 collector 的 HTTP 429 後自動降低取樣率，collector 恢復正常後逐步回升。動態取樣在正常情況下不生效（取樣率 = 1.0），只在 collector 過載時啟用。和靜態取樣互補 — 靜態控制基線負載，動態應對突發。</p>
<h2 id="取樣校正">取樣校正</h2>
<p>分析時用取樣率還原原始量級。取樣率 0.1 時收到 100 筆事件，推估原始量為 100 / 0.1 = 1000 筆。SDK 端的 <code>sdk.sampling.rate</code> 指標記錄當前取樣率，讓下游分析知道如何校正。取樣校正對 funnel 和 cohort 分析有效（趨勢和比例不變），對個別事件追蹤無效（被丟棄的事件無法回復）。</p>
<h2 id="設計責任">設計責任</h2>
<p>取樣承擔的設計責任是「在可觀測性覆蓋率和系統負載之間找到平衡」。Error 類事件不做取樣（每筆都可能是需要修的 bug），metric 類事件適合高比例取樣（丟幾筆不影響趨勢），event 類和 lifecycle 類取決於分析需求。</p>
<h2 id="完整章節">完整章節</h2>
<p>靜態取樣率的設定 → <a href="/blog/monitoring/03-sdk-design/sensor-lifecycle-management/" data-link-title="感測器生命週期管理" data-link-desc="產品生命週期的五個階段各啟用什麼感測器 — feature flag 整合、取樣率動態調整、感測器開關的可觀察性">感測器生命週期管理</a>。動態取樣在四層防線中的位置 → <a href="/blog/monitoring/04-collector/ingestion-scaling/" data-link-title="Ingestion Scaling" data-link-desc="四層防線應對 ingestion 端的流量擴展 — SDK 取樣、Collector 背壓、水平擴展、Queue 解耦">Ingestion Scaling</a>。取樣造成的損失量化和控制 → <a href="/blog/monitoring/04-collector/data-integrity/" data-link-title="端到端資料完整性" data-link-desc="從 SDK 到 storage 的資料損失地圖 — 每個環節的損失類型、控制策略、完整性指標、被自己 SDK DDoS 的防護">端到端資料完整性</a>。</p>
]]></content:encoded></item><item><title>感測器生命週期管理</title><link>https://tarrragon.github.io/blog/monitoring/03-sdk-design/sensor-lifecycle-management/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/03-sdk-design/sensor-lifecycle-management/</guid><description>&lt;p>感測器的啟用組合隨產品階段變化。早期開發只需要 error 和 lifecycle 幫助 debug，production 上線後需要商業事件和效能量測，A/B 測試期間需要實驗專用感測器。把所有感測器一次全開會浪費頻寬和儲存、產生大量低價值事件；全程只開 error 則在需要行為分析時發現沒有資料。感測器的啟停是設計決策，由 SDK config、collector 下發和 feature flag 三層機制控制。&lt;/p>
&lt;h2 id="五個階段">五個階段&lt;/h2>
&lt;h3 id="早期開發">早期開發&lt;/h3>
&lt;p>開發期的首要需求是 debug — 程式碼寫完跑起來、出問題時能定位。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>感測器類型&lt;/th>
 &lt;th>啟用&lt;/th>
 &lt;th>理由&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>error&lt;/td>
 &lt;td>全開&lt;/td>
 &lt;td>每個例外都要看到&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>lifecycle&lt;/td>
 &lt;td>全開&lt;/td>
 &lt;td>app 啟動、連線、狀態轉換的步驟紀錄&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>event&lt;/td>
 &lt;td>按需&lt;/td>
 &lt;td>正在開發的功能手動加埋點，其他關閉&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>metric&lt;/td>
 &lt;td>關閉&lt;/td>
 &lt;td>效能量測在功能穩定前沒有意義&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>開發期的取樣率全部設 1.0（全收）— 事件量極低（開發者自己操作），不需要取樣。&lt;/p>
&lt;h3 id="功能測試">功能測試&lt;/h3>
&lt;p>針對被測功能開啟完整感測器，驗證功能的行為事件和效能指標是否正確觸發。&lt;/p>
&lt;p>被測功能的 event 和 metric 全開。其他功能維持開發期設定。測試期間的感測器設定通常由測試 config 檔覆寫 SDK 預設值。&lt;/p>
&lt;h3 id="production-上線">Production 上線&lt;/h3>
&lt;p>上線後的感測器組合平衡覆蓋率和成本：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>感測器類型&lt;/th>
 &lt;th>策略&lt;/th>
 &lt;th>理由&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>error&lt;/td>
 &lt;td>全收&lt;/td>
 &lt;td>每個 production error 都有 debug 價值&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>lifecycle&lt;/td>
 &lt;td>全收&lt;/td>
 &lt;td>session 分析和環境資訊需要完整紀錄&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>event（核心操作）&lt;/td>
 &lt;td>全收&lt;/td>
 &lt;td>漏斗關鍵步驟、轉換事件不能漏&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>event（高頻 UI）&lt;/td>
 &lt;td>取樣&lt;/td>
 &lt;td>scroll、mousemove、hover 等高頻操作只取部分&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>metric&lt;/td>
 &lt;td>取樣&lt;/td>
 &lt;td>效能指標按時間取樣（每 30 秒一次而非每 frame）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>安全事件&lt;/td>
 &lt;td>全收&lt;/td>
 &lt;td>auth 失敗、權限越界、敏感操作不取樣&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="ab-測試">A/B 測試&lt;/h3>
&lt;p>實驗感測器只對 treatment group 啟用。Control group 不觸發實驗事件，避免污染對照組資料。&lt;/p>
&lt;p>實驗專用事件（&lt;code>experiment.pricing_test.assigned&lt;/code>、&lt;code>experiment.pricing_test.converted&lt;/code>）由 feature flag 控制 — flag 開啟時 SDK 才送這些事件。實驗結束後 flag 關閉，感測器自動停止。&lt;/p>
&lt;p>實驗事件的保留期和實驗週期綁定，實驗結束 + 分析完成後可以 purge。&lt;/p>
&lt;h3 id="功能下線">功能下線&lt;/h3>
&lt;p>功能移除時，對應的感測器 config 一起移除。Collector 端 purge 該功能的歷史事件（或降級到聚合摘要）。&lt;/p>
&lt;p>移除 checklist：SDK config 移除事件名稱 → SDK 版本部署 → 確認 collector 不再收到該事件 → purge 歷史資料（可選）。&lt;/p>
&lt;h2 id="控制機制">控制機制&lt;/h2>
&lt;p>三層控制機制各自適合不同的變更頻率：&lt;/p>
&lt;h3 id="sdk-init-config靜態">SDK init config（靜態）&lt;/h3>
&lt;p>隨 app 版本部署的本地設定檔。變更需要發新版本。適合穩定的感測器組合。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="nt">sensors&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">error&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled: true, sampling&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1.0&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">lifecycle&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled: true, sampling&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1.0&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">event&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">funnel.*&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled: true, sampling&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1.0&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">click.*&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled: true, sampling&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.1&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">metric&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">duration&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled: true, sampling&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.5&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">experiment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pricing_test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w"> &lt;/span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="collector-端下發動態">Collector 端下發（動態）&lt;/h3>
&lt;p>SDK 啟動時從 collector 的 &lt;code>/config&lt;/code> endpoint 拉取當前的感測器設定。Collector 端修改設定後，下一次 SDK 重啟或定期 refresh（每 5 分鐘）時生效。適合需要動態調整但不值得接 feature flag 服務的場景。&lt;/p></description><content:encoded><![CDATA[<p>感測器的啟用組合隨產品階段變化。早期開發只需要 error 和 lifecycle 幫助 debug，production 上線後需要商業事件和效能量測，A/B 測試期間需要實驗專用感測器。把所有感測器一次全開會浪費頻寬和儲存、產生大量低價值事件；全程只開 error 則在需要行為分析時發現沒有資料。感測器的啟停是設計決策，由 SDK config、collector 下發和 feature flag 三層機制控制。</p>
<h2 id="五個階段">五個階段</h2>
<h3 id="早期開發">早期開發</h3>
<p>開發期的首要需求是 debug — 程式碼寫完跑起來、出問題時能定位。</p>
<table>
  <thead>
      <tr>
          <th>感測器類型</th>
          <th>啟用</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>error</td>
          <td>全開</td>
          <td>每個例外都要看到</td>
      </tr>
      <tr>
          <td>lifecycle</td>
          <td>全開</td>
          <td>app 啟動、連線、狀態轉換的步驟紀錄</td>
      </tr>
      <tr>
          <td>event</td>
          <td>按需</td>
          <td>正在開發的功能手動加埋點，其他關閉</td>
      </tr>
      <tr>
          <td>metric</td>
          <td>關閉</td>
          <td>效能量測在功能穩定前沒有意義</td>
      </tr>
  </tbody>
</table>
<p>開發期的取樣率全部設 1.0（全收）— 事件量極低（開發者自己操作），不需要取樣。</p>
<h3 id="功能測試">功能測試</h3>
<p>針對被測功能開啟完整感測器，驗證功能的行為事件和效能指標是否正確觸發。</p>
<p>被測功能的 event 和 metric 全開。其他功能維持開發期設定。測試期間的感測器設定通常由測試 config 檔覆寫 SDK 預設值。</p>
<h3 id="production-上線">Production 上線</h3>
<p>上線後的感測器組合平衡覆蓋率和成本：</p>
<table>
  <thead>
      <tr>
          <th>感測器類型</th>
          <th>策略</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>error</td>
          <td>全收</td>
          <td>每個 production error 都有 debug 價值</td>
      </tr>
      <tr>
          <td>lifecycle</td>
          <td>全收</td>
          <td>session 分析和環境資訊需要完整紀錄</td>
      </tr>
      <tr>
          <td>event（核心操作）</td>
          <td>全收</td>
          <td>漏斗關鍵步驟、轉換事件不能漏</td>
      </tr>
      <tr>
          <td>event（高頻 UI）</td>
          <td>取樣</td>
          <td>scroll、mousemove、hover 等高頻操作只取部分</td>
      </tr>
      <tr>
          <td>metric</td>
          <td>取樣</td>
          <td>效能指標按時間取樣（每 30 秒一次而非每 frame）</td>
      </tr>
      <tr>
          <td>安全事件</td>
          <td>全收</td>
          <td>auth 失敗、權限越界、敏感操作不取樣</td>
      </tr>
  </tbody>
</table>
<h3 id="ab-測試">A/B 測試</h3>
<p>實驗感測器只對 treatment group 啟用。Control group 不觸發實驗事件，避免污染對照組資料。</p>
<p>實驗專用事件（<code>experiment.pricing_test.assigned</code>、<code>experiment.pricing_test.converted</code>）由 feature flag 控制 — flag 開啟時 SDK 才送這些事件。實驗結束後 flag 關閉，感測器自動停止。</p>
<p>實驗事件的保留期和實驗週期綁定，實驗結束 + 分析完成後可以 purge。</p>
<h3 id="功能下線">功能下線</h3>
<p>功能移除時，對應的感測器 config 一起移除。Collector 端 purge 該功能的歷史事件（或降級到聚合摘要）。</p>
<p>移除 checklist：SDK config 移除事件名稱 → SDK 版本部署 → 確認 collector 不再收到該事件 → purge 歷史資料（可選）。</p>
<h2 id="控制機制">控制機制</h2>
<p>三層控制機制各自適合不同的變更頻率：</p>
<h3 id="sdk-init-config靜態">SDK init config（靜態）</h3>
<p>隨 app 版本部署的本地設定檔。變更需要發新版本。適合穩定的感測器組合。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">sensors</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">  </span><span class="nt">error</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">enabled: true, sampling</span><span class="p">:</span><span class="w"> </span><span class="m">1.0</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="nt">lifecycle</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">enabled: true, sampling</span><span class="p">:</span><span class="w"> </span><span class="m">1.0</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="nt">event</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nt">funnel.*</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">enabled: true, sampling</span><span class="p">:</span><span class="w"> </span><span class="m">1.0</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">click.*</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">enabled: true, sampling</span><span class="p">:</span><span class="w"> </span><span class="m">0.1</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">  </span><span class="nt">metric</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="nt">duration</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">enabled: true, sampling</span><span class="p">:</span><span class="w"> </span><span class="m">0.5</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">  </span><span class="nt">experiment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="nt">pricing_test</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span>}</span></span></code></pre></div><h3 id="collector-端下發動態">Collector 端下發（動態）</h3>
<p>SDK 啟動時從 collector 的 <code>/config</code> endpoint 拉取當前的感測器設定。Collector 端修改設定後，下一次 SDK 重啟或定期 refresh（每 5 分鐘）時生效。適合需要動態調整但不值得接 feature flag 服務的場景。</p>
<p>MVP 階段跳過 collector 下發，只用 SDK 本地 config。下發 API 的定義和實作標為第二階段 — 感測器的開關在 SDK 本地 config 已經能完全控制。</p>
<h3 id="feature-flag-服務整合">Feature flag 服務整合</h3>
<p>SDK 在送出事件前查詢 feature flag 判斷感測器是否啟用。適合 A/B 測試 — flag 可以按使用者 / 百分比 / 條件分群啟用。</p>
<h3 id="優先順序">優先順序</h3>
<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">Feature flag &gt; Collector 下發 &gt; SDK 本地 config</span></span></code></pre></div><p>SDK 本地 config 是 baseline。Collector 下發覆蓋 baseline 的特定欄位。Feature flag 覆蓋一切 — 即使本地 config 和 collector 都說啟用，flag 說關閉就關閉。</p>
<h2 id="取樣率設計">取樣率設計</h2>
<p>取樣率決定「多少比例的事件會被實際送出」。取樣在 SDK 端執行 — 不送的事件不佔頻寬和儲存。</p>
<h3 id="全收sampling-10">全收（sampling: 1.0）</h3>
<p>每筆事件都送。適用於：</p>
<ul>
<li><strong>error</strong>：每個 production error 都有 debug 價值，漏掉的 error 可能是最嚴重的那個</li>
<li><strong>安全事件</strong>：auth 失敗、權限越界的取樣可能讓攻擊嘗試隱形</li>
<li><strong>漏斗關鍵步驟</strong>：funnel 分析的轉換率計算需要精確的步驟計數</li>
</ul>
<h3 id="百分比取樣001-05">百分比取樣（0.01-0.5）</h3>
<p>只送一定比例的事件。適用於高頻且個別事件價值低的場景：</p>
<ul>
<li>scroll / mousemove / hover：每秒觸發數十次，全收會產生大量事件。取樣 1-10% 足以分析使用者行為模式</li>
<li>frame rate 量測：每幀一筆 metric 太多，每秒或每 30 秒取一筆足夠</li>
</ul>
<p>取樣的實作用 SDK 端的隨機數 — <code>if random() &lt; sampling_rate then send(event)</code> — 不需要 server 端參與。</p>
<h3 id="條件取樣retrospective-full-capture">條件取樣（retrospective full capture）</h3>
<p>正常情況取樣，但發生 error 時回溯收集該 session 的全部事件。實作方式是 SDK 在記憶體中保留最近 N 筆事件的環形 buffer，觸發 error 時把 buffer 中的事件一併送出。</p>
<p>條件取樣讓「error session 的上下文完整」和「正常 session 不過度收集」兩個目標共存。</p>
<h2 id="感測器開關的可觀察性">感測器開關的可觀察性</h2>
<p>感測器本身的狀態變化需要被觀察 — 如果感測器靜默失效（config 錯誤導致某類事件停送），開發者可能很久後才發現「怎麼最近沒有 funnel 資料」。</p>
<h3 id="啟動時-log-感測器清單">啟動時 log 感測器清單</h3>
<p>SDK 初始化完成時 log 當前啟用的感測器清單和取樣率。開發者在 debug console 就能看到「哪些感測器在跑」。</p>
<h3 id="config-變更事件">Config 變更事件</h3>
<p>感測器 config 變更時（collector 下發新 config、或 feature flag 變化），SDK 送一個 lifecycle 事件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;lifecycle&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;sensor.config.changed&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;collector_push&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nt">&#34;changed&#34;</span><span class="p">:</span> <span class="p">{</span><span class="nt">&#34;click.*&#34;</span><span class="p">:</span> <span class="p">{</span><span class="nt">&#34;sampling&#34;</span><span class="p">:</span> <span class="s2">&#34;0.1 → 0.05&#34;</span><span class="p">}},</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="nt">&#34;active_sensors&#34;</span><span class="p">:</span> <span class="mi">12</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這筆事件讓開發者在查詢時能看到「某個時間點感測器 config 改變了」，和事件量的變化做交叉比對。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>感測器偵測哪些行為 → <a href="/blog/monitoring/03-sdk-design/frontend-sensor-design/" data-link-title="前端感測器設計" data-link-desc="什麼行為值得埋感測器、每類感測器的實作方式、取樣策略和效能影響 — 和 auto-intercept 的被動攔截互補">前端感測器設計</a></li>
<li>SDK 的公開 API → <a href="/blog/monitoring/03-sdk-design/public-api/" data-link-title="SDK 公開 API 設計" data-link-desc="init / event / error / metric / flush / close 六個方法構成 SDK 的完整生命週期 — 跨平台共用相同 API 介面">SDK 公開 API 設計</a></li>
<li>四類事件的定義 → <a href="/blog/monitoring/01-mental-model/four-event-types/" data-link-title="四類事件的完整定義" data-link-desc="Event / Error / Metric / Lifecycle 四類事件各自的語意、觸發時機和典型用途 — 分類是監控體系的統一語言">四類事件的完整定義</a></li>
<li>事件枚舉方法 → <a href="/blog/monitoring/01-mental-model/event-enumeration-method/" data-link-title="事件枚舉與補齊檢查" data-link-desc="從操作盤點系統性地推導出完整的事件清單 — 四類補齊檢查確保沒有遺漏、粒度判準確保每個事件只記一個事實">事件枚舉與補齊檢查</a></li>
</ul>
]]></content:encoded></item><item><title>3.10 Constrained decoding 內部：grammar mask 跟性能取捨</title><link>https://tarrragon.github.io/blog/llm/03-theoretical-foundations/constrained-decoding-internals/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/03-theoretical-foundations/constrained-decoding-internals/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/sampling-and-decoding/" data-link-title="3.5 Sampling 與 Decoding 策略" data-link-desc="Greedy、beam search、top-k、top-p、temperature、min-p：模型輸出後怎麼挑下一個 token">3.5 sampling-and-decoding&lt;/a> 寫了 greedy / beam / top-p / top-k sampling、是「在合法輸出中選下一個 token」的基本機制。&lt;a href="https://tarrragon.github.io/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 application-protocols&lt;/a> 寫了 function calling / structured output 的應用層 — 但「為什麼 LLM 能保證輸出合法 JSON」這層原理在前兩章都沒展開。本章補 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding&lt;/a> 的內部機制：token mask 怎麼算、JSON schema / regex / &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/grammar/" data-link-title="Grammar" data-link-desc="描述合法字串形狀的形式規則，在 structured output 中用來限制 LLM 每一步可輸出的 token">CFG&lt;/a> 三種 grammar、為什麼 XGrammar 等實作反而加速生成。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>解釋「grammar 強制」是在 sampling 階段哪一步做的。&lt;/li>
&lt;li>區分 JSON schema / regex / CFG 三種 grammar 的適用場景。&lt;/li>
&lt;li>看 XGrammar / outlines / llama.cpp grammar 等實作、能對應到本章 framing。&lt;/li>
&lt;li>判讀「constrained decoding 加速還是拖慢」的具體場景。&lt;/li>
&lt;/ol>
&lt;h2 id="sampling-階段的位置">Sampling 階段的位置&lt;/h2>
&lt;p>回顧 LLM 輸出流程（見 &lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/sampling-and-decoding/" data-link-title="3.5 Sampling 與 Decoding 策略" data-link-desc="Greedy、beam search、top-k、top-p、temperature、min-p：模型輸出後怎麼挑下一個 token">3.5&lt;/a>）：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">[forward pass] → logits（vocab_size 維、每個 token 一個實數）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓ apply temperature（logits / T）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> ↓ apply constrained decoding（本章聚焦） ← grammar mask
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓ softmax → probability distribution
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> ↓ top-p / top-k / sampling
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ↓ next token&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Constrained decoding 在 softmax &lt;strong>之前&lt;/strong>插入 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">grammar mask&lt;/a>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">For each position：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> 1. Grammar 算當前位置的「合法 token 集合」（vocab 子集）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> 2. 對不在合法集的 token、logit 設 -∞
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> 3. Softmax 後、不合法 token 機率為 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> 4. Sampling 只可能選到合法 token&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>關鍵理解：grammar 不改變模型本身、不改變 logits 數值（除了 mask 部分）、只是&lt;strong>限制 sampling 空間&lt;/strong>。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/llm/03-theoretical-foundations/sampling-and-decoding/" data-link-title="3.5 Sampling 與 Decoding 策略" data-link-desc="Greedy、beam search、top-k、top-p、temperature、min-p：模型輸出後怎麼挑下一個 token">3.5 sampling-and-decoding</a> 寫了 greedy / beam / top-p / top-k sampling、是「在合法輸出中選下一個 token」的基本機制。<a href="/blog/llm/04-applications/application-protocols/" data-link-title="4.6 應用層協議：function calling / structured output / MCP" data-link-desc="三個常被混為一談的概念：模型能力、sampling 約束、server 協議，三者的層級差異與組合方式">4.6 application-protocols</a> 寫了 function calling / structured output 的應用層 — 但「為什麼 LLM 能保證輸出合法 JSON」這層原理在前兩章都沒展開。本章補 <a href="/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">constrained decoding</a> 的內部機制：token mask 怎麼算、JSON schema / regex / <a href="/blog/llm/knowledge-cards/grammar/" data-link-title="Grammar" data-link-desc="描述合法字串形狀的形式規則，在 structured output 中用來限制 LLM 每一步可輸出的 token">CFG</a> 三種 grammar、為什麼 XGrammar 等實作反而加速生成。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>解釋「grammar 強制」是在 sampling 階段哪一步做的。</li>
<li>區分 JSON schema / regex / CFG 三種 grammar 的適用場景。</li>
<li>看 XGrammar / outlines / llama.cpp grammar 等實作、能對應到本章 framing。</li>
<li>判讀「constrained decoding 加速還是拖慢」的具體場景。</li>
</ol>
<h2 id="sampling-階段的位置">Sampling 階段的位置</h2>
<p>回顧 LLM 輸出流程（見 <a href="/blog/llm/03-theoretical-foundations/sampling-and-decoding/" data-link-title="3.5 Sampling 與 Decoding 策略" data-link-desc="Greedy、beam search、top-k、top-p、temperature、min-p：模型輸出後怎麼挑下一個 token">3.5</a>）：</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">[forward pass] → logits（vocab_size 維、每個 token 一個實數）
</span></span><span class="line"><span class="ln">2</span><span class="cl">       ↓ apply temperature（logits / T）
</span></span><span class="line"><span class="ln">3</span><span class="cl">       ↓ apply constrained decoding（本章聚焦）  ← grammar mask
</span></span><span class="line"><span class="ln">4</span><span class="cl">       ↓ softmax → probability distribution
</span></span><span class="line"><span class="ln">5</span><span class="cl">       ↓ top-p / top-k / sampling
</span></span><span class="line"><span class="ln">6</span><span class="cl">       ↓ next token</span></span></code></pre></div><p>Constrained decoding 在 softmax <strong>之前</strong>插入 <a href="/blog/llm/knowledge-cards/constrained-decoding/" data-link-title="Constrained Decoding" data-link-desc="推論時用 grammar 強制 LLM 輸出符合特定格式（JSON / regex / CFG）的 sampling 機制、把不合法 token 的機率歸零">grammar mask</a>：</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">For each position：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  1. Grammar 算當前位置的「合法 token 集合」（vocab 子集）
</span></span><span class="line"><span class="ln">3</span><span class="cl">  2. 對不在合法集的 token、logit 設 -∞
</span></span><span class="line"><span class="ln">4</span><span class="cl">  3. Softmax 後、不合法 token 機率為 0
</span></span><span class="line"><span class="ln">5</span><span class="cl">  4. Sampling 只可能選到合法 token</span></span></code></pre></div><p>關鍵理解：grammar 不改變模型本身、不改變 logits 數值（除了 mask 部分）、只是<strong>限制 sampling 空間</strong>。</p>
<h2 id="三種主流-grammar">三種主流 grammar</h2>
<h3 id="json-schema">JSON Schema</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;object&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nt">&#34;properties&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="p">{</span><span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;string&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nt">&#34;age&#34;</span><span class="p">:</span> <span class="p">{</span><span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;integer&#34;</span><span class="p">,</span> <span class="nt">&#34;minimum&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="nt">&#34;required&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;name&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>LLM 輸出必須是合法 JSON 且符合 schema。實作：</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">當前已生：&#39;{&#34;name&#34;: &#34;alice&#34;, &#39;
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ↓ 算下一個合法 token：
</span></span><span class="line"><span class="ln">3</span><span class="cl">  - 必須繼續產合法 JSON
</span></span><span class="line"><span class="ln">4</span><span class="cl">  - schema 還沒填 age（optional）但 name 已填、所以 } 合法、&#34;age&#34; 也合法
</span></span><span class="line"><span class="ln">5</span><span class="cl">  - 不合法：&#39;{&#39; / &#39;]&#39; / 任意其他 key
</span></span><span class="line"><span class="ln">6</span><span class="cl">  ↓ Token mask 套用
</span></span><span class="line"><span class="ln">7</span><span class="cl">  → 模型只能選 } 或 &#34;age&#34;</span></span></code></pre></div><h3 id="regex">Regex</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">\d{3}-\d{4}-\d{4}  # 台灣 phone number 格式</span></span></code></pre></div><p>LLM 輸出必須符合 regex。實作：</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">當前已生：&#39;09&#39;
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ↓ 算下一個合法 token：
</span></span><span class="line"><span class="ln">3</span><span class="cl">  - regex 期望 \d 接下來
</span></span><span class="line"><span class="ln">4</span><span class="cl">  - 合法 token：&#39;0&#39;-&#39;9&#39; 開頭的 token
</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">  ↓ Token mask</span></span></code></pre></div><h3 id="cfgcontext-free-grammar">CFG（Context-Free Grammar）</h3>
<p>用 <a href="/blog/llm/knowledge-cards/bnf/" data-link-title="BNF（Backus-Naur Form）" data-link-desc="用遞迴產生式描述語法的經典記法，是 CFG、parser 與 grammar-constrained sampling 常見的基礎表示">BNF</a> / EBNF 描述合法語法：</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">expr   ::= term (&#34;+&#34; term)*
</span></span><span class="line"><span class="ln">2</span><span class="cl">term   ::= number | &#34;(&#34; expr &#34;)&#34;
</span></span><span class="line"><span class="ln">3</span><span class="cl">number ::= [0-9]+</span></span></code></pre></div><p>LLM 輸出必須符合此 grammar。實作：</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">當前已生：&#39;(1+2&#39;
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ↓ CFG 算當下合法 next token：
</span></span><span class="line"><span class="ln">3</span><span class="cl">  - 已 match 部分 term + &#34;+&#34; + term
</span></span><span class="line"><span class="ln">4</span><span class="cl">  - 合法：&#34;)&#34; 或 &#34;+&#34; 開始新 term
</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">  ↓ Token mask</span></span></code></pre></div><p>CFG 是最強表達力、但實作最複雜。SQL / 程式碼 generation 多用 CFG-based grammar。</p>
<h2 id="xgrammar-的-pre-compile-機制">XGrammar 的 pre-compile 機制</h2>
<p>XGrammar（Dong et al., 2024）是 2024-2025 主流的高效實作。核心優化：</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">Naive 實作（如 outlines 早期版）：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  每次 sampling 都重算 grammar state
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  每個 token 都跑一次 grammar parse
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  → 開銷大、可能拖慢 generation
</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">XGrammar 優化：
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  1. Pre-compile grammar → 確定性 DFA / push-down automaton
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  2. Cache 每個 grammar state 的「合法 token mask bitmap」
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  3. Sampling 時 O(1) 查表得到 mask
</span></span><span class="line"><span class="ln">10</span><span class="cl">  4. Mask 用 bitwise op 套用到 logits</span></span></code></pre></div><p>效果：grammar 套用 overhead 趨近 0、甚至<strong>因為跳過 boilerplate token 反而加速</strong>：</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">無 grammar 生 JSON：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  {     &#34; n a m e &#34;     : &#34; a l i c e &#34; ...
</span></span><span class="line"><span class="ln">3</span><span class="cl">  ←     每個 token 都跑 forward pass    →
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">有 grammar 生 JSON：
</span></span><span class="line"><span class="ln">6</span><span class="cl">  跳過固定 token（{ &#34; : 等）、直接生關鍵字串
</span></span><span class="line"><span class="ln">7</span><span class="cl">  forward pass 次數減少
</span></span><span class="line"><span class="ln">8</span><span class="cl">  → 實測加速 1.5-3×</span></span></code></pre></div><p>主流推論伺服器（vLLM、SGLang、TensorRT-LLM）2025 後預設用 XGrammar。</p>
<h2 id="性能取捨加速還是拖慢">性能取捨：加速還是拖慢</h2>
<p>常見誤解：「constrained decoding 拖慢生成」。實際看實作：</p>
<table>
  <thead>
      <tr>
          <th>實作</th>
          <th>性能</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>XGrammar（vLLM 等預設）</td>
          <td><strong>加速 1.5-3×</strong>（跳過固定 token、forward pass 次數減）</td>
      </tr>
      <tr>
          <td>outlines（pre-compiled）</td>
          <td>略加速到中性</td>
      </tr>
      <tr>
          <td>outlines（lazy compile）</td>
          <td>略拖慢</td>
      </tr>
      <tr>
          <td>guidance（高階 API）</td>
          <td>中性到略拖慢</td>
      </tr>
      <tr>
          <td>llama.cpp grammar</td>
          <td>中性</td>
      </tr>
      <tr>
          <td>Lazy / naive 實作</td>
          <td>拖慢</td>
      </tr>
  </tbody>
</table>
<p>判讀：用主流推論伺服器（vLLM / SGLang）+ XGrammar 路線、constrained decoding 通常加速；自己寫 naive 實作可能拖慢。</p>
<h2 id="跟-function-calling-的關係">跟 <a href="/blog/llm/knowledge-cards/function-calling/" data-link-title="Function Calling" data-link-desc="模型訓練階段建立的「呼叫工具」能力：知道何時該呼叫、傳什麼參數">function calling</a> 的關係</h2>
<p>兩個概念可獨立、也可疊用：</p>
<table>
  <thead>
      <tr>
          <th>路線</th>
          <th>機制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Pure function calling（無 constrained decoding）</td>
          <td>靠模型訓練、不強制合法、可能有解析失敗</td>
      </tr>
      <tr>
          <td>Pure constrained decoding（無 function calling 訓練）</td>
          <td>推論時強制合法、但模型不一定知道「何時該呼叫工具」</td>
      </tr>
      <tr>
          <td>Function calling + constrained decoding</td>
          <td>訓練教模型何時呼叫、grammar 強制呼叫格式合法</td>
      </tr>
  </tbody>
</table>
<p>主流商業 API（Anthropic / OpenAI / Gemini）的 function calling 通常<strong>內部已用 constrained decoding</strong>、開發者無感。本地推論用 vLLM / SGLang + XGrammar 也是預設組合。</p>
<h2 id="失敗模式">失敗模式</h2>
<h3 id="1-grammar-太嚴讓模型該說的話說不出來">1. Grammar 太嚴讓模型「該說的話說不出來」</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">Schema 強制 type 是 enum [&#34;A&#34;, &#34;B&#34;, &#34;C&#34;]
</span></span><span class="line"><span class="ln">2</span><span class="cl">但真實答案是「none of the above」
</span></span><span class="line"><span class="ln">3</span><span class="cl">→ 模型強制選 A/B/C、輸出語義錯誤</span></span></code></pre></div><p><strong>緩解</strong>：enum 加 fallback option（&ldquo;unknown&rdquo; / &ldquo;none&rdquo;）、schema 別過度約束</p>
<h3 id="2-cfg-太複雜編譯失敗--慢">2. CFG 太複雜、編譯失敗 / 慢</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">復雜 CFG（如完整 SQL grammar）pre-compile 數秒
</span></span><span class="line"><span class="ln">2</span><span class="cl">production cold start 多花這數秒</span></span></code></pre></div><p><strong>緩解</strong>：cache compiled grammar、用較簡單 grammar 版本（如「INSERT only」而非完整 SQL）</p>
<h3 id="3-grammar-跟-model-訓練分佈不符">3. Grammar 跟 model 訓練分佈不符</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">Schema 要求很罕見的 JSON 結構
</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">即使 grammar 強制合法、語義可能空洞</span></span></code></pre></div><p><strong>緩解</strong>：grammar 用模型訓練過的形態（function call spec、common JSON）、自定義 schema 加 few-shot example</p>
<h3 id="4-streaming-跟-grammar-衝突">4. Streaming 跟 grammar 衝突</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">Streaming 邊生邊輸出
</span></span><span class="line"><span class="ln">2</span><span class="cl">Grammar 中段 token 可能要 backtrack 修正
</span></span><span class="line"><span class="ln">3</span><span class="cl">streaming UX 跳字</span></span></code></pre></div><p><strong>緩解</strong>：用 incremental-parsing grammar（XGrammar 支援）、避免 backtrack 場景</p>
<h3 id="5-constrained-decoding-蓋過-function-calling-訓練">5. Constrained decoding 蓋過 function calling 訓練</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">模型訓練用 OpenAI function spec、應用強制套 Anthropic tools 的 grammar
</span></span><span class="line"><span class="ln">2</span><span class="cl">模型輸出「合法但語意空洞」（schema 對、欄位胡亂填）</span></span></code></pre></div><p><strong>緩解</strong>：grammar spec 跟模型訓練 spec 一致、別人工維護兩份不同 schema</p>
<h2 id="何時不該用-constrained-decoding">何時不該用 constrained decoding</h2>
<ol>
<li><strong>自由 / 創意輸出</strong>：寫作、brainstorming、grammar 限制模型表達</li>
<li><strong>可靠的 model + simple format</strong>：模型本身能穩定輸出 JSON、grammar overhead 不必要</li>
<li><strong>Grammar 太嚴有語義錯</strong>：見失敗模式 1</li>
<li><strong>Streaming + 複雜 grammar</strong>：streaming UX 受影響</li>
</ol>
<h2 id="主流實作詳細">主流實作詳細</h2>
<table>
  <thead>
      <tr>
          <th>實作</th>
          <th>適合場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>XGrammar</strong></td>
          <td>Production 高吞吐（vLLM / SGLang / TensorRT-LLM 預設）</td>
      </tr>
      <tr>
          <td><strong>outlines</strong></td>
          <td>Python script、開發 / 實驗、HF Transformers 用</td>
      </tr>
      <tr>
          <td><strong>lm-format-enforcer</strong></td>
          <td>動態 grammar、運行時切 schema</td>
      </tr>
      <tr>
          <td><strong>guidance</strong></td>
          <td>Microsoft 系、想要 high-level API</td>
      </tr>
      <tr>
          <td><strong>llama.cpp grammar</strong></td>
          <td>本地 GGUF 模型、GBNF 語法</td>
      </tr>
      <tr>
          <td><strong>OpenAI Structured Outputs</strong></td>
          <td>OpenAI API、JSON schema、開發者無感</td>
      </tr>
      <tr>
          <td><strong>Anthropic JSON mode</strong></td>
          <td>Anthropic API、簡化版</td>
      </tr>
  </tbody>
</table>
<h2 id="何時過時--何時不過時">何時過時 / 何時不過時</h2>
<p><strong>不會過時的部分</strong>：</p>
<ul>
<li>Constrained decoding 在 sampling 哪一步插入（softmax 之前）的 framing</li>
<li>三種 grammar 類型（JSON schema / regex / CFG）的分類</li>
<li>Token mask 機制（不合法 token logit 設 -∞）</li>
<li>「正確實作下加速、不是拖慢」的反直覺結論</li>
<li>5 大失敗模式分類</li>
</ul>
<p><strong>會變的部分</strong>：</p>
<ul>
<li>XGrammar / outlines 等實作的具體效能跟功能</li>
<li>主流推論伺服器的預設 grammar engine</li>
<li>JSON schema spec 標準化（新版會出）</li>
<li>Function calling + constrained decoding 是否會被 native multimodal 取代</li>
</ul>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/03-theoretical-foundations/going-deeper-theory/" data-link-title="3.11 想學更深：推薦公開課程" data-link-desc="Karpathy、Stanford CS224N / CS25 / CS336、DeepLearning.AI、Hugging Face：LLM 理論深入學習的完整路線">3.11 想學更深</a>、整個模組三理論基礎走完。</p>
]]></content:encoded></item></channel></rss>