GGUF 小註解

拜台幣升值 10% 左右之賜, 我在 Q2 的投資差點要 GG. 我期望不高, 只要月底能跟 Q1 對齊, 也算是有 10% 看不見的成長了. 在這混亂的國際局勢下, 我來筆記一下 GGUF. 它不是 “GG, U Failed" 的縮寫! 而是全名 GPT-Generated Unified Format, 一個為了 LLM 推理而設計出的檔案格式.

一般我們去 Huggingface 下載一個模型, 它會可能是 .bin 檔, safetensor 檔, 或是 ONNX 檔. 以 safetensor 而言, 裡面放的是模型未量化過的模型權重. 至於 model 的架構 (graph 長相), 會另外寫在 config.json 裡面. 雖然這個檔案名稱也通用於很多其他的地方, 這裡特別是指用來描述模型的 config.json.

例如 Gemma-2-2B 的 config.json [1] :

{
  "architectures": [
    "Gemma2ForCausalLM"
  ],
  "attention_bias": false,
  "attention_dropout": 0.0,
  "attn_logit_softcapping": 50.0,
  "bos_token_id": 2,
  "cache_implementation": "hybrid",
  "eos_token_id": 1,
  "final_logit_softcapping": 30.0,
  "head_dim": 256,
  "hidden_act": "gelu_pytorch_tanh",
  "hidden_activation": "gelu_pytorch_tanh",
  "hidden_size": 2304,
  "initializer_range": 0.02,
  "intermediate_size": 9216,
  "max_position_embeddings": 8192,
  "model_type": "gemma2",
  "num_attention_heads": 8,
  "num_hidden_layers": 26,
  "num_key_value_heads": 4,
  "pad_token_id": 0,
  "query_pre_attn_scalar": 256,
  "rms_norm_eps": 1e-06,
  "rope_theta": 10000.0,
  "sliding_window": 4096,
  "torch_dtype": "float32",
  "transformers_version": "4.42.4",
  "use_cache": true,
  "vocab_size": 256000
}

使用 GGUF 就不用單獨的 config.json 了, 但是生成 GGUF 的時候還是需要. 所以從Huggingface 下載 GGUF 時看到 config.json 也不用太驚訝. 總之, GGUF 檔案裡面除了放模型的權重, 還放了超參數和詞彙表. 所謂超參數就是模型訓練中需要手動設定、無法透過模型訓練的參數 – 這些就是從 config.json 抄過來的.

至於詞彙表 (Vacabulary) 裡面就是放所有 toekn 的字典. 它有一點大, 但不會太大. 假設u有個模型認得 128K 個 token (大約是 GPT-4 的用量), 每個 token 長度 4 bytes, 算起來才 5.1MB, 比起模型權重動輒就是幾 GB, 真的也不差多哪一點.

GGUF 把這些都打包起來, 使用時就不用東市買駿馬,西市買鞍韉,南市買轡頭,北市買長鞭. 假設要量化參數, 下個指令轉出新的 .gguf 檔就好. 包括混合精度也能做到. 更不會改了參數檔, 結果 config.json 沒改到. 我們常常會遇到這個 bug.

雖然 GGUF 把相關資料都包在一起, 但它執行起來並不像個肥宅, 甚至它更省記憶體. 如果我們使用 safetensor, 因為那一整包都是權重, 我們無法知道哪一個 byte 是屬於哪一層的參數, 所以通常整包都得放進記憶體. 像是 Llama 3 70B 量化過也需要用 30GB 記憶體.

反觀 GGUF 自己就有完整的訊息, 它可以把檔案中的模型 memory mapping 到 virtual memory 裡面, run 到哪個 chunk, CPU 或 GPU 直接看 index 載入哪部分的參數即可, 號稱可以作到 zero copy. 以前述 Llama 3 70B 模型為例, 可能只需要 500MB~2GB 的記憶體就夠了. 據說在一台 16GB 的筆電上也能運行.

另外, safetensor 檔案裡面只放權重, 還大剌剌地以 safe 為名. GGUF 包了更多東西進去, 那還能保證安全嗎? 答案也是肯定的. 它對格式和 API 都有所規範, 並內建 SHA-256 校驗, 所以不至於輕易地被駭客埋 code 進去, 並且被當作 Python 執行. 至於 pickle (.pt) 或是 .bin 檔案則是有這方面風險.

最後談 GGUF 的缺點. 首先是它只支援 LLM, 不像 ONNX 適用於所有深度學習 (Yolo 等等)、safetensor 更適用於儲存所有 tensor. 其次是背後沒有大人認養, 所以 toolchain 比較不足. 最後的主要缺點是 – 它對 CPU 優化較好, 同一個 model 用 ONNX 表示的話, 在 GPU 上會跑得更快.

最後叫 Monica 生成一個簡單的 GGUF 檔案範本, 這樣比較好理解.

[檔案頭 Metadata]
general.name = "MiniModel-v1"
general.description = "示範用微型語言模型"
general.file_type = 1 (FP32)
general.architecture = "Transformer"
general.quantization_version = 2

[tensors]
1. token_embedding.weight
   - shape: [5, 3]  # 5個token,每個token向量大小為3
   - data: [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], ...]

2. layer.0.attention.wq.weight
   - shape: [3, 2]  # 3維輸入 → 2維輸出
   - data: [[0.7, 0.8], [0.9, 1.0], [1.1, 1.2]]

3. layer.0.ffn.w1.weight
   - shape: [2, 4]
   - data: [[...], ...]

實際上 head -c 1000 granite-7b-lab-Q4_K_M.gguf | xxd -c 32, 可以看到檔案前面有幾個文字可讀, 後面 tensor 就都是 binary. 如果仔細看下圖, 會看到裡面還看到 ggml 的字樣, 那是 GGUF 的前代格式. 主要是差在舊版沒有 chunked index 結構, 不方便快速抓出某一層參數, 以及沒有 SHA 保護機制.

[REF]

  1. https://blog.csdn.net/shizheng_Li/article/details/144866526

發表留言