<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Machine Learning on kenji.blog</title><link>http://kenji.blog/categories/machine-learning/</link><description>Recent content in Machine Learning on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ja</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 22:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/categories/machine-learning/index.xml" rel="self" type="application/rss+xml"/><item><title>【初心者向け】Transformerモデルの数学的構造を読み解く</title><link>http://kenji.blog/p/transformer-mathematical-structure/</link><pubDate>Fri, 11 Sep 2026 22:00:00 +0900</pubDate><guid>http://kenji.blog/p/transformer-mathematical-structure/</guid><description>&lt;img src="http://kenji.blog/p/transformer-mathematical-structure/img/eyecatch.jpg" alt="Featured image of post 【初心者向け】Transformerモデルの数学的構造を読み解く" />&lt;h1 id="はじめになぜtransformerの数学を学ぶのか">はじめに：なぜTransformerの数学を学ぶのか？
&lt;/h1>&lt;p>現代の自然言語処理（NLP）、そしてAI全体の歴史を塗り替えたと言っても過言ではないアーキテクチャが「Transformer」です。2017年にGoogleの研究者らによって発表された論文『Attention Is All You Need』で初めて提案されたこのモデルは、OpenAIのGPTシリーズ（ChatGPTの基盤技術）やGoogleのBERT、AnthropicのClaudeなど、現在世界を席巻している大規模言語モデル（LLM）の心臓部として機能しています。&lt;/p>
&lt;p>しかし、Transformerの仕組みについて「Attention（注意機構）を使って文脈を理解する」といった定性的な説明はよく見かけますが、その背後にある&lt;strong>数学的な構造&lt;/strong>について初心者向けに深く掘り下げた解説は意外と少ないのが現状です。AIがどのようにして「言葉」を「数式」として処理し、驚くほど自然な文章を生成しているのかを真に理解するためには、その数学的メカニズムを読み解くことが不可欠です。&lt;/p>
&lt;p>本記事では、数学やプログラミングの基礎知識を持つ方（高校レベルの行列や微分の概念がわかる方）を対象に、Transformerの心臓部である「Self-Attention機構」「クエリ・キー・バリュー（Q/K/V）モデル」「Softmax関数による正規化」、そして「Positional Encoding」などの数学的構造を徹底的に、かつわかりやすく解き明かしていきます。&lt;/p>
&lt;p>数式の羅列に圧倒されるかもしれませんが、一つ一つの計算には明確な「意味」があります。この記事を読み終える頃には、Transformerが単なる魔法のブラックボックスではなく、精緻に設計された数学と統計の結晶であることが理解できるはずです。&lt;/p>
&lt;hr>
&lt;h1 id="1-従来手法の限界とtransformerの革新性">1. 従来手法の限界とTransformerの革新性
&lt;/h1>&lt;p>Transformerが登場する前、自然言語処理の主流はリカレントニューラルネットワーク（RNN）や、その派生であるLSTM（Long Short-Term Memory）でした。RNNは時系列データを処理するために設計されており、文章を単語ごとに先頭から順番に読み込んでいきます。&lt;/p>
&lt;p>しかし、RNNには致命的な弱点が2つありました。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>長期依存性の学習が困難&lt;/strong>：文章が長くなると、最初の方に入力された単語の情報が、最後の方に到達するまでに薄れてしまう（勾配消失問題）。&lt;/li>
&lt;li>&lt;strong>並列計算が不可能&lt;/strong>：単語を順番に処理しなければならないため、GPUを用いた大規模な並列計算が難しく、学習に膨大な時間がかかる。&lt;/li>
&lt;/ol>
&lt;p>Transformerは、RNNの構造を完全に捨て去り、「Attention」のみを用いて文脈を捉えるというパラダイムシフトを起こしました。これにより、系列長がどれだけ長くても情報の損失がなく、かつ計算を並列化してGPUの性能を最大限に引き出すことが可能になったのです。&lt;/p>
&lt;hr>
&lt;h1 id="2-transformerの全体アーキテクチャ">2. Transformerの全体アーキテクチャ
&lt;/h1>&lt;p>まずは、Transformer全体のアーキテクチャを俯瞰してみましょう。Transformerは大きく分けて「Encoder（エンコーダ）」と「Decoder（デコーダ）」の2つのブロックから構成されています。翻訳タスクを例にとると、Encoderが入力言語（例：英語）を数学的なベクトル表現に変換し、Decoderがそのベクトル表現をもとに出力言語（例：日本語）を生成します。&lt;/p>
&lt;p>以下の図は、Encoderブロックの内部構造を簡略化したものです。&lt;/p>
&lt;div class="mermaid">graph TD
A["Input Tokens"] --> B["Input Embedding"]
B --> C["Positional Encoding"]
C --> D["Multi-Head Self-Attention"]
D --> E["Add &amp; Layer Normalization"]
E --> F["Feed Forward Network"]
F --> G["Add &amp; Layer Normalization"]
G --> H["Output to Next Layer"]
C -.->|"Residual Connection"| E
E -.->|"Residual Connection"| G&lt;/div>
&lt;p>ここからは、各コンポーネントで行われている数学的な操作を順を追って見ていきましょう。&lt;/p>
&lt;hr>
&lt;h1 id="3-単語のベクトル化と位置エンコーディングpositional-encoding">3. 単語のベクトル化と位置エンコーディング（Positional Encoding）
&lt;/h1>&lt;p>コンピュータはテキストをそのまま理解することはできません。入力されたテキストはまず「トークン（Token）」と呼ばれる単位に分割され、それぞれが固定長のベクトルに変換されます。これが&lt;strong>Input Embedding&lt;/strong>です。&lt;/p>
&lt;h2 id="31-input-embeddingの数学">3.1 Input Embeddingの数学
&lt;/h2>&lt;p>語彙（ボキャブラリー）のサイズを $V$、埋め込みベクトルの次元数を $d_{model}$（元の論文では $d_{model} = 512$）とします。各単語 $w_i$ は、埋め込み行列 $W_E \in \mathbb{R}^{V \times d_{model}}$ を用いてベクトル $x_i \in \mathbb{R}^{d_{model}}$ に変換されます。&lt;/p>
$$ x_i = W_E \cdot \text{one\_hot}(w_i) $$
&lt;p>これにより、文章全体は行列 $X \in \mathbb{R}^{N \times d_{model}}$ として表現されます（$N$ は文章の長さ）。&lt;/p>
&lt;h2 id="32-positional-encoding位置エンコーディングの必要性と数式">3.2 Positional Encoding（位置エンコーディング）の必要性と数式
&lt;/h2>&lt;p>TransformerはRNNのように単語を順番に処理するわけではなく、すべての単語を同時に並列処理します。これは計算速度の観点では大きなメリットですが、同時に**「単語の順番（語順）」という重要な情報が失われてしまう**という問題を引き起こします。例えば、「犬が人を噛む」と「人が犬を噛む」は、入力される単語集合は同じですが意味は全く異なります。&lt;/p>
&lt;p>この語順情報をモデルに与えるために考案されたのが&lt;strong>Positional Encoding&lt;/strong>です。
位置 $pos$ にある単語の $i$ 番目の次元に対するPositional Encoding $PE$ は、以下の三角関数を用いて計算されます。&lt;/p>
$$ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$
$$ PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$
&lt;p>ここで、$pos$ は単語の位置（$0, 1, 2, \dots, N-1$）、$i$ はベクトルの次元のインデックス（$0, 1, \dots, d_{model}/2 - 1$）です。&lt;/p>
&lt;h3 id="なぜサインとコサインを使うのか">なぜサインとコサインを使うのか？
&lt;/h3>&lt;p>一見すると非常に複雑で奇妙な数式に見えますが、これには深い数学的な理由があります。三角関数を使用することで、モデルは**「絶対的な位置」だけでなく「相対的な位置」の差分**を容易に学習できるようになるのです。&lt;/p>
&lt;p>高校数学で学ぶ三角関数の加法定理を思い出してください。
&lt;/p>
$$ \sin(\alpha + \beta) = \sin\alpha \cos\beta + \cos\alpha \sin\beta $$
$$ \cos(\alpha + \beta) = \cos\alpha \cos\beta - \sin\alpha \sin\beta $$
&lt;p>ある位置 $pos$ からオフセット $k$ だけ離れた位置 $pos + k$ のPositional Encodingは、位置 $pos$ のPositional Encodingの線形結合として表すことができます。つまり、行列 $M_k$ を用いて次のように書けます。&lt;/p>
$$ PE_{pos+k} = M_k \cdot PE_{pos} $$
&lt;p>これにより、Attention機構は単語同士が「どれくらい離れているか」という相対距離を、内積計算を通じて簡単に認識できるようになります。また、波長が異なる複数のサイン・コサイン波を組み合わせることで、どんなに長い文章であっても一意の位置ベクトルを生成できるという利点もあります。&lt;/p>
&lt;p>最終的な入力行列 $X_{input}$ は、単語の埋め込みベクトルにこの位置エンコーディングを足し合わせたものになります。&lt;/p>
$$ X_{input} = X + PE $$
&lt;hr>
&lt;h1 id="4-self-attention自己注意機構の深淵なる数学">4. Self-Attention（自己注意機構）の深淵なる数学
&lt;/h1>&lt;p>いよいよTransformerの最重要コンポーネントである**Self-Attention（自己注意機構）**に踏み込みます。Self-Attentionの目的は、「文章内のすべての単語同士の関連度を計算し、各単語のベクトルを、文脈を考慮したより豊かな表現に更新すること」です。&lt;/p>
&lt;p>ここでは、「検索システム」のアナロジーが使われます。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Query (Q)&lt;/strong>: クエリ（検索語）。「私が今探している情報は何か？」&lt;/li>
&lt;li>&lt;strong>Key (K)&lt;/strong>: キー（見出し）。「私が持っている情報は何か？」&lt;/li>
&lt;li>&lt;strong>Value (V)&lt;/strong>: バリュー（実体）。「私が実際に提供する情報は何か？」&lt;/li>
&lt;/ul>
&lt;h2 id="41-行列-q-k-v-の生成">4.1 行列 $Q, K, V$ の生成
&lt;/h2>&lt;p>入力行列 $X \in \mathbb{R}^{N \times d_{model}}$（ここでは話を簡単にするためバッチサイズは無視します）に対して、学習可能な重み行列 $W^Q, W^K, W^V \in \mathbb{R}^{d_{model} \times d_k}$ を掛け合わせることで、クエリ $Q$、キー $K$、バリュー $V$ を計算します。（通常 $d_k = d_v = d_{model} / h$）&lt;/p>
$$ Q = X W^Q $$
$$ K = X W^K $$
$$ V = X W^V $$
&lt;p>ここで、$Q, K, V$ はすべて $\mathbb{R}^{N \times d_k}$ の行列になります。&lt;/p>
&lt;h2 id="42-attention-scoreの計算内積">4.2 Attention Scoreの計算（内積）
&lt;/h2>&lt;p>各単語のQueryが、他のすべての単語のKeyとどれくらい関連しているかを測るために、ベクトルの&lt;strong>内積&lt;/strong>を計算します。行列演算で書くと以下のようになります。&lt;/p>
$$ \text{Scores} = Q K^T $$
&lt;p>この計算により得られる行列 $\text{Scores} \in \mathbb{R}^{N \times N}$ の各要素 $s_{ij}$ は、$i$ 番目の単語のQueryと $j$ 番目の単語のKeyの内積、すなわち「関連度の強さ」を表します。&lt;/p>
&lt;h2 id="43-スケーリングscale">4.3 スケーリング（Scale）
&lt;/h2>&lt;p>内積によるスコア計算には一つの問題があります。ベクトルの次元 $d_k$ が大きくなると、内積の値が極端に大きくなったり小さくなったりしてしまうのです。&lt;/p>
&lt;p>これを数学的に証明しましょう。
クエリの各要素 $q \sim \mathcal{N}(0, 1)$、キーの各要素 $k \sim \mathcal{N}(0, 1)$ が独立した標準正規分布に従うと仮定します。
内積 $q \cdot k = \sum_{i=1}^{d_k} q_i k_i$ の平均と分散を求めます。
平均： $\mathbb{E}[q_i k_i] = \mathbb{E}[q_i] \mathbb{E}[k_i] = 0 \times 0 = 0$ なので、和の平均も $0$。
分散： $q_i k_i$ の分散は、独立性から $\text{Var}(q_i k_i) = \mathbb{E}[(q_i k_i)^2] - (\mathbb{E}[q_i k_i])^2 = 1 \times 1 - 0 = 1$。
したがって、内積全体の分散は次元数 $d_k$ に等しくなります。&lt;/p>
$$ \text{Var}(q \cdot k) = d_k $$
&lt;p>分散が大きくなると、この後適用するSoftmax関数において、最大値以外の勾配が極端に小さくなる「勾配消失」が発生し、学習が進まなくなってしまいます。
これを防ぐため、スコアを $\sqrt{d_k}$ で割る（スケーリングする）ことで、分散を常に $1$ に保つようにします。&lt;/p>
$$ \text{Scaled Scores} = \frac{Q K^T}{\sqrt{d_k}} $$
&lt;h2 id="44-softmax関数による確率化">4.4 Softmax関数による確率化
&lt;/h2>&lt;p>得られたスコアを、合計が $1$ になるような確率分布（重み）に変換するために &lt;strong>Softmax関数&lt;/strong> を行ごとに適用します。&lt;/p>
$$ a_{ij} = \text{softmax}(s_i)_j = \frac{\exp(s_{ij} / \sqrt{d_k})}{\sum_{m=1}^N \exp(s_{im} / \sqrt{d_k})} $$
&lt;p>行列 $A \in \mathbb{R}^{N \times N}$ はAttention Weight（注意の重み）行列と呼ばれます。この行列の各行 $i$ を見ると、「単語 $i$ を理解する上で、他のどの単語 $j$ にどれくらい注目（Attention）すべきか」が 0 から 1 の値で表現されています。&lt;/p>
&lt;h2 id="45-valueの重み付き和">4.5 Valueの重み付き和
&lt;/h2>&lt;p>最後に、得られたAttention Weight行列 $A$ を用いて、Value行列 $V$ の重み付き和を計算します。&lt;/p>
$$ \text{Output} = A V = \text{softmax}\left(\frac{Q K^T}{\sqrt{d_k}}\right) V $$
&lt;p>この演算によって出力される行列 $Z \in \mathbb{R}^{N \times d_v}$ は、「文脈を考慮して更新された単語のベクトル表現」の集まりとなります。
これが、論文で定義されている &lt;strong>Scaled Dot-Product Attention&lt;/strong> の全容です。&lt;/p>
&lt;hr>
&lt;h1 id="5-multi-head-attentionマルチヘッドアテンション">5. Multi-Head Attention（マルチヘッド・アテンション）
&lt;/h1>&lt;p>一回のAttention計算（シングルヘッド）だけでは、一つの観点（例えば「文法的な関係」）しか文脈を捉えられない可能性があります。そこで、言語の持つ多様な意味的・構文的関係（「主語と述語」「代名詞とその指示先」など）を同時に捉えるために、&lt;strong>Multi-Head Attention&lt;/strong> が導入されました。&lt;/p>
&lt;p>先ほどの $Q, K, V$ の生成とAttention計算を、並列に $h$ 回（ヘッドの数。元の論文では $h=8$）行います。&lt;/p>
$$ \text{head}_i = \text{Attention}(X W_i^Q, X W_i^K, X W_i^V) $$
&lt;p>ここで、$W_i^Q, W_i^K, W_i^V \in \mathbb{R}^{d_{model} \times d_k}$ は $i$ 番目のヘッド専用の学習可能な重み行列です。&lt;/p>
&lt;p>各ヘッドから出力された結果 $\text{head}_i \in \mathbb{R}^{N \times d_v}$ は、横に結合（Concatenate）されます。&lt;/p>
$$ \text{Concat}(\text{head}_1, \dots, \text{head}_h) \in \mathbb{R}^{N \times (h \cdot d_v)} $$
&lt;p>通常 $h \cdot d_v = d_{model}$ となるように設定されるため、結合後の次元は再び入力と同じ $d_{model}$ に戻ります。最後に、この行列に重み行列 $W^O \in \mathbb{R}^{d_{model} \times d_{model}}$ を掛けて、最終的な出力を得ます。&lt;/p>
$$ \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h) W^O $$
&lt;div class="mermaid">graph TD
X["Input X"] --> Q1["Q1"]
X --> K1["K1"]
X --> V1["V1"]
Q1 &amp; K1 &amp; V1 --> H1["Head 1"]
X --> Q2["Q2"]
X --> K2["K2"]
X --> V2["V2"]
Q2 &amp; K2 &amp; V2 --> H2["Head 2"]
X --> QN["..."]
X --> KN["..."]
X --> VN["..."]
QN &amp; KN &amp; VN --> HN["Head h"]
H1 &amp; H2 &amp; HN --> C["Concatenate"]
C --> WO["Multiply by WO"]
WO --> OUT["Multi-Head Output"]&lt;/div>
&lt;hr>
&lt;h1 id="6-feed-forward-neural-network-ffn">6. Feed-Forward Neural Network (FFN)
&lt;/h1>&lt;p>Multi-Head Attentionの出力は、次に &lt;strong>Position-wise Feed-Forward Network (FFN)&lt;/strong> に入力されます。
これは、系列の「各位置（単語）ごとに独立して」適用される2層の全結合ニューラルネットワークです。&lt;/p>
&lt;p>数式で表すと以下のようになります。&lt;/p>
$$ \text{FFN}(x) = \max(0, x W_1 + b_1) W_2 + b_2 $$
&lt;p>ここで、$\max(0, z)$ はReLU（Rectified Linear Unit）活性化関数を表しています（最近のモデルではGELUやSwiGLUが使われることも多いです）。&lt;/p>
&lt;p>このネットワークの役割は非常に重要です。Attention機構は「単語間の関係性（空間的・系列的な関係）」を学習しますが、FFNは「各単語ベクトル自体の非線形な特徴変換」を担当します。
通常、第一層の重み $W_1$ によって次元を一時的に大きく拡大（例えば $d_{model}=512$ から $d_{ff}=2048$ に4倍拡大）し、特徴空間上で複雑な計算を行った後、第二層の重み $W_2$ で再び元の次元に戻します。この「次元の拡大と縮小」によって、モデルの表現力が飛躍的に高まっています。&lt;/p>
&lt;hr>
&lt;h1 id="7-残差接続residual-connectionとlayer-normalization">7. 残差接続（Residual Connection）とLayer Normalization
&lt;/h1>&lt;p>ディープラーニングにおいて、ネットワークの層を深くしていくと、学習時に勾配が消失または爆発してしまい、うまく学習できなくなる問題が発生します。これを防ぐために、Transformerの各サブレイヤー（AttentionとFFN）の周囲には、&lt;strong>残差接続（Residual Connection）&lt;/strong> と &lt;strong>Layer Normalization（層正規化）&lt;/strong> が配置されています。&lt;/p>
&lt;p>数式で書くと、サブレイヤーの出力は次のように処理されます。&lt;/p>
$$ \text{Output} = \text{LayerNorm}(x + \text{Sublayer}(x)) $$
&lt;h2 id="71-残差接続-x--textsublayerx">7.1 残差接続 ($x + \text{Sublayer}(x)$)
&lt;/h2>&lt;p>入力 $x$ をサブレイヤーの出力に直接足し合わせます。これにより、逆伝播時に勾配がショートカットを通って直接浅い層に伝わるため、層を深くしても学習が安定します。&lt;/p>
&lt;h2 id="72-layer-normalizationの数学">7.2 Layer Normalizationの数学
&lt;/h2>&lt;p>Layer Normalizationは、特徴次元方向の平均と分散を計算し、データを正規化する技術です。バッチサイズ $B$、系列長 $N$、次元数 $d_{model}$ の入力において、ある一つの単語ベクトル $x \in \mathbb{R}^{d_{model}}$ に対して正規化を行います。&lt;/p>
&lt;p>平均 $\mu$ と分散 $\sigma^2$ を計算します。
&lt;/p>
$$ \mu = \frac{1}{d_{model}} \sum_{i=1}^{d_{model}} x_i $$
$$ \sigma^2 = \frac{1}{d_{model}} \sum_{i=1}^{d_{model}} (x_i - \mu)^2 $$
&lt;p>そして、正規化された出力 $\hat{x}$ を得ます。
&lt;/p>
$$ \text{LN}(x) = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} \odot \gamma + \beta $$
&lt;p>
（$\epsilon$ はゼロ除算を防ぐための微小な定数。$\gamma, \beta$ は学習可能なスケールとシフトのパラメータ）&lt;/p>
&lt;p>バッチ方向の正規化（Batch Normalization）ではなく、層方向の正規化（Layer Normalization）を採用した理由は、文章のように長さが一定でない系列データを処理する際、バッチ間の統計量が不安定になりやすいためです。Layer Normalizationにより、Transformerはバッチサイズに依存せずに安定した学習が可能になります。&lt;/p>
&lt;hr>
&lt;h1 id="8-デコーダ特有の構造masked-attentionとcross-attention">8. デコーダ特有の構造：Masked AttentionとCross-Attention
&lt;/h1>&lt;p>ここまで解説した構造はエンコーダのものです。文章を生成するデコーダブロックでは、構造が少し異なります。&lt;/p>
&lt;h2 id="81-masked-multi-head-attention">8.1 Masked Multi-Head Attention
&lt;/h2>&lt;p>デコーダの役割は「過去の単語から次の単語を予測すること」です。したがって、訓練時に「未来の単語」を見てしまってはカンニングになってしまいます。これを防ぐための数学的操作が &lt;strong>Masking（マスキング）&lt;/strong> です。&lt;/p>
&lt;p>スコア行列 $Q K^T$ に対して、上三角部分（未来の情報に相当）に $-\infty$ に近い非常に小さな値を設定するマスク行列 $M$ を足し合わせます。&lt;/p>
$$ M_{ij} = \begin{cases} 0 &amp; (i \le j) \\ -\infty &amp; (i > j) \end{cases} $$
$$ \text{Masked Attention}(Q, K, V) = \text{softmax}\left(\frac{Q K^T + M}{\sqrt{d_k}}\right) V $$
&lt;p>Softmax関数を計算する際、$\exp(-\infty) = 0$ となるため、未来の単語へのAttention Weightは完全に $0$ になります。これにより、因果関係（Causality）を保持した自己回帰的な生成が可能になります。&lt;/p>
&lt;h2 id="82-encoder-decoder-cross-attention">8.2 Encoder-Decoder Cross-Attention
&lt;/h2>&lt;p>デコーダの2つ目のサブレイヤーは、エンコーダからの出力を参照する &lt;strong>Cross-Attention&lt;/strong> です。
ここでは、$Q$ は一つ前のデコーダ層から生成されますが、$K$ と $V$ はエンコーダの最終層の出力から生成されます。&lt;/p>
$$ Q_{decoder} = X_{dec} W^Q $$
$$ K_{encoder} = X_{enc} W^K $$
$$ V_{encoder} = X_{enc} W^V $$
&lt;p>この計算により、翻訳タスクなどで「今翻訳している単語が、元の外国語の文章のどの部分に強く関連しているか」をモデルが学習できるようになります。&lt;/p>
&lt;hr>
&lt;h1 id="9-計算量と現代における最適化の数学">9. 計算量と現代における最適化の数学
&lt;/h1>&lt;p>Transformerは素晴らしいモデルですが、その数学的構造ゆえの「弱点」も存在します。
Self-Attentionの計算量に注目してください。スコア行列 $Q K^T$ の計算では、$(N \times d_k)$ の行列と $(d_k \times N)$ の行列を掛け合わせるため、その計算量は &lt;strong>$O(N^2 \cdot d_{model})$&lt;/strong> となります。&lt;/p>
&lt;p>つまり、&lt;strong>系列長 $N$ に対して計算量とメモリ使用量が二乗で増加する&lt;/strong>のです。
文章が短い場合は問題になりませんが、書籍丸ごと一冊のような長大なコンテキストをLLMに入力しようとすると、$N$ が数万〜数十万に達し、従来のAttention計算ではGPUのメモリが即座に枯渇してしまいます。&lt;/p>
&lt;p>この $O(N^2)$ の呪いを断ち切るために、近年では数学的・ハードウェア的アプローチから様々な最適化が提案されています。
その代表例が &lt;strong>FlashAttention&lt;/strong> です。FlashAttentionは、GPUのメモリ階層（SRAMとHBM）間のデータ転送（メモリアクセス）を最小限に抑えるよう、Attention計算をタイル状に分割（Tiling）して行うアルゴリズムです。数式上は標準的なAttentionと全く同じ結果を出力する（Exact Attention）にもかかわらず、ハードウェアレベルの最適化により劇的な高速化とメモリ削減を実現し、GPT-4などの長文脈モデルの実現を可能にしました。&lt;/p>
&lt;p>他にも、計算量を $O(N \log N)$ や $O(N)$ に近似する Sparse Attention や Linear Attention などの研究も盛んに行われています。&lt;/p>
&lt;hr>
&lt;h1 id="10-実装のイメージpytorch風の擬似コード">10. 実装のイメージ（PyTorch風の擬似コード）
&lt;/h1>&lt;p>ここまでの数学的構造を、実際のプログラミングコード（Python / PyTorch）に落とし込むと、驚くほどシンプルに記述できることがわかります。Self-Attentionのコア部分の擬似コードを示します。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">torch.nn.functional&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">F&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">math&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">scaled_dot_product_attention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mask&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># q, k, vの形状: [batch_size, num_heads, seq_length, d_k]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">d_k&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">q&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 1. 内積によるスコア計算: Q * K^T&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 最後の2つの次元を転置して行列積を計算&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">matmul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">transpose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 2. スケーリング&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">scores&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">math&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sqrt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">d_k&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 3. マスキング (Masked Attentionの場合)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">mask&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">scores&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">masked_fill&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mask&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mf">1e9&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 4. Softmaxによる確率化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">attention_weights&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">softmax&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">scores&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 5. Value行列の掛け合わせ&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">matmul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">attention_weights&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">attention_weights&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>数式で表された $Q K^T / \sqrt{d_k}$ が &lt;code>torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k)&lt;/code> として直感的に実装されていることがわかります。数学の理論が、高度な最適化ライブラリの力を借りて数行のコードで実現されるのは、ディープラーニングの非常に面白い側面です。&lt;/p>
&lt;hr>
&lt;h1 id="おわりに数式から見えてくる知能の形">おわりに：数式から見えてくる「知能」の形
&lt;/h1>&lt;p>本記事では、Transformerモデルの深奥にある数学的構造を解き明かしてきました。&lt;/p>
&lt;p>単語を多次元ベクトル空間にマッピングするEmbedding、位置情報を三角波の合成で表現するPositional Encoding、そして情報検索のアナロジーから生まれた行列の内積計算であるSelf-Attention機構。これら一つ一つのコンポーネントは、線形代数、微積分、確率統計といった基礎的な数学の積み重ねにすぎません。&lt;/p>
&lt;p>しかし、これらの単純な行列演算が何層にも重なり、何十億、何千億というパラメータを通じて巨大なデータセットからパターンを学習するとき、そこには私たちの「言葉」を解し、論理的な推論を行い、時に創造的なアイデアを生み出すかのような「知能の形」が立ち現れます。&lt;/p>
&lt;p>「Attention Is All You Need」という挑発的なタイトルが示す通り、複雑なリカレント処理や畳み込み処理を捨て去り、純粋な「アテンション（関連度）」の計算に特化したこのアーキテクチャの美しさは、その数学的なシンプルさにこそあると言えるでしょう。&lt;/p>
&lt;p>今後、Transformerを超える新たなアーキテクチャ（State Space ModelであるMambaなど）が登場する可能性もありますが、Transformerが築き上げた「Attentionによる文脈理解」の数学的枠組みは、AIの歴史に永遠に刻まれるはずです。&lt;/p>
&lt;p>もしあなたが今後、ChatGPTやClaudeなどのLLMを使う機会があれば、そのバックグラウンドで毎秒何兆回もの $Q K^T$ の行列積が計算され、Softmax関数が確率を弾き出している様子を想像してみてください。技術に対する解像度が上がり、よりAIの世界が面白く感じられるはずです。&lt;/p>
&lt;h3 id="参考文献">参考文献
&lt;/h3>&lt;ul>
&lt;li>Vaswani, A., et al. (2017). &amp;ldquo;Attention Is All You Need.&amp;rdquo; &lt;em>Advances in Neural Information Processing Systems&lt;/em>.&lt;/li>
&lt;li>Alammar, J. (2018). &amp;ldquo;The Illustrated Transformer.&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;em>この記事は、自然言語処理とAIの数学的基礎を学ぶ方々へのガイドとして執筆されました。質問や議論があれば、ぜひコメント欄でお知らせください！&lt;/em>&lt;/p></description></item><item><title>llama.cppの量子化技術（GGUF）の仕組みを解説</title><link>http://kenji.blog/p/llama-cpp-quantization-gguf/</link><pubDate>Fri, 11 Sep 2026 00:00:00 +0900</pubDate><guid>http://kenji.blog/p/llama-cpp-quantization-gguf/</guid><description>&lt;img src="http://kenji.blog/p/llama-cpp-quantization-gguf/img/eyecatch.jpg" alt="Featured image of post llama.cppの量子化技術（GGUF）の仕組みを解説" />&lt;h2 id="1-はじめになぜllmには量子化が必要なのか">1. はじめに：なぜLLMには量子化が必要なのか？
&lt;/h2>&lt;p>近年の大規模言語モデル（LLM: Large Language Models）の進化は目覚ましいものがありますが、その裏で「計算資源の枯渇」と「メモリ帯域のボトルネック」という深刻な問題が浮上しています。例えば、Llama 3 のような 70B（700億）パラメータのモデルを、標準的な 16ビット浮動小数点（FP16）でメモリにロードした場合、パラメータだけで約 140GB の VRAM/RAM を消費します。これに推論時のコンテキスト（KVキャッシュ）が加わると、データセンター向けのハイエンドGPU（NVIDIA A100 80GB や H100 80GB）を複数台クラスタリングしなければ動作しません。&lt;/p>
&lt;p>個人開発者やエッジデバイス（MacBookや一般的なゲーミングPC）でLLMを動作させるための救世主として登場したのが &lt;strong>llama.cpp&lt;/strong> とその中核を成す &lt;strong>量子化（Quantization）技術&lt;/strong> です。特に &lt;strong>GGUF (GPT-Generated Unified Format)&lt;/strong> というファイルフォーマットと、&lt;strong>k-quants&lt;/strong> と呼ばれる高度なブロック単位の量子化アルゴリズムは、モデルの精度（Perplexity）の低下を極限まで抑えつつ、モデルサイズを数分の一に圧縮する画期的な手法です。&lt;/p>
&lt;p>本記事では、この llama.cpp における量子化の数学的背景から、GGML形式との違い、GGUFフォーマットの詳細な構造、そして k-quants の内部メカニズムに至るまで、徹底的に解説します。&lt;/p>
&lt;hr>
&lt;h2 id="2-量子化quantizationの数学的基礎">2. 量子化（Quantization）の数学的基礎
&lt;/h2>&lt;p>LLMの文脈における量子化とは、連続的な値（あるいは高精度の浮動小数点数）を、より少ないビット数（INT8, INT4, INT3 など）の離散的な値にマッピングする操作を指します。&lt;/p>
&lt;h3 id="21-線形量子化の基本数式">2.1. 線形量子化の基本数式
&lt;/h3>&lt;p>最も単純なアプローチは線形量子化（Min-Max量子化）です。元の高精度の重みテンソルを $W$、量子化された整数テンソルを $W_q$ とします。&lt;/p>
$$ W_q = \text{round}\left( \frac{W}{S} \right) + Z $$
&lt;p>ここで、&lt;/p>
&lt;ul>
&lt;li>$S$ は &lt;strong>スケールファクタ（Scale Factor）&lt;/strong> であり、量子化のステップサイズ（解像度）を決定します。&lt;/li>
&lt;li>$Z$ は &lt;strong>ゼロポイント（Zero-point）&lt;/strong> であり、実数の $0.0$ が量子化後のどの整数値に対応するかをシフトするためのバイアス値です。&lt;/li>
&lt;li>$\text{round}(\cdot)$ は最近接整数への丸め関数です。&lt;/li>
&lt;/ul>
&lt;p>逆量子化（Dequantization）により、推論時には近似的な実数重み $\tilde{W}$ を復元します。&lt;/p>
$$ \tilde{W} = S \times (W_q - Z) $$
&lt;h3 id="22-対称量子化-vs-非対称量子化">2.2. 対称量子化 vs 非対称量子化
&lt;/h3>&lt;p>ゼロポイント $Z$ の扱いにより、大きく二つの方式に分かれます。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>非対称量子化 (Asymmetric Quantization)&lt;/strong>
データの最小値 $W_{\min}$ と最大値 $W_{\max}$ を用いてマッピングします。
&lt;/p>
$$ S = \frac{W_{\max} - W_{\min}}{2^b - 1}, \quad Z = \text{round}\left(-\frac{W_{\min}}{S}\right) $$
&lt;p>
ここで $b$ は量子化ビット数（例：4ビットなら $2^4-1 = 15$）。$Z$ を保持する必要があるため、計算とメモリのオーバーヘッドがわずかに増えます。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>対称量子化 (Symmetric Quantization)&lt;/strong>
データの絶対値の最大値を用いて、ゼロを中心にマッピングします（$Z=0$）。
&lt;/p>
$$ S = \frac{\max(|W_{\max}|, |W_{\min}|)}{2^{b-1} - 1}, \quad Z = 0 $$
&lt;p>
llama.cppの初期の量子化（例えばレガシーな Q4_0 など）は対称量子化を採用しており、$Z$ の項がないためSIMD命令での内積計算が非常に高速化されるという利点があります。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="3-ggmlからggufへの進化とファイル構造">3. GGMLからGGUFへの進化とファイル構造
&lt;/h2>&lt;p>llama.cpp を語る上で欠かせないのが、C++で書かれたテンソル演算ライブラリ &lt;strong>GGML&lt;/strong> と、そこから派生したファイルフォーマット &lt;strong>GGUF&lt;/strong> です。&lt;/p>
&lt;h3 id="31-ggmlの課題">3.1. GGMLの課題
&lt;/h3>&lt;p>初期の llama.cpp は &lt;code>ggml&lt;/code> フォーマット（および &lt;code>ggjt&lt;/code> などの亜種）を使用していました。しかし、これらには以下の問題がありました。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>拡張性の欠如:&lt;/strong> マジックナンバーやハイパーパラメータが固定長・固定順序でハードコードされており、新しいモデルアーキテクチャ（例: Llama, Falcon, Mixtral など）や新しいトークナイザを追加するたびに破壊的変更が生じた。&lt;/li>
&lt;li>&lt;strong>後方互換性の喪失:&lt;/strong> フォーマットが頻繁にアップデートされ、古いモデルファイルが最新の llama.cpp で読み込めなくなる事態が多発しました。&lt;/li>
&lt;/ul>
&lt;h3 id="32-ggufフォーマットの誕生">3.2. GGUFフォーマットの誕生
&lt;/h3>&lt;p>2023年8月に導入された &lt;strong>GGUF&lt;/strong> は、これらの問題を解決するために設計された汎用性の高いフォーマットです。最大の特徴は、&lt;strong>キー・バリュー（Key-Value）ベースのメタデータ構造&lt;/strong> を採用したことです。&lt;/p>
&lt;p>以下の Mermaid 図は、GGUFのファイル構造を抽象化したものです。&lt;/p>
&lt;div class="mermaid">graph TD
A["GGUF File"] --> B["Header (Magic, Version)"]
A --> C["Metadata (Key-Value Pairs)"]
A --> D["Tensor Info (Name, Shape, Offset)"]
A --> E["Tensor Data (Binary payload)"]
C --> C1["general.architecture: llama"]
C --> C2["llama.context_length: 4096"]
C --> C3["tokenizer.ggml.tokens: [...]"]
E --> E1["Layer 0 Weights"]
E --> E2["Layer 1 Weights"]
E --> E3["..."]&lt;/div>
&lt;p>&lt;strong>GGUFの主な利点:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>柔軟性:&lt;/strong> モデルのハイパーパラメータやRoPE（Rotary Positional Embedding）の設定、トークナイザの語彙データなどをすべて名前付きの Key-Value ペアとして格納。未知のキーは無視されるため、新機能の追加が容易。&lt;/li>
&lt;li>&lt;strong>エンディアン非依存:&lt;/strong> GGUFはデフォルトでリトルエンディアンを採用していますが、明示的にフラグを持つため異なるアーキテクチャ間でも安全にポータブルです。&lt;/li>
&lt;li>&lt;strong>mmap(メモリマッピング)への最適化:&lt;/strong> テンソルデータは特定の境界でアライメント（パディング）されており、OSの &lt;code>mmap()&lt;/code> システムコールを用いてディスクから直接メモリ空間にマップ可能です。これにより、モデル読み込みの初期化時間が事実上ゼロになります。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="4-k-quants-の深淵高度なブロック単位量子化">4. k-quants の深淵：高度なブロック単位量子化
&lt;/h2>&lt;p>GGUFフォーマットの真骨頂は、モデルウェイトの圧縮を担う &lt;strong>k-quants (K-quantization)&lt;/strong> という仕組みです。&lt;/p>
&lt;p>通常のニューラルネットワークの重みは、層全体で見ると正規分布に近い形をしていますが、局所的には外れ値（Outliers）が存在します。一律のスケールファクタ $S$ で層全体の重みを量子化すると、外れ値に引きずられて小さな重みの情報が完全に失われてしまいます。&lt;/p>
&lt;p>これを防ぐため、llama.cppでは &lt;strong>ブロック単位量子化（Block-wise Quantization）&lt;/strong> を行います。重みテンソルを小さなブロック（例えば 32要素 や 256要素）に分割し、ブロックごとに固有のスケールファクタ（およびゼロポイント）を持たせるのです。&lt;/p>
&lt;h3 id="41-レガシーな量子化q4_0-q4_1の限界">4.1. レガシーな量子化（Q4_0, Q4_1）の限界
&lt;/h3>&lt;p>初期の &lt;code>Q4_0&lt;/code> は、32個のFP16重みを1つのブロックとし、1つのFP16スケールファクタを共有していました。&lt;/p>
&lt;ul>
&lt;li>ブロックサイズ: 32&lt;/li>
&lt;li>メモリ: 1つのスケール(16bit) + 32個の4bitウェイト(128bit) = 144bit&lt;/li>
&lt;li>要素あたりの実効ビット数 (bpw: bits per weight): $144 / 32 = 4.5$ bpw&lt;/li>
&lt;/ul>
&lt;p>これでも十分に優秀ですが、精度と圧縮率の限界が見えてきました。そこで登場したのが、より複雑で精巧な階層構造を持つ &lt;strong>k-quants&lt;/strong> です。&lt;/p>
&lt;h3 id="42-スーパーブロックとサブブロックの階層構造q4_k_m-の例">4.2. スーパーブロックとサブブロックの階層構造（Q4_K_M の例）
&lt;/h3>&lt;p>k-quants は、大きな「スーパーブロック（Super-block）」と、その内側に含まれる小さな「サブブロック（Sub-block）」という階層構造を持ちます。これにより、メタデータ（スケール値など）自体の量子化も行い、極限まで bpw を下げつつ精度を保ちます。&lt;/p>
&lt;p>最も人気のある設定である &lt;strong>Q4_K_M&lt;/strong> の構造を見てみましょう。Q4_K_M では、256要素のスーパーブロックを使用します。&lt;/p>
&lt;div class="mermaid">graph TD
A["Super-block (256 weights)"] --> B["Scale metadata (FP16/INT8)"]
A --> C["Sub-block 0 (32 weights, 4-bit)"]
A --> D["Sub-block 1 (32 weights, 4-bit)"]
A --> E["..."]
A --> F["Sub-block 7 (32 weights, 4-bit)"]
B --> B1["Super-scale (FP16)"]
B --> B2["Sub-scales (8 x 6-bit)"]
B --> B3["Sub-mins (8 x 6-bit)"]&lt;/div>
&lt;p>C++（GGML）における実際の構造体は以下のように定義されています。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// llama.cpp における block_q4_K の概念的な構造
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#define QK_K 256
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">struct&lt;/span> &lt;span class="nc">block_q4_K&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// スーパーブロック全体のスーパー・スケール (FP16 x 2 など)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">scales&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 8つのサブブロック(32要素ずつ)の 6-bit スケールと 6-bit 最小値(ゼロポイント)をパックしたデータ
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">qs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">QK_K&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 4-bit で量子化されたウェイトデータ (256要素 / 2 = 128 bytes)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>数学的なデキュー（Dequantization）処理：&lt;/strong>&lt;/p>
&lt;p>サブブロック $i$（$0 \le i &lt; 8$）内の要素 $j$（$0 \le j &lt; 32$）の近似的な実数値 $\tilde{W}_{i, j}$ は、以下のように計算されます。&lt;/p>
$$ \tilde{W}_{i, j} = S_{\text{super}} \times s_i \times (w_{i, j} - m_i) $$
&lt;ul>
&lt;li>$S_{\text{super}}$: スーパーブロック全体の浮動小数点スケール&lt;/li>
&lt;li>$s_i$: サブブロック $i$ 用に量子化された 6-bit スケール&lt;/li>
&lt;li>$m_i$: サブブロック $i$ 用に量子化された 6-bit 最小値（ゼロポイント）&lt;/li>
&lt;li>$w_{i, j}$: 4-bit の量子化ウェイト ($0 \dots 15$)&lt;/li>
&lt;/ul>
&lt;p>この階層構造により、外れ値への適応力を維持しながら、スケールファクタ自体が占めるメモリ量を劇的に削減しています。Q4_K_M は全体で &lt;strong>4.8 bpw&lt;/strong> 程度を実現します。&lt;/p>
&lt;h3 id="43-多様な-k-quants-オプション">4.3. 多様な k-quants オプション
&lt;/h3>&lt;p>llama.cpp は目的に応じて多数のバリエーションを提供しています。「K」の後ろのサフィックス（S, M, L）はサイズの大小を表します。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">フォーマット&lt;/th>
&lt;th style="text-align:center">BPW (Bits per Weight)&lt;/th>
&lt;th style="text-align:left">概要と特徴&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q2_K&lt;/strong>&lt;/td>
&lt;td style="text-align:center">2.5～3.3&lt;/td>
&lt;td style="text-align:left">極限まで圧縮。精度低下が著しいが、VRAMが極端に少ない環境用。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q3_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">3.3&lt;/td>
&lt;td style="text-align:left">3ビット量子化の標準。Q4よりは劣化するが許容範囲内に収まることが多い。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q4_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">4.8&lt;/td>
&lt;td style="text-align:left">&lt;strong>推奨されるスイートスポット&lt;/strong>。モデルサイズの半減と精度の維持を両立。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q5_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">5.5&lt;/td>
&lt;td style="text-align:left">より高精度を求める場合。Q4とFP16の中間的な立ち位置。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q6_K&lt;/strong>&lt;/td>
&lt;td style="text-align:center">6.6&lt;/td>
&lt;td style="text-align:left">FP16とほぼ同等のPerplexityを維持するが、ファイルサイズは大きめ。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q8_0&lt;/strong>&lt;/td>
&lt;td style="text-align:center">8.5&lt;/td>
&lt;td style="text-align:left">INT8相当。主に推論時の計算用中間テンソルや、最終層でのみ使用される。&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>※実際のBPWは、モデルのテンソル（例えば Attention の Q/K/V プロジェクションか、FFNの重みか）によって、混合量子化（Mixed Quantization）が行われるため、モデル全体で平均化されます。重要なテンソルはQ6で、それ以外をQ4で量子化するなどの最適化が内部で行われています。&lt;/p>
&lt;hr>
&lt;h2 id="5-推論時のパフォーマンス最適化simdとcudaアーキテクチャ">5. 推論時のパフォーマンス最適化：SIMDとCUDAアーキテクチャ
&lt;/h2>&lt;p>GGUFモデルをメモリにロードしただけでは、推論は高速になりません。LLMの推論の大半は「行列積（Matrix-Vector Multiplication, 略して GEMV、あるいは Matrix-Matrix, GEMM）」です。量子化された重みと、FP16（またはFP32）で保持されているアクティベーション（入力データ）の積和演算をいかに高速化するかが鍵です。&lt;/p>
&lt;h3 id="51-cpu環境におけるsimd命令の活用">5.1. CPU環境におけるSIMD命令の活用
&lt;/h3>&lt;p>llama.cpp がCPU推論において驚異的な速度を誇るのは、アセンブリ・レベルでの &lt;strong>SIMD (Single Instruction, Multiple Data)&lt;/strong> 最適化にあります。
例えば Intel/AMD の CPU では &lt;strong>AVX2&lt;/strong> や &lt;strong>AVX-512&lt;/strong>、Apple Silicon では &lt;strong>ARM NEON&lt;/strong> 命令セットをフル活用します。&lt;/p>
&lt;p>推論中、わざわざ $W_q$ を FP32 に戻して（Dequantizeして）から掛け算を行うわけではありません。
アクティベーション側もブロック単位で動的に量子化（Dynamic Quantization、通常は INT8 へ量子化）し、&lt;strong>INT8 $\times$ INT4&lt;/strong> の整数演算を SIMD の特殊なドット積命令（例: &lt;code>vdpaddd&lt;/code> や &lt;code>_mm256_madd_epi16&lt;/code>）を用いて一気に計算します。最終的なアキュムレータで FP32 に戻してスケールファクタを掛けることで、驚異的なスループットを実現しています。&lt;/p>
&lt;h3 id="52-gpu環境-cublas--cuda-でのオフロード">5.2. GPU環境 (cuBLAS / CUDA) でのオフロード
&lt;/h3>&lt;p>最近の llama.cpp は CPU だけでなく、NVIDIA GPU に対する強力なサポート（CUBLAS / CUDA）も持っています。
GGUFファイルの一部または全部の層をVRAMにオフロードすることが可能です（&lt;code>--n-gpu-layers&lt;/code> オプション）。&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant User
participant CPU_RAM as CPU &amp; RAM (mmap)
participant VRAM as GPU VRAM
participant Compute as Tensor Cores
User->>CPU_RAM: Load GGUF (mmap)
CPU_RAM->>VRAM: Offload Layers (e.g. 30/32 layers)
Note over CPU_RAM, VRAM: Data remains quantized in VRAM
User->>Compute: Forward Pass (Input Tokens)
Compute->>VRAM: Fetch Quantized Weights
Compute->>Compute: Dequantize to FP16 on-the-fly in SRAM
Compute->>Compute: Matrix Multiplication (cuBLAS / Custom Kernels)
Compute->>User: Output Logits&lt;/div>
&lt;p>GPU上で計算する場合、VRAMの帯域幅（Memory Bandwidth）が最大のボトルネックになります。重みが k-quants で圧縮されているため、VRAMからGPUの演算ユニット（SM: Streaming Multiprocessor や Tensor Cores）へのデータ転送量が 1/3 ～ 1/4 に削減されます。計算ユニットに重みが到達した瞬間に、オンザフライでFP16にデキュー（展開）され、Tensor Core を使って超高速に行列積が実行されます。
つまり、量子化は**「計算量を減らす」ためではなく「メモリ転送量を減らす」ために行われている**と言えます。&lt;/p>
&lt;hr>
&lt;h2 id="6-メモリ使用量と性能のトレードオフの具体例">6. メモリ使用量と性能のトレードオフの具体例
&lt;/h2>&lt;p>ここで、Llama 3 8B モデルを例に、GGUFの量子化レベル別の要求スペックを見てみましょう。（数値はおおよその目安です）&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">モデル/量子化&lt;/th>
&lt;th style="text-align:left">ファイルサイズ&lt;/th>
&lt;th style="text-align:left">必要なVRAM/RAM&lt;/th>
&lt;th style="text-align:left">推論速度(目安)&lt;/th>
&lt;th style="text-align:left">Perplexity劣化&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (FP16)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">約 16 GB&lt;/td>
&lt;td style="text-align:left">18 GB以上&lt;/td>
&lt;td style="text-align:left">基準&lt;/td>
&lt;td style="text-align:left">なし (Base)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q8_0)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">約 8.5 GB&lt;/td>
&lt;td style="text-align:left">10 GB以上&lt;/td>
&lt;td style="text-align:left">高速&lt;/td>
&lt;td style="text-align:left">ほぼゼロ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q6_K)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">約 6.6 GB&lt;/td>
&lt;td style="text-align:left">8 GB以上&lt;/td>
&lt;td style="text-align:left">非常に高速&lt;/td>
&lt;td style="text-align:left">極小&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q4_K_M)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">約 4.9 GB&lt;/td>
&lt;td style="text-align:left">6.5 GB以上&lt;/td>
&lt;td style="text-align:left">最速・最適&lt;/td>
&lt;td style="text-align:left">許容範囲・微小&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q3_K_M)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">約 3.9 GB&lt;/td>
&lt;td style="text-align:left">5.5 GB以上&lt;/td>
&lt;td style="text-align:left">最速&lt;/td>
&lt;td style="text-align:left">やや目立つ&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q2_K)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">約 3.0 GB&lt;/td>
&lt;td style="text-align:left">4.5 GB以上&lt;/td>
&lt;td style="text-align:left">高速&lt;/td>
&lt;td style="text-align:left">明らかな劣化&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>注意点 (KVキャッシュの影響):&lt;/strong>
LLMの推論において、コンテキスト長（プロンプトのトークン数）が長くなると、モデルの重みだけでなく、過去のAttention状態を保存する &lt;strong>KVキャッシュ&lt;/strong> のメモリ消費が爆発的に増加します。
例えばコンテキストが 8192 トークンの場合、KVキャッシュだけで数GBを消費します。したがって、実運用では &lt;code>モデルファイルサイズ + 約1.5GB～3GB&lt;/code> のマージン（Headroom）を確保しておく必要があります。Q4_K_M が推奨される理由は、このKVキャッシュを確保しても、一般的な 8GB VRAM 搭載の GPU（RTX 3060 / 4060 など）で安全に動作する絶妙なラインだからです。&lt;/p>
&lt;p>最近の llama.cpp では、この &lt;strong>KVキャッシュ自体を Q8_0 や Q4_0 で量子化する機能&lt;/strong> も追加されており、コンテキスト長をさらに伸ばすための工夫が絶え間なく行われています。&lt;/p>
&lt;hr>
&lt;h2 id="7-まとめ">7. まとめ
&lt;/h2>&lt;p>本記事では、llama.cpp の心臓部である GGUF フォーマットと k-quants 量子化技術の内部構造について、深く掘り下げて解説しました。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>GGUFの柔軟性:&lt;/strong> キー・バリュー型のメタデータ構造により、LLMの急速な進化（新しいモデルアーキテクチャの登場）にも破壊的変更なしに追従できる強固なエコシステムを構築しました。&lt;/li>
&lt;li>&lt;strong>k-quantsによる極限圧縮:&lt;/strong> スーパーブロックとサブブロックの階層的なスケールファクタ管理により、外れ値の情報を保持しつつ、重み1つあたり平均 4.8 ビット（Q4_K_M）という驚異的な圧縮を実現しました。&lt;/li>
&lt;li>&lt;strong>メモリ帯域ネックの解消:&lt;/strong> SIMDやCUDAにおける高度なカーネル実装により、オンザフライでデキューしながら計算を行うことで、VRAM転送量を削減し、推論スピードを劇的に向上させました。&lt;/li>
&lt;/ol>
&lt;p>AIの民主化を推し進める llama.cpp の技術力は、単なるツールの枠を超え、現代のソフトウェア・エンジニアリングの最高峰の一つと言っても過言ではありません。量子化アルゴリズムやGGUFフォーマットの仕組みを理解することで、ご自身の環境に最適なモデルの選択や、パフォーマンスチューニングがより正確に行えるようになるでしょう。&lt;/p>
&lt;h3 id="参考リンク">参考リンク
&lt;/h3>&lt;ul>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/llama.cpp" target="_blank" rel="noopener"
>llama.cpp GitHub Repository&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/ggml/blob/master/docs/gguf.md" target="_blank" rel="noopener"
>GGUF Format Specification&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/llama.cpp/pull/1684" target="_blank" rel="noopener"
>K-quants Implementation PR&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>（おわり）&lt;/p></description></item></channel></rss>