GGUFは、llama.cpp で使われる言語モデルのファイル形式です。
この形式のいちばんの魅力は、「モデルを動かすのに必要なものが、だいたい1つのファイルにまとまる」こと。
たとえば、Hugging Face の safetensors 系のモデルだと、重みのファイルに加えて JSON や設定ファイルがいろいろ散らばっていることがあります。
Ollama のモデルでも、OCIレイヤーやテンプレート類が別々に入っていて、見た目はちょっとややこしい。
GGUFはそのへんを「まとめて面倒を見よう」という思想が強くて、かなりエレガントです。
個人的には、こういう**“実用上の面倒をちゃんと潰す標準”**は本当に偉いと思います。理屈より先に、使う人のストレスが減るからです。
LLMを正しく動かすには、重みだけではダメです。
モデルに対して「どう入力し、どう出力を扱い、どう止め、どう解釈するか」という周辺ルールが必要になります。
この記事では、その“周辺ルール”がGGUFのメタデータにどこまで入っているか、そして何がまだ足りないかを整理しています。
会話型LLMは、学習時から特定の会話フォーマットでやり取りするように訓練されています。
これが chat template です。
たとえば、あるモデルはこんな感じの形式を使います。
<|turn|>user
Hi there!<turn|>
<|turn|>model
Hi there, how can I help you today?<turn|>
別のモデルでは、もっと違う記法になります。
<s>
<|im_start|>user Hi there!<|im_end|>
<|im_start|>assistant Hi there, how can I help you today?<|im_end|>
さらにややこしいのが、最近のモデルは単なる会話だけでなく、
までフォーマットが絡みます。
ここでGGUFの tokenizer.chat_template に Jinja2 テンプレートが入っている、という話が重要になります。
Jinja2はテンプレート言語ですが、実際にはかなりプログラミング言語っぽいです。条件分岐もループもできます。つまり、会話用のモデルを扱う側は、ある意味でテンプレートを実行する仕組みを持たないといけない。
著者はこの点をかなり現実的に見ていて、transformers は Python の Jinja2、llama.cpp 系は独自実装、NobodyWho は minijinja を使っていると説明しています。
性能差はあるけれど、チャットテンプレート自体がボトルネックになる場面は多くないので、そこまで神経質になる話でもない、としています。
ここ、地味だけど面白いです。
LLMアプリって「モデルを回すだけ」で終わらず、入力をどう整形するかに意外なほど知恵が要るんですよね。AIの賢さ以前に、まず“礼儀正しい話し方”を整える必要がある、みたいな感じです。
LLMは、与えたトークン列に対して次のトークンを延々と出し続けられます。
だから、どこかで止める仕組みが必要です。その典型が end-of-sequence token(EOS) です。
これは、モデルが「ここで終わり」と知らせるための特別なトークンです。
他にも、
bos = Beginning of Sequence(入力の始まり)tool_call の開始・終了のような special tokens があります。
記事中では Gemma4 の例として、以下のようなトークンが紹介されています。
eos : 生成終了bos : 入力開始|tool_call| 系 : tool呼び出しの境界|turn| 系 : 会話のターン境界要するに、special tokens は人間向けの文章ではなく、モデルと実行系の約束事です。
この約束があるから、モデルの出力を「ただの文字列」ではなく「意味のある構造」として扱えます。
こういう“見えないけど大事な印”がちゃんと入っているのは、GGUFのいいところです。
モデルを人間が読むだけなら不要に見えるけれど、実際にアプリで使うときは、ここがズレるとすぐ破綻します。
LLMは次に出す単語候補の確率分布を返します。
そこから1つを選ぶ処理が sampling です。
単純にランダムに選ぶだけでも動きますが、実際にはその前にいろいろな調整を入れたほうが、出力が安定したり、面白くなったりします。
たとえば温度(temperature)や top-p などの調整です。
研究機関やモデル提供元は、よく「このモデルにはこういう sampler 設定がおすすめ」と案内します。
でも、ユーザーが毎回それを別ファイルからコピペするのは面倒です。
著者のチームは以前、その設定を独自形式で Hugging Face に載せていたそうですが、GGUFに sampler chain を直接入れられるようになったことで、その独自形式は不要になった、と書いています。
これはかなりいい流れです。**“標準が育って独自実装がいらなくなる”**瞬間は、エンジニアにとって最高に気持ちいいです。
地味に重要なのが、sampler をどの順番で適用するかです。
著者は、sampler step の順番を入れ替えると、最終的な分布がかなり変わると指摘しています。
でも多くの設定形式、たとえば Ollama の JSON や Hugging Face の generation_config.json には、順番を明示する仕組みがないそうです。
GGUFには general.sampling.sequence という項目があり、これで順序を指定できます。
ただし実際には、この項目が省略されていて、llama.cpp のデフォルト順序に任せているモデルも多いとのこと。
ここは本当に“あると助かる”話です。
設定って、値そのものより順番で壊れることが多いんですよね。しかも壊れ方が地味なので、原因がわかりにくい。そういう意味で、順番を標準化できるのはかなり大きいです。
ここからがこの記事の本題の後半です。
GGUFにはかなり多くの情報が入るようになったけれど、まだ足りないものがある。その代表が tool calling の形式です。
tool calling は、LLMが「外部ツールを呼び出す」仕組みです。
たとえば天気を調べる、DBを引く、計算する、といった処理をモデル自身ではなく外部の関数に任せます。
問題は、モデルごとに tool call の出力形式がバラバラなこと。
例として記事では、以下のような違いが挙げられています。
<tool_call>{"name": "get_weather", "arguments": {"location": "Copenhagen"}}</tool_call>
<tool_call>
<function=get_weather>
<parameter=city>
Copenhagen
</parameter>
</function>
</tool_call>
<|tool_call>call:get_weather{city:<|"|>Copenhagen<|"|>}<tool_call|>
見ての通り、かなり違います。
このため、推論エンジン側は新しいモデルが出るたびに、専用のパーサーを追加する羽目になります。
著者はここで、GGUFにgrammar を入れて、そこから parser を導けるようになったら素晴らしい、と提案しています。
これはかなり筋がいい発想だと思います。
「文字列の形を手で頑張って読む」のではなく、「許される文法を先に定義する」ほうが、壊れにくいからです。
さらに NobodyWho では、tool ごとに 制約付き grammar を生成して、型安全な tool call を保証しているそうです。
モデルが小さいと、整数が必要な場所に浮動小数を出したりすることがあるので、この工夫はかなり実用的です。
ここは個人的にとても好きなポイントで、**“LLMの自由さ”をそのまま放置しない**設計思想が見えます。
次は think token です。
これは、モデルの「考え中」部分を区切るためのトークンです。
最近の Hugging Face 側のリポジトリには think_token が入るようになってきたらしいのですが、GGUF変換後にはそれが落ちてしまうことが多いとのこと。
すると、GGUFベースの推論エンジンでは、
といったことがしづらくなります。
著者は、これはGGUF変換パイプラインに素直に入れれば解決する問題だと言っています。
私もこれはかなり同意で、「せっかく元データにあるのに変換時に落ちる」のは、かなりもったいないです。こういう小さな欠落が、あとで地味に効いてくるんですよね。
マルチモーダルLLM、つまり画像や音声も扱えるモデルでは、テキスト以外を処理するための追加モデルが必要です。
これが projection model です。
通常は、
の2つを渡します。
でもこれだと、「1ファイルで済む」というGGUFの美点が少し崩れます。
著者は、projection model の重みや設定も主ファイルにまとめられたらいいと提案しています。もちろん、約1GBくらいのサイズになることもあるので、使わないときはスキップしたい。だから、projectionあり版 / なし版 の2種類を用意するのが現実的ではないか、としています。
これはバランス感覚がいいですね。
全部を常に抱き込むのではなく、使うときだけ持つ設計にする。こういう“重い機能の付き合い方”は、実務ではかなり大事です。
最後に、意外と重要なのが 「このモデルは何ができるのか」一覧がないことです。
モデルによって、
などの機能差があります。
でも、それがGGUFメタデータから簡単にはわからない。
今はかなり場当たり的に、
という扱いをしているらしく、著者はこれをかなりハック的だと書いています。
ここはまさに標準化の弱点が出る部分です。
「入っているかもしれない」情報が増えるほど、逆に何が確実にできるのかを知る仕組みが大切になります。
個人的には、GGUFの次の成長ポイントはまさにここだと思います。モデルの中身を詰めるだけでなく、能力の宣言がほしい。
記事の締めくくりでは、著者はGGUFをかなり高く評価しています。
そのうえで、今あるGGUFメタデータの良さを活かしつつ、まだ足りないところをみんなで埋めていけるはずだ、と前向きに書いています。
この姿勢はすごくいいなと思いました。
単に「GGUFは神!」で終わるのではなく、どこまで標準化できて、どこがまだ未整理かを具体的に切り分けている。こういう記事は、技術の話としても面白いし、実務のヒントとしても使えます。
この文章が言っているのは、かなりシンプルです。
LLMを正しく使うには、重みだけでは足りない。
そしてGGUFは、その“足りない部分”をうまく抱え込める良いフォーマットになりつつある。
ただしまだ、
などの整備は足りない。
だからこそ、GGUFは「完成品」ではなく、進化中の標準として見るのが面白い、という話です。
私はこの方向性、かなり好きです。
LLMの世界ってモデル本体ばかり注目されがちですが、実際に困るのは「どう呼ぶか」「どう止めるか」「どう解釈するか」だったりします。そこを1つのフォーマットに寄せていくのは、地味だけど本質的です。
派手さはないけれど、こういう土台の整備こそ、あとから効いてくるんですよね。
参考: What's in a GGUF, besides the weights - and what's still missing?