(ch03)= # 第3章 llama.cpp の全体像 この章では、{ref}`第 2 章 `で整理した推論の流れを、``llama.cpp`` の実際の構成要素に対応づけます。 扱うのは、リポジトリ全体を貫く 2 つの層、主要なディレクトリの役割、そしてデータが通っていく道筋です。 {ref}`第 2 章 `では、推論を「テキスト → トークン → ベクトル → モデル本体 → ロジット → サンプリング → 次のトークン」という一本の流れとして捉えました。 本章のゴールは、その抽象的な流れに、``ggml`` やトークナイザー、サンプラーといった具体的な名前と、ソースファイルの住所を割り当てることです。 言い換えると、ここで読者の頭の中に「地図」を一枚描きます。 ここで個々のファイルの中身まで読む必要はありません。 いまは、どこに何があり、データがどの順で通っていくのか、その全体像だけつかめれば十分です。 細部は第 2 部以降で、各ブロックを一段ずつ開いていきます。 (2つの層 ggmlとllamacpp本体)= ## 3.1 2 つの層:`ggml` と `llama.cpp` 本体 最初に、``llama.cpp`` というプロジェクトを理解するうえで最も重要な区別を示します。 それは、 **テンソル計算をおこなう下の層(``ggml``)と、その上で「言語モデルの推論」を組み立てる層(``llama.cpp`` 本体)が、はっきり分かれている** という点です。 先に役割を整理しておきます。 - **``ggml``** は、テンソル(多次元配列)に対する計算と、その実行を担う汎用のライブラリです。 「行列を掛ける」「要素ごとに足す」といった演算を計算グラフとして組み立て、CPU や GPU といったバックエンドの上で実際に走らせます。 言語モデル特有の知識は持っていません。あくまで「数値計算のエンジン」です。 - **``llama.cpp`` 本体** は、その ``ggml`` を使って「言語モデルの推論」という仕事を組み立てる層です。 モデルファイルを読み込み、トークン化をおこない、Transformer の計算グラフを ``ggml`` の演算として組み立て、その結果からロジットを取り出してサンプリングする、こうした推論固有のロジックがここにあります。 この上下関係を図にすると、次のようになります。 ```{d2} :align: center :width: 100% direction: down cli: "llama-cli / llama-server\n(tools/)" simple: "examples/simple\n(examples/)" api: "公開 API\ninclude/llama.h" core: "llama.cpp 本体(src/)\nトークナイザー・モデル・グラフ構築\ncontext / decode・KV キャッシュ・サンプラー" common: "共通基盤(common/)\n引数解析・チャット・ログ" ggmlcore: "ggml 中核(ggml/)\nテンソルと計算グラフ" backend: "バックエンド抽象\nggml-backend" cpu: CPU metal: Metal cuda: CUDA vk: Vulkan ほか cli -> api simple -> api cli -> common: { style.stroke-dash: 4 } api -> core core -> common: { style.stroke-dash: 4 } core -> ggmlcore ggmlcore -> backend backend -> cpu backend -> metal backend -> cuda backend -> vk ``` :::{objcaption} 図 3-1 ggml と llama.cpp 本体の 2 層構造 ::: 図を上から読みます。 ``llama-cli`` のようなコマンドや ``examples/simple`` のようなサンプルプログラムは、いきなり内部に触れるのではなく、``include/llama.h`` が公開する API を通して ``llama.cpp`` 本体を呼び出します。 本体(``src/``)は推論のロジックを組み立て、計算が必要になると下の ``ggml`` に渡します。 ``ggml`` は計算グラフを組み、最終的に CPU や Metal、CUDA、Vulkan といったバックエンドの上で数値計算を実行します。 この 2 層構造を意識しておくと、後の章がぐっと追いやすくなります。 「いま読んでいるコードは、推論ロジックの話なのか、それとも数値計算エンジンの話なのか」を切り分けられるからです。 本書でも、``ggml`` は第 3 部でまとめて詳しく読み、``llama.cpp`` 本体は第 4 部以降で扱う、という形で層ごとに章を分けています。 :::{note} ``ggml`` は ``llama.cpp`` 専用ではなく、``whisper.cpp`` などと並行して育てられてきた、設計上独立したテンソルライブラリです。 リポジトリの中でも ``ggml/`` という独立したディレクトリにまとまっており、画像生成など別の用途にも使われています。 本書では「``llama.cpp`` を動かすための計算エンジン」という立場から読み解きます。 ::: (リポジトリの歩き方)= ## 3.2 リポジトリの歩き方 次に、この 2 層がリポジトリのどこにあるのかを見ます。 ``llama.cpp`` のリポジトリには多くのディレクトリがありますが、本書で繰り返し戻ってくるのは限られた数か所です。 まずはその主要なディレクトリと役割を一覧で押さえておきましょう。 (ch03-repo)= | ディレクトリ | 役割 | 本書で扱う部 | | --- | --- | --- | | ``ggml/`` | テンソル計算エンジンとバックエンド実装(CPU、Metal、CUDA、Vulkan ほか) | 第 3 部 | | ``src/`` | ``llama.cpp`` 本体。モデル、トークナイザー、グラフ構築、context / decode、KV キャッシュ、サンプラー | 第 4、5、6 部 | | ``include/`` | 公開 API ヘッダー(``llama.h``) | 第 2 部 | | ``common/`` | ツール共通の補助(引数解析、チャット、ログ、サンプリング設定 など) | 第 2、6 部 | | ``tools/`` | 実行コマンド群(``llama-cli`` 、 ``llama-server`` 、量子化ツール など) | 第 2、7 部 | | ``examples/`` | 小さなサンプル(``simple`` 、 ``embedding`` など) | 第 2 部 | :::{objcaption} 表 3-1 リポジトリの主なディレクトリと役割 ::: それぞれを一段だけ補足します。 ``ggml/`` は計算エンジンの本体で、``ggml/src/ggml.c`` にテンソルと計算グラフの中核があります。 その下に ``ggml/src/ggml-cpu/`` や ``ggml/src/ggml-metal/`` といったバックエンド別のディレクトリが並びます。 ``src/`` が ``llama.cpp`` 本体です。 ファイル名はおおむね役割を表しており、たとえばトークナイザーは ``src/llama-vocab.cpp``、計算グラフの構築は ``src/llama-graph.cpp``、コンテキストとデコードは ``src/llama-context.cpp`` といった具合に分かれています。 モデルアーキテクチャごとの個別実装は ``src/models/`` 以下に分散しています。 ``include/`` には公開 API のヘッダーがあり、外部のプログラムは原則この ``llama.h`` を経由して ``llama.cpp`` を使います。 ``tools/`` と ``examples/`` は、その API を実際に呼び出す側です。 ``common/`` は、複数のツールが共有する補助コード(コマンドライン引数の解析やログなど)を集めた場所です。 ```{image} /_static/figures/fig3-2.svg :align: center :width: 100% :alt: リポジトリの主要ディレクトリ構成 ``` :::{objcaption} 図 3-2 リポジトリの主要ディレクトリ構成 ::: リポジトリを開いて、``src/`` の中をざっと眺めてみてください。 ``llama-`` で始まるファイル名が、本章や {ref}`表 3-2 ` に出てくる役割と対応していることが見て取れるはずです。 いまは中身を読む必要はありません。「役割ごとにファイルが分かれている」という構造だけ確認できれば十分です。 (データフロー 地図と現地を結ぶ)= ## 3.3 データフロー:地図と現地を結ぶ {ref}`第 2 章 `で見た推論の流れを、{ref}`表 3-1 ` のディレクトリと結びつけます。 まず、データがどの順で通っていくのかを図で示します。 ```{d2} :align: center :width: 100% direction: down t: 入力テキスト tok: "トークン化\nsrc/llama-vocab.cpp" emb: "埋め込み + 計算グラフ構築\nsrc/llama-graph.cpp ほか" exec: "グラフ実行(テンソル計算)\nggml + バックエンド" logits: ロジット smpl: "サンプリング\nsrc/llama-sampler.cpp" next: 次のトークン detok: "デトークナイズ\nsrc/llama-vocab.cpp" out: 出力テキスト kv: "KV キャッシュ\nsrc/llama-kv-cache*.cpp" t -> tok -> emb -> exec -> logits -> smpl -> next next -> emb: 自己回帰ループ next -> detok -> out exec -- kv: 保存 / 再利用 { style.stroke-dash: 4 } ``` :::{objcaption} 図 3-3 推論のデータフローと対応ソース ::: 図の流れを、上から順にたどります。 1. **入力テキスト → トークン化** :ユーザーが入力した文字列を、トークン ID の列に変換します。担当は ``src/llama-vocab.cpp`` のトークナイザーです。 2. **埋め込み + 計算グラフ構築** :トークン ID を埋め込みベクトルに変え、Transformer の各層を ``ggml`` の演算として組み立てます。グラフの設計図を作る段階で、担当は ``src/llama-graph.cpp`` と ``src/models/`` 以下の各アーキテクチャ実装です。 3. **グラフ実行** :組み立てたグラフを、``ggml`` がバックエンドの上で実際に計算します。ここが数値計算の本番で、CPU や GPU が行列計算を回します。 4. **ロジット → サンプリング** :最後の層から出てきたロジットをもとに、``src/llama-sampler.cpp`` が次の 1 トークンを選びます。 5. **自己回帰ループ** :選ばれたトークンを末尾に加え、次の 1 トークンを予測するために 2 番に戻ります。{ref}`第 2 章 `で見たループが、ここに現れます。 6. **デトークナイズ → 出力テキスト** :選ばれたトークンを文字列に戻し、画面に表示します。担当は再びトークナイザーです。 図の右下にある **KV キャッシュ** (``src/llama-kv-cache*.cpp``)は、この流れの脇で実行を支える存在です。 {ref}`第 2 章 `で触れたとおり、過去のトークンの計算結果を保存しておき、ループのたびに再利用することで、新しいトークン 1 つ分だけの計算で済むようにします。 そして、この一連のループ全体を制御しているのが ``src/llama-context.cpp`` と ``src/llama-batch.cpp`` です。 「トークンをまとめて受け取り、グラフを実行させ、ロジットを取り出す」という一回分の処理は、本体では ``llama_decode`` という関数を入口にして進みます。 {ref}`第 2 章 `で出てきた prefill と decode の 2 フェーズも、この入口の内側で扱われます。 各段階と、対応するソース、そして本書のどこで詳しく読むかを一覧にまとめます。 (ch03-dataflow)= | 段階 | 主に対応するソース | 詳しく読む部 | | --- | --- | --- | | モデルの読み込み(前準備) | ``ggml/src/gguf.cpp`` 、 ``src/llama-model-loader.cpp`` 、 ``src/llama-model.cpp`` | 第 4 部 | | トークン化 / デトークナイズ | ``src/llama-vocab.cpp`` | 第 4 部 | | 計算グラフの構築(埋め込み〜各層) | ``src/llama-graph.cpp`` 、 ``src/models/*.cpp`` | 第 5 部 | | グラフの実行(テンソル計算) | ``ggml/src/ggml.c`` 、 ``ggml-backend.cpp`` 、 ``ggml-cpu/`` ほか | 第 3 部 | | コンテキストとデコードの制御 | ``src/llama-context.cpp`` 、 ``src/llama-batch.cpp`` | 第 5 部 | | KV キャッシュ | ``src/llama-kv-cache*.cpp`` | 第 5 部 | | ロジットからサンプリング | ``src/llama-sampler.cpp`` | 第 6 部 | :::{objcaption} 表 3-2 データフローの各段階と対応するソース ::: この表は、本書全体の見取り図でもあります。 推論のどこか一点について「あれはどのファイルの話だったか」と迷ったときは、ここに戻ってくれば、対応するソースと、詳しく読む部がすぐに分かります。 {ref}`第 2 章 `の抽象的な流れは、すべて実在するソースファイルに対応しています。 地図の上の各地点に、現地の住所が割り当てられました。 (最初の推論を動かす)= ## 3.4 最初の推論を動かす 地図ができたところで、最後に「現地を歩く入口」を示しておきます。 全体像を一度に追うには、``llama-cli`` や ``llama-server`` は機能が多すぎます。 そこで本書は、必要最小限のコードで推論の流れを一周できる ``examples/simple`` を最初の入口に選びます。 ``examples/simple/simple.cpp`` は、``include/llama.h`` の公開 API を順番に呼び出すだけの、短いプログラムです。 モデルを読み込み、プロンプトをトークン化し、``llama_decode`` で計算を回し、サンプリングで次のトークンを選び、デトークナイズして表示する。 本章のデータフロー図とほぼ同じ順序が、そのままコードに現れます。 このプログラムを読み解くことが、第 4 部の最初の仕事になります。 ```{image} /_static/figures/fig3-4.svg :align: center :width: 95% :alt: examples/simple の公開 API 呼び出し列 ``` :::{objcaption} 図 3-4 examples/simple の公開 API 呼び出し列 ::: 実際に動かすには、まず ``llama.cpp`` を CMake でビルドします。 ビルドすると、``tools/`` や ``examples/`` のプログラムが実行可能ファイルとして生成されます。 あとは GGUF 形式のモデルファイルを 1 つ用意すれば、推論を走らせられます。 :::{note} ``ggml`` は環境に応じて使うバックエンドを選びます。 たとえば macOS では、何も指定しないと既定で Metal(GPU)が使われます。 CPU 側の処理を観察したいときは、GPU へ載せる層の数を 0 にする ``-ngl 0`` を付けると、計算が CPU バックエンドに固定されます。 バックエンドの選択そのものは第 3 部で詳しく扱います。ここでは「同じソースでも、実行されるバックエンドは環境や設定で変わる」とだけ押さえてください。 ::: ビルドや実行の細かな手順は付録 B にまとめます。 本章の段階では、コマンドを完璧に動かすことよりも、「``examples/simple`` が、本章で描いた地図をいちばん短く一周するプログラムだ」という位置づけをつかんでおくことが大切です。 この章では、``llama.cpp`` を ``ggml``(計算エンジン)と本体の 2 層として捉え、リポジトリの主要ディレクトリと、推論のデータフローを実際のソースに対応づけました。 {ref}`第 2 章 `で見た **「テキスト → トークン → 計算グラフ → 実行 → ロジット → サンプリング → 次のトークン」** という流れは、{ref}`表 3-2 ` のとおり、すべて実在するファイルの住所を持っています。 この地図を手にしたことで、本書の第 1 部(導入)は完了です。 次の{ref}`第 4 章 `からは、いよいよ現地を歩き始めます。 最初に開くのは ``examples/simple`` です。 本章で俯瞰したデータフローが、公開 API の呼び出しとして実際のコードにどう現れるのかを、一行ずつ読み解いていきます。