Appearance
概念卡片
KV Cache(Key-Value 快取)
是什麼 在自迴歸生成中,每個時間步計算注意力時,需要目前 token 的 Query 與所有過去 token 的 Key 和 Value 做計算。KV Cache 將過去 token 的 K、V 向量儲存下來,避免每次重新計算。
在 llama2.c 中的實作
c
s->key_cache = calloc(p->n_layers * p->seq_len * kv_dim, sizeof(float));
s->value_cache = calloc(p->n_layers * p->seq_len * kv_dim, sizeof(float));三維陣列:[layer][position][kv_dim]
優點
- 時間複雜度從 O(n³) 降為 O(n²)
- 每個新 token 只需計算自身的 QKV,過去 token 的 KV 直接從 cache 讀取
注意事項
- 需要 O(n_layers × seq_len × kv_dim) 的額外記憶體
- 對長序列生成,記憶體占用可能成為瓶頸
RoPE(Rotary Position Embedding)
是什麼 一種相對位置編碼方法,將位置資訊透過旋轉矩陣注入到 Q 和 K 向量中。
核心公式
Rot(Θ, pos) = [cos(posθ) -sin(posθ)]
[sin(posθ) cos(posθ)]每個維度對的旋轉頻率 θ_i = 10000^(-2i/d) 不同。
在 llama2.c 中的實作
c
for (int i = 0; i < dim; i+=2) {
int head_dim = i % head_size;
float freq = 1.0f / powf(10000.0f, head_dim / (float)head_size);
float val = pos * freq;
float fcr = cosf(val), fci = sinf(val);
// 旋轉 q[i], q[i+1] 和 k[i], k[i+1]
}特點
- 絕對位置編碼的形式,相對位置編碼的效果
- 當 q 和 k 都旋轉相同角度時,它們的 dot product 只取決於位置差
- 無需學習額外參數
- 支援序列長度外推
RMSNorm(Root Mean Square Layer Normalization)
是什麼 一種簡化的 Layer Normalization,只做縮放(scale)不做平移(shift)。
公式
RMSNorm(x) = weight * x / sqrt(mean(x²) + eps)與 LayerNorm 的比較
| LayerNorm | RMSNorm |
|---|---|
(x - μ) / σ * γ + β | x / RMS(x) * weight |
| 需要計算均值 μ | 不需要 |
| 2 個可學習參數 (γ, β) | 1 個可學習參數 (weight) |
| 計算量大約 2× | 計算量較少 |
為什麼 LLM 用 RMSNorm
- 計算效率更高(少了減均值的操作)
- 在深層 Transformer 中效果與 LayerNorm 相當
- Meta 的 Llama 系列使用 RMSNorm
GQA(Grouped Query Attention)
是什麼 多頭注意力的一種變體,將 Query head 分成若干組,每組共享一個 Key/Value head。
示意圖
Q heads: [H1][H2][H3][H4][H5][H6]
\ / \ /
KV heads: [KV1] [KV2]n_heads=6, n_kv_heads=2 → kv_mul=3
在 llama2.c 中的實作
c
int kv_dim = (p->dim * p->n_kv_heads) / p->n_heads;
int kv_mul = p->n_heads / p->n_kv_heads;
// 在 attention 中:
float* k = s->key_cache + loff + t * kv_dim + (h / kv_mul) * head_size;優點
- 減少 KV Cache 的記憶體用量(約 n_kv_heads/n_heads)
- 推理速度更快
- 模型品質損失極小
GQA vs MHA vs MQA
| 類型 | KV heads | 典型值 |
|---|---|---|
| MHA (Multi-Head Attention) | = n_heads | 32 |
| GQA (Grouped Query Attention) | 介於 1 ~ n_heads | 8 |
| MQA (Multi-Query Attention) | = 1 | 1 |
Tokenizer(分詞器)
是什麼 將文字字串轉換為整數 ID 序列的元件,LLM 處理的是 ID 而非原始文字。
SentencePiece + BPE llama2.c 使用 SentencePiece 訓練的 BPE(Byte Pair Encoding)tokenizer:
- 從 UTF-8 位元組開始
- 反覆合併最頻繁的相鄰 token pair
- 形成次詞(subword)詞彙表
特殊 Token
| ID | Token | 意義 |
|---|---|---|
| 0 | <unk> | 未知 token |
| 1 | <s> | BOS(Beginning of Sequence) |
| 2 | </s> | EOS(End of Sequence) |
在 llama2.c 中
encode():文字 → token IDs(含 BPE 合併)decode():token ID → 文字片段str_lookup():用二分搜尋在排序後的 vocab 中查詢字串
Quantization(量化)
是什麼 將模型權重從 float32 降為較低精度(如 int8),減少儲存空間並加速推理。
Q8_0 格式(llama2.c 使用)
- 對稱量化(symmetric),範圍 [-127, 127]
- 每組 (block) 有獨立的 scale 參數(float32)
- 權重儲存為 int8,運算時反量化為 float32
runq.c 的量化推理 僅量化參與矩陣乘法的權重(matmul),RMSNorm 等層仍使用 float32。
效果
| 指標 | float32 | int8 |
|---|---|---|
| 儲存空間 | 26GB (7B) | 6.7GB (7B) |
| 推理速度 | ~4.6 tok/s | ~14 tok/s |
| 模型品質 | 基準 | 略微下降 |
SwiGLU(Swish-Gated Linear Unit)
是什麼 LLM 中 MLP 層使用的激活函數,結合 Swish (SiLU) 和門控機制。
公式
SwiGLU(x) = w2(SiLU(w1(x)) * w3(x))
其中 SiLU(x) = x * sigmoid(x) = x / (1 + e^(-x))與 ReLU MLP 的比較
| ReLU MLP | SwiGLU MLP |
|---|---|
w2(ReLU(w1(x))) | w2(SiLU(w1(x)) * w3(x)) |
| 2 個權重矩陣 | 3 個權重矩陣 |
| 參數較少 | 參數較多,但效果更好 |
在 llama2.c 中:
c
matmul(hb, xb, w1, dim, hidden_dim); // w1(x)
matmul(hb2, xb, w3, dim, hidden_dim); // w3(x)
for (i...) hb[i] = SiLU(hb[i]) * hb2[i]; // 門控相乘
matmul(xb, hb, w2, hidden_dim, dim); // w2()