Read OSS

llama.cpp のアーキテクチャ:コードベースの全体マップ

中級

前提知識

  • C/C++ の基礎知識(ポインタ、クラス、仮想ディスパッチ)
  • 大規模言語モデル(LLM)がどのようにテキストを生成するかについての概括的な理解

llama.cpp のアーキテクチャ:コードベースの全体マップ

llama.cpp は、LLM エコシステムにおいて最も影響力のあるオープンソースプロジェクトのひとつです。MacBook から Raspberry Pi まで、コンシューマー向けハードウェア上で大規模言語モデルを 2〜4 ビットに量子化して動作させることができます。しかし、120 以上のモデルアーキテクチャをサポートするコアライブラリのコードは 55,000 行を超えており、ソースコードに飛び込むのは地図なしで見知らぬ街を探索するようなものです。

この記事は、その地図です。コードベースを効率よく読み解くために必要なメンタルモデルを整理します。具体的には、2 層のライブラリ構造、ディレクトリのレイアウト、C API ファサードパターン、推論ライフサイクル全体を支える 3 つの重要な型、そしてライブラリにトークン生成を要求したときに何が起きるかのステップごとのトレースを取り上げます。

2 層のライブラリ構造

llama.cpp はモノリシックなコードベースではありません。2 つの独立したライブラリと、その上に乗るユーティリティ層で構成されています。

graph TD
    subgraph "User-facing tools"
        CLI["tools/cli"]
        SRV["tools/server"]
        QUANT["tools/quantize"]
    end

    subgraph "Shared utilities"
        COMMON["common/"]
    end

    subgraph "Core libraries"
        LLAMA["libllama (src/)"]
        GGML["GGML (ggml/)"]
    end

    CLI --> COMMON
    SRV --> COMMON
    QUANT --> COMMON
    COMMON --> LLAMA
    LLAMA --> GGML

GGMLggml/)は汎用のテンソル演算ライブラリです。言語モデルについては何も知らず、テンソル型、遅延評価の計算グラフ、量子化データフォーマット、そして CPU・GPU・アクセラレータ向けのプラガブルなバックエンドシステムを提供します。PyTorch のテンソル層を最小限かつ推論に最適化した代替品と考えると分かりやすいでしょう。

libllamasrc/)は LLM 固有のライブラリです。モデルアーキテクチャ、トークナイゼーション、KV キャッシュ、サンプリング戦略を理解しており、LLaMA から Mamba、RWKV まで 120 以上のモデルアーキテクチャに対して GGML を使って計算グラフを構築・実行します。

common/ は、コマンドラインツール(server、CLI、quantize など)だけが使うユーティリティ層です。引数パース、チャットテンプレートの適用、高レベルのサンプリングラッパーを担当します。公開ライブラリ API の一部ではありません

この分離には重要な意味があります。GGML は単独で利用可能であり、whisper.cpp をはじめ他のプロジェクトにも共有されています。また libllama と common/ の境界が明確に保たれているため、自分のアプリケーションに llama.cpp を組み込む場合は libllamaggml のみをリンクすれば済み、common/ は完全に無視できます。

ヒント: 依存の方向は厳密に一方向です:tools → common → libllama → GGML。これに違反する #include があればバグです。

ディレクトリ構造ガイド

最上位ディレクトリの一覧とそれぞれの役割を示します。

ディレクトリ 役割
include/ 公開 C API ヘッダ(llama.hllama-cpp.h
src/ libllama のコア実装(C++ 内部)
src/models/ モデルアーキテクチャごとの .cpp ファイル(約 110 ファイル)
ggml/ GGML テンソルライブラリ(include/、src/、backends/)
common/ CLI ツール向け共有ユーティリティ
tools/ ユーザー向けバイナリ(server、cli、quantize、bench など)
gguf-py/ GGUF ファイルの読み書き用 Python ライブラリ
tests/ ユニットテストと統合テスト
convert_hf_to_gguf.py HuggingFace フォーマットからのモデル変換スクリプト

src/CMakeLists.txt は libllama のファイル一覧として最も信頼できる情報源です。20 以上のコア llama-*.cpp モジュールから models/ 以下の 110 以上のモデルアーキテクチャファイルまで、ライブラリのすべてのソースファイルがここに列挙されています。

flowchart LR
    subgraph "src/CMakeLists.txt lists all files"
        CORE["Core modules\nllama-context.cpp\nllama-model.cpp\nllama-graph.cpp\n...20+ files"]
        MODELS["Model architectures\nmodels/llama.cpp\nmodels/qwen2.cpp\nmodels/mamba.cpp\n...110+ files"]
    end
    CORE -.-> MODELS

src/ 内の命名規則は一貫しています。すべてのコアモジュールは llama-{module}.h / llama-{module}.cpp というパターンに従っています。たとえば llama-batch.h はバッチ処理、llama-kv-cache.h は KV キャッシュ、llama-sampler.h はサンプリングチェーンを定義しています。目的の機能を grep で探しやすい構造になっています。

C API ファサードパターン

llama.cpp は include/llama.h を通じて純粋な C API を公開しています。このヘッダは不透明な構造体型と、llama_* という統一された命名規則に従う関数を宣言しています。

struct llama_model;     // opaque
struct llama_context;   // opaque
struct llama_sampler;   // opaque

実装は src/llama.cpp にあり、薄い委譲レイヤーとして機能します。各公開 llama_* 関数は、対応する内部 C++ クラスのメソッドに処理を転送するだけです。たとえば llama_decode()llama_context::decode() に委譲します。

classDiagram
    class llama_h["llama.h (C API)"] {
        <<header>>
        llama_model_load_from_file()
        llama_init_from_model()
        llama_decode()
        llama_sampler_sample()
    }
    class llama_cpp["llama.cpp (Facade)"] {
        <<delegation>>
        Forwards to C++ classes
    }
    class llama_model["llama_model (C++)"] {
        load_hparams()
        load_tensors()
        build_graph()
        create_memory()
    }
    class llama_context_impl["llama_context (C++)"] {
        decode()
        encode()
        process_ubatch()
    }
    llama_h --> llama_cpp : "declares"
    llama_cpp --> llama_model : "delegates"
    llama_cpp --> llama_context_impl : "delegates"

なぜ C API なのでしょうか。理由は 3 つあります。第一に、C は ABI が安定しており、コンシューマーを再コンパイルせずに libllama をアップグレードできます。また Python、Rust、Go、C# など任意の言語からバインディングを作れます。第二に、明確な境界を強制できます。テンプレート、仮想ディスパッチ、RAII といった C++ の複雑さはすべて内部に閉じ込められます。第三に、ヘッダの漏れを防ぎます。利用者が必要とするのは llama.h と GGML ヘッダだけで、内部の llama-*.h ファイルは 20 以上あっても一切不要です。

ヒント: llama_decode() のような公開関数の実装を追うときは、まず src/llama.cpp を確認しましょう。そこから委譲先の C++ クラスとメソッドを特定できます。

3 つの重要な型

llama.cpp のすべてが、include/llama.h で宣言された 3 つの型を中心に成り立っています。

llama_model はロードされたモデルを表します。アーキテクチャのメタデータ(ハイパーパラメータ)、語彙、そしてバックエンドバッファに確保された重みテンソルを保持します。モデルはロード後イミュータブルで、複数のコンテキスト間で共有できます。内部構造は src/llama-model.h に定義されており、アーキテクチャの enum、ハイパーパラメータ、レイヤーごとのテンソルポインタ、オフロードに使用するデバイスリストを格納しています。

llama_context は推論セッションを表します。KV キャッシュ(またはその他のメモリ型)、計算バッファ、バックエンドスケジューラ、バッチサイズやスレッド数といったランタイムパラメータを所有します。コンテキストはモデルから生成し、すべてのミュータブルな状態はここに集約されます。定義は src/llama-context.h にあります。

llama_sampler はトークン選択パイプラインを表します。サンプラーはコンポーザブルで、温度スケーリング、top-k、top-p、繰り返しペナルティなどの戦略をチェーンとして組み合わせられます。チェーンの定義は src/llama-sampler.h にあります。

classDiagram
    class llama_model {
        +arch: llm_arch
        +hparams: llama_hparams
        +vocab: llama_vocab
        +layers: vector~llama_layer~
        +tok_embd: ggml_tensor*
        +output: ggml_tensor*
        +build_graph()
        +create_memory()
    }
    class llama_context {
        +model: const llama_model&
        +memory: llama_memory_i*
        +sched: ggml_backend_sched_t
        +decode()
        +process_ubatch()
    }
    class llama_sampler_chain {
        +samplers: vector~info~
        +cur: vector~llama_token_data~
    }
    llama_model "1" <-- "*" llama_context : "references (immutable)"
    llama_context "1" --> "1" llama_sampler_chain : "uses"

所有関係は重要です。モデルはすべてのコンテキストより長く生存します。複数のコンテキストが 1 つのモデルを共有でき、これはそれぞれ独立した KV キャッシュを持つ並列推論に便利です。コンテキストはモデルへの const 参照を借用するだけで、重みを変更することはありません。

推論ライフサイクルのウォークスルー

ロードから出力まで、1 トークンを生成するための完全なシーケンスを示します。

sequenceDiagram
    participant App as Application
    participant API as llama.h
    participant Model as llama_model
    participant Ctx as llama_context
    participant GGML as GGML Backend

    App->>API: llama_model_load_from_file()
    API->>Model: load_hparams(), load_tensors()
    Model-->>App: llama_model*

    App->>API: llama_init_from_model()
    API->>Ctx: construct(model, params)
    Ctx->>Model: create_memory() → KV cache
    Ctx-->>App: llama_context*

    App->>API: llama_tokenize("Hello")
    API-->>App: [token_ids]

    App->>API: llama_decode(batch)
    API->>Ctx: decode(batch)
    Ctx->>Ctx: balloc->init(batch)
    Ctx->>Ctx: memory->init_batch()
    loop For each ubatch
        Ctx->>Model: build_graph(params)
        Model->>GGML: ggml_backend_sched_alloc_graph()
        Ctx->>GGML: set_inputs() + graph_compute()
        GGML-->>Ctx: logits
    end
    Ctx-->>App: logits ready

    App->>API: llama_sampler_sample()
    API-->>App: next_token

    App->>API: llama_detokenize()
    API-->>App: "world"

ステップ 1:モデルのロード。 llama_model_load_from_file() は GGUF ファイルを読み込み、メタデータからアーキテクチャを解析してハイパーパラメータをロードします。さらにテンソル名を内部の llama_layer 構造にマッピングし、重みデータをバックエンドバッファ(CPU、GPU、またはその両方)に確保します。

ステップ 2:コンテキストの生成。 llama_init_from_model()llama_context を構築します。このとき、モデル上の create_memory() が呼ばれ、適切なメモリ実装が選択されます。Transformer には KV キャッシュ、Mamba/RWKV にはリカレント状態、あるいはハイブリッドです。また最悪ケースのグラフ推定を使って計算バッファも予約されます。

ステップ 3:トークナイズ。 llama_tokenize() はモデルに埋め込まれた語彙を使ってテキストをトークン ID に変換します。語彙の種類(BPE、SentencePiece、WordPiece など)によってアルゴリズムが決まります。

ステップ 4:デコード。 llama_decode() が実際の処理の中心です。バッチはサニタイズされてマイクロバッチ("ubatch")に分割され、各 ubatch は process_ubatch() で処理されます。計算グラフの構築 → バックエンドメモリの確保 → 入力テンソルのセット → 実行 → ロジットの取り出し、という流れです。

ステップ 5:サンプリング。 llama_sampler_sample() は直前のデコード呼び出しから得られたロジットを受け取り、サンプラーチェーンを通してトークンを選択します。

ステップ 6:デトークナイズ。 選択されたトークンをテキストに変換します。

コードベースの読み方ガイド

目的のコードを探すときに役立つ方法を紹介します。

まず src/CMakeLists.txt を確認する。 ライブラリ内のすべてのファイルが網羅されており、再帰的な grep よりも速く目的のファイルを見つけられることが多いです。

llama-*.h の命名パターンを活用する。 バッチシステムを理解したければ llama-batch.h を開きましょう。KV キャッシュなら llama-kv-cache.h。20 以上あるコアモジュールすべてにわたって一貫したパターンです。

モデルアーキテクチャを探す。 すべてのモデルは src/models/ 以下にファイルを持っています。ファイル名は GGUF のアーキテクチャ名に対応しています:LLaMA → models/llama.cpp、Qwen2 → models/qwen2.cpp、Mamba → models/mamba.cpp。ファイル名が分からない場合は src/llama-arch.cppLLM_ARCH_NAMES マップを検索しましょう。GGUF の文字列識別子と enum 値の対応表になっています。

公開 API 呼び出しを追う。 まず src/llama.cpp で関数を見つけ、委譲先の C++ クラスを確認してから、対応するモジュールファイルへと追っていきましょう。

探しているもの 参照先
公開 API のインターフェース include/llama.h
API 委譲レイヤー src/llama.cpp
モデルのロード/アーキテクチャディスパッチ src/llama-model.cpp
コンテキスト構築/デコードループ src/llama-context.cpp
グラフ構築ツールキット src/llama-graph.h
特定モデルの実装 src/models/{name}.cpp
アーキテクチャ enum/GGUF キー名 src/llama-arch.hsrc/llama-arch.cpp
KV キャッシュの内部構造 src/llama-kv-cache.hsrc/llama-kv-cache.cpp
GGML テンソル演算 ggml/include/ggml.h
バックエンドシステム ggml/src/ggml-backend-impl.h

ヒント: models/llama.cpp は、他の多くのモデルアーキテクチャが参照する基準実装です。モデルビルダーの動作を理解したいなら、まずこのファイルを読みましょう。

次のステップ

コードベースの構造についてのメンタルモデルが固まりました。次の記事では llama.cpp 設計の中核に踏み込みます。モデルアーキテクチャが GGML の計算グラフへどう変換されるかを掘り下げ、llm_graph_context ツールキットを解説します。さらに LLaMA のグラフビルダーを一行ずつ追いながら、Mamba や RWKV のような非 Transformer モデルも含む 120 以上のアーキテクチャが統合フレームワークへどう収まっているかを見ていきます。