GGUFには重み以外に何が入っていて、まだ何が足りないのか?
(nobodywho.ooo)- GGUF は llama.cpp が使う言語モデルのファイル形式で、実行に必要なメタデータを 単一ファイル に収めることで、モデル配布と読み込みを簡素化する
- チャットテンプレート は Jinja2 スクリプトとして、会話形式、ツール呼び出し、マルチメディアメッセージのエンコードを処理するが、実装ごとに挙動の差がある
- GGUF には終了トークンのような 特殊トークン と推奨サンプラー設定を格納でき、最近では サンプラーチェーンの順序 も明示可能になった
- まだ ツール呼び出し形式 はモデルごとに異なり、推論エンジンごとのハードコードが必要で、文法ベースのパーサー生成は標準改善候補として残っている
think_token、プロジェクションモデル のバンドル、機能フラグが不足しており、思考区間の分離、マルチモーダル構成、対応機能の検出は依然として難しい
GGUFが含むもの
- GGUF は llama.cpp が言語モデルに使用するファイル形式
- GGUF の中核的な利点は、モデル実行に必要な複数の構成要素を 単一ファイル に収める点にある
- Hugging Face の一般的な safetensors リポジトリ では、必要な JSON ファイルが複数箇所に分散している
- 一般的な Ollama モデル は、レイヤー JSON、Go テンプレートなどを含む OCI 形式になっている
- GGUF はこうした 付加情報 を 1 ファイルに入れることで、モデルをより扱いやすくしている
チャットテンプレート
- 対話型言語モデルは特定形式のトークン列で学習され、この形式は会話構造のように見える
- Gemma4 形式の例は次のとおり
<|turn>user
Hi there!<turn|>
<|turn>model
Hi there, how can I help you today?<turn|>
- LFM2 形式テンプレートの例は次のとおり
<s>
<|im_start|>user Hi there!<|im_end|>
<|im_start|>assistant Hi there, how can I help you today?<|im_end|>
- 実際のテンプレートは 推論ブロック、ツール説明、ツール呼び出しと応答、画像・音声・動画のようなマルチメディアメッセージのエンコードまで含み、はるかに複雑になる
- こうした処理を担うのが チャットテンプレート であり、Jinja2 テンプレート言語で書かれたスクリプトである
- 例として Gemma 4 に含まれる chat template がある
- GGUF メタデータでは、デフォルトのチャットテンプレートは
tokenizer.chat_templateキーの下に保存される
- モデルは複数のチャットテンプレートを持てる
- ツール呼び出しをサポートするテンプレートと、サポートしないテンプレートが別になっている場合がある
- ほとんどのモデルは単一の巨大なチャットテンプレートを提供し、ツールが指定されたときだけツール呼び出し関連の処理を行う
- 一部のモデルでは、ツール専用チャットテンプレートを別途探す必要がある
- Jinja2 はループ、条件分岐、代入、リスト、辞書などを備えた プログラミング言語 に近い
- 対話型 LLM アプリケーションは、新しいメッセージが追加されるたびに、Gemma が提供する約 250 行の Jinja スクリプトのようなプログラムを実行するインタープリタを含まなければならない
- 実装ごとの Jinja 処理方式もそれぞれ異なる
- Hugging Face transformers は Python の既存の jinja2 ライブラリを使う
- llama.cpp の
llama-serverとllama-cliは 独自の Jinja 実装 を使う libllamaAPI に公開されている llama_chat_apply_template は、少数のチャット形式を C++ に直接ハードコードした古い方式- NobodyWho は、Jinja の作者が Rust で再実装した minijinja を使用している
- これは llama.cpp がかつて使っていたミニマルな Jinja ライブラリ minja とは別物
- Jinja 実装間には かなりの性能差 がある
- ローカル LLM アプリケーションではチャットテンプレート処理は性能ボトルネックではないため、大きな論争点ではない
特殊トークン
- 言語モデルは入力されたトークン列に対して次のトークンを出力し続けられるため、生成を止める方法が必要になる
- 一般的な解決策は 終了トークン を設け、モデルがこのトークンを出力したら推論エンジンが生成を停止する方式
- 終了トークンは 特殊トークン の一例
- 特殊トークンは通常、トークン化された文字以上の意味を持つ
- 通常はユーザーに見せるべきではないが、テキスト表現を持つことが多く、表示自体は可能な場合が多い
- Gemma4 の特殊トークンの一部例は次のとおり
1/<eos>: シーケンスの終了であり、モデルが生成を止めるために出力する2/<bos>: シーケンスの開始であり、入力の前に付く46/<|tool_call>: ツール呼び出しの開始を示す47/<tool_call|>: ツール呼び出しの終了を示す105/<|turn>: 会話ターンの開始を示す106/<turn|>: 会話ターンの終了を示す
サンプラー設定と順序
- 言語モデルは次トークンの確率分布を出力し、この分布からトークンを選ぶ過程を サンプリング という
- 最も単純な方法は、重み付き分布からランダムに選ぶこと
- 実際には、具体的なトークンを選ぶ前に確率分布へ変換を適用すると、より良い結果が得られる
- 研究機関が新しいモデルを公開するとき、特定の 推奨サンプラー設定 をあわせて提供することが多い
- ユーザーがより良い応答を得るために、Markdown ファイルなどから値をコピー&ペーストすることもよくある
- NobodyWho はユーザーの手動コピーを減らすため、Hugging Face ページ に選別したモデルを掲載し、独自形式で推奨サンプラー設定を束ねて提供していた
- 動作はしたが、モデルを有用にするには NobodyWho 側の変換が必要だった
- GGUF 形式に 最近追加された機能 により、サンプラーチェーンをモデルファイル内に直接明示できるようになった
- その結果、NobodyWho の独自形式は不要になり、これは意図していた結果だった
- llm-sampling ウェブアプリ では、異なるサンプラー段階の役割を素早く確認できる
- 個々の段階をドラッグ&ドロップすると、サンプリング段階の 順序 が最終分布に大きな差を生むことが分かる
- Ollama イメージの JSON ファイルや Hugging Face の
generation_config.jsonを含む多くのサンプラー設定形式には、サンプリング段階の順序を明示する方法がない - GGUF 標準では
general.sampling.sequenceフィールドで サンプリング順序 を指定できる - それでも多くの GGUF モデルはこのフィールドを省略し、llama.cpp のデフォルト動作という暗黙の順序に依存している
まだ足りないもの
- 優れた推論エンジンは、さまざまな言語モデルに対して 統一インターフェース を提供しようとする
- GGUF メタデータの付加情報を解析して活用できれば、モデルごとのコードパスを大幅に減らせる
-
ツール呼び出し形式
- ほぼすべての推論エンジンは、異なる ツール呼び出し形式 を解析するためのハードコードされた経路を持っている
- Qwen3 のツール呼び出し形式の例は次のとおり
<tool_call>{"name": "get_weather", "arguments": {"location": "Copenhagen"}}</tool_call>
- Qwen3.5 のツール呼び出し形式の例は次のとおり
<tool_call>
<function=get_weather>
<parameter=city>
Copenhagen
</parameter>
</function>
</tool_call>
- Gemma4 のツール呼び出し形式の例は次のとおり
<|tool_call>call:get_weather{city:<|"|>Copenhagen<|"|>}<tool_call|>
- 新しいモデルが出るたびに、複数の推論エンジンがそれぞれパーサーを実装しなければならない状況になっている
- モデルファイルに文法 (grammar) が含まれ、その文法からパーサーを導出できるなら、GGUF 標準への優れた追加になり得る
- NobodyWho は、渡された特定のツールに合わせて 制約文法 を生成する追加ステップを実行している
- これにより、ツール呼び出しの型安全性を保証できる
- 特に 1B 以下の小型モデルが、整数が必要な箇所に float を渡すようなミスをすることがあり、有用である
- 一般的なツール呼び出しパーサーを作れる文法があったとしても、NobodyWho は渡された具体的なツールごとの文法を生成する関数を引き続き実装する必要がある
- 特定ツール向けの具体文法を作り、そこからパーサーを導出できる メタ文法形式 は、興味深い課題として残っている
-
Think トークン
- 欠けている項目の中では最も追加しやすい部分
- アップストリームの Hugging Face リポジトリ は
think_tokenフィールドを含み始めている think_tokenは、生成出力の 思考区間 を分離するのに非常に有用- 思考区間は通常、削除するか本文出力とは別の形でレンダリングする必要がある
- ダウンストリームの GGUF 変換版 にはこのフィールドが通常含まれていない
- その結果、GGUF ベースの推論エンジンは、特定モデル系列ごとのコードを書かずには思考ストリームを本文出力から分離できない
- 標準 GGUF 変換パイプラインに
think_tokenを追加すれば、この問題は解決する
-
プロジェクションモデル
- 画像や音声をテキストではなく LLM がネイティブに見られるようにする マルチモーダル LLM のやり取りでは、非テキスト入力を処理する追加モデルが必要になる
- この追加モデルは プロジェクションモデル と呼ばれる
- 現在の慣行は 2 つの GGUF ファイルを渡す方式
- 1 つは主言語モデル用の GGUF
- もう 1 つは画像や音声処理用のより小さなモデル
- この方式は、GGUF の単一ファイルの利便性を損なう
- 単一の GGUF ファイルが主ファイル内にプロジェクションモデルの重みと設定をまとめてバンドルできれば、大きな改善になる
- プロジェクションモデルはしばしば約 1GB ある
- 使わないときにはこのオーバーヘッドを避けたいと思う程度には大きい
- プロジェクション重みを含む GGUF と含まない GGUF の 2 つの派生を提供するやり方は妥当である
- そうすれば、ダウンロードする URL は 1 つ、ディスクにキャッシュするファイルも 1 つだけを管理する状態に戻れる
-
対応機能一覧
- モデルごとに対応機能が異なり、GGUF ファイルだけを見て実際の対応機能を簡単に検出するのは難しい
- 一部のモデルは画像入力をサポートし、一部はサポートしない
- 現在の最善策は、プロジェクションモデルが渡されたら画像対応ありと仮定すること
- 一部のモデルはネイティブなツール呼び出しをサポートし、一部はサポートしない
- 現在の最善策は、チャットテンプレート内にツール JSON スキーマ一覧をレンダリングしようとする部分があるかを文字列の部分一致で確認すること
- これは明らかに暫定策
- 一部のモデルは思考ブロックを出力し、一部は出力しない
- 思考タグは通常 GGUF メタデータにないため、モデルに思考ブロックを期待してよいかを確認する良い方法がはっきりしない
- GGUF コミュニティがモデルファイルに 機能フラグ を追加すれば、モデル非依存の推論ライブラリがより一貫したエラーメッセージと警告を提供できる
- たとえば、ネイティブなツール呼び出しをサポートしないモデルに対してツール呼び出しを試みたとき、より適切な案内が可能になる
結論
- GGUF は、モデルを正しく実行するために必要な付加情報を 単一ファイル に収めることで、モデルごとのコードパスを大量に追加しなくても済むようにしている
- GGUF はオープンで拡張可能な形式であり、強いコミュニティを持っている
- 標準をともに強化すれば、優れた開発者体験を維持しつつ、アプリケーション内でモデルを簡単に入れ替えられる
- GGUF メタデータにはすでに有用な部分が多いが、ツール呼び出し文法、
think_token、プロジェクションモデルのバンドル、機能フラグといった改善の余地が残っている
1件のコメント
Hacker News のコメント
プロジェクションモデルが別ファイルに分かれてしまったのは残念で、自分としても単一ファイルに入っている方を望んでいた
なぜそうなったのか正確には分からないが、GGUF を設計するときに念頭にあった単一ファイルの哲学とはかなりずれている
誰かが両者を統合する取り組みを主導してくれるといいのだが、今回は自分は流れから少し外れすぎている気がする :-)
その判断は気に入っている。だとすれば、Mmproj ファイルも GGUF に含めることに前向きである可能性があると考えるのは無理ではないと思う
思いつく唯一の問題は、どの形式を入れるかだ。BF16、F16 などの選択肢がある
GGML と GGUF はオープンソースの機械学習/AI エコシステムにとって非常に重要だった
llama.cpp、whisper.cpp、stable-diffusion.cpp のようなプロジェクトは、さまざまなプラットフォームやハードウェアバックエンドでたいていそのままうまく動く
コンパイルして、モデルを入れて、実行すればいい。そうすれば Web UI と API まで手に入る
> <|turn>user Hi there!<|turn>model Hi there, how can I help you todayなんてことだ、XML よりも可読性の低い形式を作り出してしまった
この形式は実際の内容と混同されないように設計されていて、その内容はインターネット由来の任意のテキストになり得る
そのためには、どこでも使われていない形式を使う必要がある
現時点で最も大きく欠けているのは、モデルアーキテクチャを現在のビルドにハードコードせずに定義する方法だと思う
完全対応モデルと 1:1 の性能同等性がある必要はない
リリース初日からベンダー検証済みのまともなサポートがあるかどうかが、そのモデルを素晴らしいと感じるか、ひどいと感じるかを左右する。最近の Gemma と Qwen のリリースがその例だ
解決策はよく分からないが、モデルグラフを記述する DSL を書いて GGUF に入れる方法はあり得る
別案としては、公式モデルリリースの PyTorch モジュールを読み込み、どうにかして GGML 演算に変換することだ
最初のバージョンに入れたかったが、当時は最小限の機能を持つ仕様を出して実装を進めることを優先した
今でも見てみたいが、現在の GGML IR の状態を非常によく理解している推進役が必要だ
そのうえで、共通パラメータを受け取る共通インターフェースを公開し、追加のカスタムパラメータは Wayland のように拡張として扱える
そうすれば LLaMa のようなトランスフォーマー系だけでなく、RWKV のような再帰型ニューラルネットワーク系やマルチモーダルモデルもサポートできる
実装は分からないが、すばらしいアイデアに聞こえる。ただ、計算グラフがモデルファイルに焼き付いていると、重みを変える必要のないアーキテクチャ改善や最適化が既存ファイルには変換なしで適用できないのではないかと心配だ
> GGUF の本当にすっきりしている点は、ファイルが 1 つだということだ。Hugging Face の一般的な safetensors リポジトリと比べると、必要な JSON ファイルがあちこちに散らばっている [...]興味深いことに、自分にとって AI モデルは“常に”単一ファイルだった。ローカル画像生成ではそれが標準だったからだ
safetensors ファイルにも内部にいろいろ入れられるので、そのために必ずしも GGUF が必要というわけではない
ただし、現代のモデルのテキストエンコーダはそれ自体が数 GB 級の言語モデルなので、どのチェックポイントにも重複コピーを入れる人はいない
多くの画像モデルは単一ファイルだったし今もそうだが、少なくとも当時の LLM の safetensors はそうではなく、構造レベルでそれを強制したかった
また、llama.cpp のようなランナーに JSON リーダーを要求したくなかったが、ST 方式だとそれが必要になっていただろう
さらに大きな問題として、記憶が正しければ当時の ST は GGML の新しい量子化形式をサポートできず、独自のファイル形式を持てば ST では得にくい柔軟性を確保できた
アーキテクチャを重みで実際に動かすには、単一の重みファイルだけでなく複数のエンコーダとデコーダなどが必要になる
使っているツールがそれを隠してくれることはあっても、水面下では依然として存在している
libllamaAPI に露出している、いくつかのチャット形式を C++ に直接ハードコードした少し奇妙なllama_chat_apply_templateのことだが、デスクトップ推論アプリを FLTK[0] でいじっている立場としては、これが llama.cpp の使っている実際のJinja2 テンプレートパーサを使ってくれればと思うあるいは、そうしたことをしてくれる別の C 関数があればうれしい。正しくパースするには、たとえばツール呼び出しの有無をテンプレートが認識できるよう、複数のデータを渡せる必要がありそうだ
今はこの間に合わせの関数を使っているが、最終的には Jinja2 インタプリタを自分で使うか、llama.cpp のコードを持ってきて貼り付けることになりそうだ
それでも、GGUF のオールインワンなアプローチは非常に便利だ。プロジェクションモデルが別ファイルなのは違和感があるという点には同意する
ビジョン対応モデルを最初に受け取ったとき、適切そうな GGUF だけをダウンロードしたのに、llama.cpp がモデルを処理できないと言ってきて、かなり後になってから追加ファイルが必要だと気づいた
そのときの感想は文字どおり「GGUF って全部入りの形式じゃなかったの?」だった :-P
[0] https://i.imgur.com/GiTBE1j.png
自分はずっと Hugging Face リポジトリに近いsafetensors + メタデータファイル形式を使ってきた
大きな不便はまったくないが、GGUF のほうがよりコンパクトな形式と良いサポートを備えているのはよさそうだ
GGUF にまだないものを見て、むしろ GGUF をより深く学べた
ツール呼び出し形式は実に自然で、LLM からエージェントへ移る節目になりそうだ
最近 TheBloke の7B Mistralを入手して試してみようと思っていて、4070 を持っている
Gemma 4 e4b を一度試してみるといい。Mistral 7B と同じくらいのサイズで、4070 でもうまく動くはずだ
「E4B」という名前は少し誤解を招くかもしれない
12GB の 4070 ならQwen 3.5 9B q4kmや Qwen 3.6 35B を動かせる。後者のほうがずっと賢いが、メモリオフロードのためかなり遅い
どちらも LM Studio で試してみると、本当に驚くほど高性能だ
TheBloke が好きなので、今でもモデルを作ってくれたらいいのにと思う