Appearance
Q&A 問答集
基礎概念
Q: llama2.c 和 llama.cpp 有什麼不同?
A: llama2.c 是 Karpathy 的教學用專案,約 700 行 C 程式碼,只支援 Llama 2 架構。llama.cpp 是生產級專案,數萬行程式碼,支援數十種模型架構和多種量化格式。llama2.c 的定位是「最簡單的參考實作」,llama.cpp 的定位是「最高效的推理引擎」。
Q: 為什麼用 C 而不是 C++?
A: Karpathy 選擇 C 是因為:
- C 是最低門檻的系統程式語言,幾乎所有人都能讀懂
- Zero dependencies——只需標準 C 函式庫
- C 程式碼可以直接當作 pseudocode 閱讀
- 更貼近「從零開始實作」的教育精神
Q: 這個專案可以用來做生產部署嗎?
A: 不建議。llama2.c 的教育價值遠大於生產價值。作者也在 README 中說「這不是一個功能完整的框架」。生產部署請使用 llama.cpp、vLLM 或 TensorRT-LLM 等專案。
技術細節
Q: C 版本和 PyTorch 版本的輸出會完全一致嗎?
A: 在相同的權重和 seed 下,應該一致。但浮點數運算順序不同可能導致微小差異(C 和 PyTorch 的 matmul 實現順序可能不同)。專案中的 test_all.py 會驗證兩者在 200 步內的一致性。
Q: KV Cache 在 C 中如何管理?
A: run.c 使用靜態分配的陣列:float* key_cache[layers][seq_len][kv_dim]。在 forward() 中,新的 K/V 透過指標寫入對應位置:
c
s->k = s->key_cache + loff + pos * kv_dim;
s->v = s->value_cache + loff + pos * kv_dim;pos 從 0 開始遞增,因此不同的 token 寫入 cache 的不同位置。所有過去的 K/V 保留在 cache 中供 attention 使用。
Q: 為什麼 QKV 的維度不同?(GQA)
A: 這是 GQA(Grouped Query Attention)的設計。Q 的維度是 dim(n_heads × head_size),K 和 V 的維度是 kv_dim(n_kv_heads × head_size)。當 n_kv_heads < n_heads 時,多個 query head 共享同一組 key/value head,減少記憶體用量和計算量。
Q: RoPE 在 C 中的實作和 PyTorch 中有什麼不同?
A: PyTorch 版預先計算所有位置的 cos/sin 值(precompute_freqs_cis),在 forward 時直接查表。C 版則在 forward 時即時計算 cosf(pos * freq) 和 sinf(pos * freq)。
這是空間 vs 時間的取捨:
- PyTorch:預先計算 O(max_seq_len × head_size),查表 O(1)
- C:即時計算 O(1)(每次 forward 都要重新算 sin/cos)
Q: memory_map_weights 中為什麼要「跳過」freq_cis?
c
ptr += p->seq_len * head_size / 2; // skip what used to be freq_cis_real
ptr += p->seq_len * head_size / 2; // skip what used to be freq_cis_imagA: 早期版本的匯出腳本會將預計算的 RoPE cos/sin 表格寫入權重檔。C 版不使用這些資料(即時計算),因此需要跳過。這行程式碼保證了與舊版權重檔的向後相容性。
Q: shared_weights 參數的作用?
A: 當 vocab_size 為正數時表示使用權重綁定(weight tying),即分類器權重與 token 嵌入表共享。負數則表示獨立的分類器權重。這個 hacky 的設計是為了在不增加檔案格式複雜度的情況下傳遞這個資訊。
Q: C 中的 BPE tokenizer 與 SentencePiece 有什麼差異?
A: C 版的 BPE 實作是 SentencePiece 的簡化版本:
- 支援 UTF-8 編碼和 byte_fallback
- BPE 合併邏輯使用原始的分數比較
- 但被作者標註「TODO: pretty sure this isn't correct in the general case」
- 對一般英文文本效果足夠,但對特殊語言可能不完美
效能相關
Q: 為什麼 int8 量化能加速 3 倍?
A: 三個原因:
- 記憶體頻寬:int8 資料量是 float32 的 1/4,減少記憶體讀取時間(推理常受記憶體頻寬限制)
- 計算效率:整數乘加比浮點乘加快
- 快取友好:更小的資料更適合 CPU 快取
Q: OpenMP 的加速效果如何?
A: 取決於 CPU 核心數。在 96 執行緒的 Linux 伺服器上,7B 模型可達 ~4 tok/s。但要小心:
- 執行緒數不是越多越好,超過物理核心數反而因 cache thrashing 而變慢
- SMT(超執行緒)環境下應設定為物理核心數而非邏輯核心數
Q: mmap 比 fread 好在哪裡?
A: mmap 將檔案映射到記憶體空間,由作業系統按需載入頁面。好處:
- 懶載入:只需載入實際使用的頁面
- 零拷貝:資料直接從磁碟進入應用程式位址空間,不需經過使用者空間 buffer
- 共享:多個行程可共享同一記憶體映射
Q: 為什麼 13B+ 模型無法執行?
A: 作者指出是因為 pointer arithmetic 的 integer overflow。雖然 n_layers 已使用 unsigned long long,但其他指標運算可能仍有溢位問題。這是個已知的待解決問題。
比較與選型
Q: 學習 LLM 推理,應該讀 llama2.c 還是 llama.cpp?
A:
- 如果你想理解原理:讀 llama2.c。700 行 C 程式碼,一天就能讀完並理解全貌。
- 如果你想實戰部署:用 llama.cpp。功能完整、高度最佳化、支援多種模型。
建議學習路徑:llama2.c → llama.cpp。
Q: Python 訓練完怎麼讓 C 推理?
A: 流程:
PyTorch 模型 → export.py → .bin 權重檔
↓
run.c 讀取 → 推理train.py 會自動在 checkpoint 時呼叫 model_export() 生成 .bin 檔。
Q: 這個專案和 nanoGPT 的關係?
A: llama2.c 是 nanoGPT 的精神繼承者。Karpathy 在 README 中說:「I took my earlier nanoGPT, tuned it to implement the Llama-2 architecture instead of GPT-2」。兩者的訓練程式碼架構相似,但推理引擎從 PyTorch 改為純 C 實作。