localaiforum
登入
指南 › 基礎概念

預填與解碼:生成如何運作

按下 Enter 之後,生成分為兩個物理特性完全不同的階段。預填(prefill)用一次平行運算處理你整個提示詞——運算密集、單位 token 很快,但第一個字出現前你得等它全部跑完。接著解碼(decode)一次吐出一個 token——每一步都要從記憶體重新讀取幾乎全部的模型權重,只為了產出一個 token。「第一個 token 慢、後面才開始串流」不是你的環境有怪癖;這就是這兩個階段。

第一階段:預填——提示詞

  • 所有提示詞 token 同時通過模型:大型平行矩陣乘法,正是 GPU 為之而生的運算。使用率高;每秒 token 數也高。
  • 你感受到的是首 token 時間(TTFT):按下 Enter 後的那段沉默。
  • 這一趟也把每個提示詞 token 的鍵/值向量填進 KV 快取——也就是解碼階段要讀的草稿本。
  • 提示詞越長,TTFT 線性越長:大型系統提示詞、RAG 文件與對話歷史,除非引擎有快取前綴,每次都要重新預填。

第二階段:解碼——回答

  • 每次前向計算產出一個 token。那次計算需要所有權重;產生單一 token 意味著把整個模型從 VRAM 串流讀一遍。
  • 上限是記憶體頻寬而非算力:大致上解碼 tok/s ≈ 有效頻寬 ÷ 權重位元組數。8 GB 權重在有效頻寬約 400 GB/s 的卡上 ≈ 50 tok/s 的上限。
  • 這就是量化能加速解碼的原因:每個權重的位元數越少,每一步要串流的位元組就越少(見 速度)。

為什麼有這種不對稱:預填每次讀權重能處理數千個 token(巨大矩陣、算術強度高——運算受限);解碼恰好只處理一個(每個位元組的權重只為單一 token 而取——頻寬受限)。同樣的權重,相反的瓶頸。

total_time ≈ prompt_tokens / prefill_tok_s  +  output_tokens / decode_tok_s

實例

prompt:  1,000 tokens    prefill 2,000 tok/s → 0.5 s   (TTFT)
output:    300 tokens    decode    40 tok/s → 7.5 s
total ≈ 8.0 s — 94% of wall-clock is decode
  • 前半秒處理了 1,000 個 token;接下來 7.5 秒產出 300 個。同一個模型、同一張 GPU、50 倍的差距——是階段不同,不是努力不同。
  • 40 tok/s 約等於每秒 160 個英文字元(≈4 字元/token):比任何人的打字速度都快,這就是串流讀起來很順的原因(token 與分詞器)。
  • 同樣的 40 tok/s 換成韓文大約是每秒 40–60 字元:token 速率相同,每個 token 的文字更少。
  • 輸出長度是解碼端的旋鈕:max_tokens 限制生成長度,停止序列可以提早結束。兩者都線性改變實際耗時(引擎參數)。
  • 串流改變的是感受,不是物理:你是在看著解碼發生,而不是在等它,速度仍是同樣的 40 tok/s。

實例二:RAG 場景

prompt: 12,000 tokens (system + 20 retrieved chunks)  prefill 1,500 tok/s → 8.0 s
output:   500 tokens                                    decode    40 tok/s → 12.5 s
total ≈ 20.5 s — TTFT alone is 39% of the wait

這就是重度檢索的本機環境的樣貌:肥大的提示詞意味著模型在開始打字前要「想」個幾秒。解法按效果排序:

  • 少檢索一點——前 3–5 塊通常勝過前 20 塊(RAG)。
  • 保持系統提示詞穩定,讓引擎能重用快取的前綴。
  • 修剪歷史:舊輪次可以改成摘要,而不是完整重播。

KV 快取:不斷長大的草稿本

沒有快取的話,產生第 N 個 token 就要從頭對之前 N−1 個 token 重算注意力——每一步都在重做相同的工作,總成本與長度成平方關係。實際的做法:

  1. 預填為所有提示詞 token 計算鍵與值,並只存一次。
  2. 每個解碼步驟只計算新 token 的查詢/鍵/值,並把它的 K/V 附加到快取。
  3. 新 token 的注意力,就是它的查詢與所有已存鍵的內積。
  4. 解碼一個 token 的順序:嵌入、跑各層並讀取快取、投影到詞彙表、取樣——附加它的 K/V、重複。

現在每個解碼步驟的成本與上下文長度成線性(讀快取)而不是平方(全部重算)。代價是快取本身,它隨每個 token 增長:

KV bytes ≈ 2 × layers × kv_heads × head_dim × bytes_per_value × context

8B-class model (32 layers, 8 KV heads, 128 head dim, FP16):
  2 × 32 × 8 × 128 × 2 B ≈ 128 KiB per token
  → ~2.6 GB at 20k context, ~17 GB at 128k context

這份快取會與權重爭搶 VRAM——長上下文對話能讓短提示詞時跑得好好的同一台機器當掉(KV 快取、VRAM 用量計算)。

批次處理改變經濟學

讀 8 GB 的權重只為吐出一個 token 很浪費;同樣的讀取可以一次服務多個序列。把 8 個請求打包成批次,一次權重串流就能產出 8 個 token——吞吐量近乎線性上升,直到算力飽和。這就是服務部署背後的全部技巧(端點服務、並行處理)。獨自在筆電上,你為每個 token 付全額;伺服器把它分攤給同時上線的多個使用者。

解讀儀表

儀表 階段 主要驅動因素
TTFT 預填 提示詞長度、算力、前綴快取命中
提示詞評估 tok/s 預填 算力、批次大小
生成 tok/s 解碼 記憶體頻寬、權重位元組數、批次
吞吐量(req/s) 兩者 批次大小、前綴快取重用、平行插槽
上下文記憶體 兩者 每 token 的 KV 位元組數、快取量化

調校前先測量:每個認真的引擎都會在日誌記錄每個請求的提示詞評估與生成速度。一分鐘的日誌就能告訴你哪個階段吃掉了延遲;用猜的沒有用。

調校要對症下藥:更短的提示詞與前綴快取治 TTFT;量化與更小的權重治解碼(引擎參數、疑難排解)。

相關章節: 速度 · KV 快取 · 並行處理 · token 與分詞器