Skip to content

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 是因為:

  1. C 是最低門檻的系統程式語言,幾乎所有人都能讀懂
  2. Zero dependencies——只需標準 C 函式庫
  3. C 程式碼可以直接當作 pseudocode 閱讀
  4. 更貼近「從零開始實作」的教育精神

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_imag

A: 早期版本的匯出腳本會將預計算的 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: 三個原因:

  1. 記憶體頻寬:int8 資料量是 float32 的 1/4,減少記憶體讀取時間(推理常受記憶體頻寬限制)
  2. 計算效率:整數乘加比浮點乘加快
  3. 快取友好:更小的資料更適合 CPU 快取

Q: OpenMP 的加速效果如何?

A: 取決於 CPU 核心數。在 96 執行緒的 Linux 伺服器上,7B 模型可達 ~4 tok/s。但要小心:

  • 執行緒數不是越多越好,超過物理核心數反而因 cache thrashing 而變慢
  • SMT(超執行緒)環境下應設定為物理核心數而非邏輯核心數

Q: mmap 比 fread 好在哪裡?

A: mmap 將檔案映射到記憶體空間,由作業系統按需載入頁面。好處:

  1. 懶載入:只需載入實際使用的頁面
  2. 零拷貝:資料直接從磁碟進入應用程式位址空間,不需經過使用者空間 buffer
  3. 共享:多個行程可共享同一記憶體映射

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 實作。