ハードウェアとテンソル 入門

このトラックの他の primer はネットワークが何をしているかを扱った。 この primer はネットが乗っている基盤を扱う。短い 4 トピック:GPU vs CPU、なぜ現代モデルはみな GPU に住むのか;VRAM、いま「どれだけ大きいモデルを動かせるか」の最も固い制約;テンソル、深層学習の基本データ単位 —— 多次元配列;そしてバッチ・パディング・マスク、現実の凸凹したテキストを GPU フレンドリーな矩形に詰め直す実践技。実務ではこの primer は飛ばせない —— この先見る「CUDA out of memory」のエラーは、すべてこの語彙で書かれている。

01

GPU vs CPU

CPU は賢いが少数。GPU は単純だが多数。深層学習は後者向きだ。

現代の CPU は「コンピュータ」のプラトン的理想形だ。少数(ノート PC で 8 個、 ワークステーションで 64 個)の速くて柔軟なコア —— どれも分岐ロジック、複雑な命令、 雑多な記帳を高速にこなす。最適化されているのは「1 スレッドが多様な タスクの長い列を順番にこなす」こと。OS、データベース、Web サーバー —— ここは CPU の領土だ。

現代の GPU は逆だ。数千個の小さくて単純なコア —— NVIDIA H100 は 16,896 個の CUDA コアを持つ。各コアは分岐が苦手、一般的なロジックも苦手、 「隣のコアたちと同じ単純な演算をする」ときだけ速い。実行モデルは SIMD (単一命令、複数データ)あるいはより細粒な SIMT。アーキテクチャ全体が「1 つの演算を数千個のデータに並列適用」に最適化されている。

その第 2 のパターンはまさにニューラルネットそのものだ:

  • 行列積。Transformer の最下層の演算。4096×4096 行列同士の積は 670 億回の乗算 —— そのすべてを並列に走らせられる。
  • 要素ごとの活性化関数。ReLU、GELU、softmax —— 同じ関数を数百万個の値に独立に適用。
  • 正規化層。LayerNorm と RMSNorm は 1 軸で reduce してから 各要素を rescale —— これも「気まずいほど並列」。
16 個の数に x → 2x を適用tick = 0CPU —— 1 コア、逐次123456789101112131415160 / 16 doneGPU —— 16 コア、並列123456789101112131415160 / 16 done16 個の数、1 操作:各々を 2 倍。両方とも 0 から始まる。
1 / 4
CPU は賢いが少数。GPU は単純だが多数。深層学習は圧倒的に後者向きの仕事だ。

数字で感じたいなら:4096 × 4096 の行列積は、速い CPU で約 50 ms、H100 で 1 ms 未満。 この差はサイズに対して超線形に開く ——4k × 4k では GPU はまだ暖機すらしておらず、 CPU は既に飽和している。最先端 LLM の訓練は通常、数千枚の GPU を数か月 並列で回す。CPU でやれば数世紀かかる。だから現代モデルはみな GPU に住む。

GPU が苦手なこと、簡潔に:

  • 分岐が多いコード。異なる「コア」が異なる経路を取るなら、順番に こなすしかない —— 速いコアが遅いコアを待つ。深層学習側の対策は「分岐なしで書く」 こと:パディングとマスクで、どこも同じ演算を当てる。
  • 小さなワークロード。GPU 起動には費用がかかる。32 × 32 のような 小さな行列積は GPU の立ち上げが支配的になり、CPU の方が速い。実深層学習では 作業を束ねて起動コストを薄める。
  • CPU↔GPU のメモリ転送。システム RAM と GPU の VRAM(§2)の間は PCIe バスを通り、GPU 内部の帯域より大幅に遅い。データは GPU に留める。

簡単に:TPU は Google の専用テンソル加速器 —— GPU よりさらに特化、 「matmul + 活性化」パターンに最適化されている。Google 内のモデル訓練 (Gemini など)の大半はこの上で回る。機能的には同じアイデアのより過激な版: 単純で並列な算術と、制御されたメモリアクセスパターン。

Transformer では:各層は行列積(Q·K、attention·V、FFN 重み)と いくつかの要素演算(softmax、GELU、残差加算、LayerNorm)。順伝播全体は 「矩形のテンソル仕事を GPU に流す」。アーキテクチャを設計する人は暗黙に このハードウェア現実のために設計し、訓練する人は暗黙に「どれだけ GPU 時間を 確保できるか」を競う。

02

VRAM —— 最も固いボトルネック

これまで見た「CUDA out of memory」のすべては、ここで書かれた物語だ。

GPU は CPU が使うシステム RAM とは別の、自分専用のメモリを持つ。NVIDIA はそれを VRAM(新しいチップでは HBM)と呼ぶ。H100 で 80 GB、A100 で 40 か 80、4090 で 24、 4070 で 12。この数字が「そのカードでどれだけ大きいモデルを訓練 / 実行できるか」の 硬い制約だ。モデルと作業領域が入らなければ訓練できない。それだけだ。

70 億パラメータ、fp16(数 1 つで 2 バイト)のモデルで計算してみる:

推論(モデルだけ):
  重み                   7 × 10⁹ × 2 バイト  =  14 GB
                         ──────────────────
                         24 GB のコンシューマ GPU に余裕で収まる

訓練(モデル + 訓練の足場すべて):
  重み                   14 GB
  勾配                   14 GB     ← パラメータ 1 個に 1 つ
  Adam モーメンタム      14 GB     ← パラメータ 1 個に 1 つ
  Adam 分散              14 GB     ← パラメータ 1 個に 1 つ
  活性                   ~30 GB    ← バッチと seq_len 依存
                         ─────────
                         ≈ 86 GB  →  80 GB H100 に入らない
単 H100 —— 7B fp16 モデルの 80 GB VRAM 予算14 GB used (18%) 80 GB 上限重み (14 GB)勾配 (14 GB)オプティマイザ状態 (28 GB)活性 (~30 GB)推論のみ。7B 重みは 80 GB 中 14 GB —— 余裕たっぷり。
1 / 4
推論なら 7B モデルは 1 枚に余裕で収まる。同じモデルの訓練は勾配 + オプティマイザ状態 + 活性で 80 GB を超える。

ここに 2 つの驚きがある。1 つ目、同じモデルでも訓練は推論の約 5–10 倍の VRAMを使う —— オプティマイザが各パラメータに複数コピーを持ち、順伝播の活性が逆伝播のために 保存されるからだ。2 つ目、推論なら 24 GB のコンシューマカードに余裕で乗る 7B モデルが、訓練では 80 GB の H100 1 枚にすら収まらない。70B モデルは重みだけで 140 GB —— 確実にマルチ GPU 領域。

緩和策、おおむね人が手を伸ばす順:

  • 量子化(Quantization)。重みを低精度で保存する —— int8(バイト半減)、int4(1/4)、さらには 3-bit や 2-bit のスキーム。 fp16 で 14 GB の 7B モデルは int4 では 3.5 GB。推論では一般的;訓練では勾配が 高精度を望むため厄介。
  • 混合精度訓練。活性と勾配は fp16 か bf16、オプティマイザの 「マスタコピー」は安定性のため fp32 で保持。活性メモリはおよそ半減。
  • 勾配 / 活性チェックポイント。順伝播のすべての活性を 保存しない —— 数層ごとに保存し、逆伝播時に間を再計算する。 計算量とメモリのトレードオフ。
  • モデル分割 / テンソル並列。モデル自身を複数 GPU に分割する。 ZeRO、FSDP、DeepSpeed、Megatron —— 分散訓練フレームワークはどれもこの変種だ。
  • Flash attention。アテンションを再実装し、n × n の アテンション行列を VRAM に実体化しない。長文脈訓練で 20–80 GB 節約;今やすべての 現代 Transformer 実装に組み込まれている。

帯域は「メモリ」の物語のもう半分で、踏むまで誰も言わない。H100 の VRAM 帯域は 3 TB/s —— 巨大だが、それでも有限。多くのニューラルネット演算はメモリ律速: GPU は計算より VRAM から届くデータを待つ時間の方が長い。効率的なカーネル (FlashAttention、fused MLP など)の仕事の一部は「VRAM への往復を減らす」こと。 GPU 内部にもメモリ階層がある —— レジスタ、shared memory / L1 キャッシュ(SM ごと 256 KB)、L2 キャッシュ(全体で 50 MB)。VRAM よりずっと速いが、ずっと小さい。 パフォーマンス最適化の多くは「データを速いメモリに長く留める」こと。

Transformer では:あらゆるアーキテクチャ選択に VRAM の側面がある。KV キャッシュ(自己回帰デコードのために残された過去トークンの key と value) は batch × seq_len × n_layers × d_kv でスケールし、長文脈モデルでは 重みより大きいことも多い。KV キャッシュを縮めること(Grouped-Query Attention、 Multi-Query Attention、スライディング窓アテンション)は、現代 LLM が 128k や 1M トークンの文脈を扱える理由の半分だ。

03

テンソル —— 多次元配列

深層学習の基本データ単位。ネットワークを流れる値はすべてこれ。

フレームワーク、モデル、抽象を全部はがすと、残るのはテンソルだけ。 テンソルは数値の多次元配列。現代の ML スタック(PyTorch、JAX、TensorFlow、NumPy) は本質的に「テンソルを効率的に生成・整形・結合する関数の集まり」だ。 テンソルを理解すれば、LLM のコードがやっていることの 90% を理解できる —— どの行も基本テンソル操作だ。

次元のはしご:

  • 0-D(スカラ) —— 数 1 つ。shape = ()。 順伝播の最後に出る損失、学習率、確率。
  • 1-D(ベクトル) —— 数の 1 列。shape = (n,)。 1 単語の埋め込み(例:768 次元ベクトル)、層のバイアス。
  • 2-D(行列) —— 行 × 列。shape = (m, n)。 重み行列、1 文の埋め込み(系列長 × d_model)、共分散行列。
  • 3-D(テンソル) —— 行列の積み重ね。shape = (a, b, c)。 Transformer の日常形:(batch, seq, d_model)
  • より高次(n-D) —— 軸を増やす。マルチヘッドアテンションは 4-D (batch, heads, seq, head_dim)、動画データは 5-D (batch, time, channel, height, width)。
テンソル —— 次元のはしごを登るスカラ (0-D)3.14shape = ()0-D スカラ —— 数 1 つ。shape = ()。損失関数が返す値。
1 / 4
テンソルは多次元配列。スカラ → ベクトル → 行列 → 3D テンソル、その先へ。ニューラルネットを流れる値はすべてこのどれか。

テンソルに何かする前に必ず知っておくべき 3 つ:

  • Shape(形状)。整数のタプル —— (32, 512, 768) は 32 サンプル、各 512 トークン、トークンごとに 768 次元の隠れ状態。 ニューラルネットのバグはほぼすべて形状不一致だ。
  • Dtype(データ型)。精度:float32(4 バイト)、float16 / bfloat16(2 バイト)、int8(1 バイト)、bool。dtype の選択は半分 VRAM(§2)の判断、 半分は数値安定性の判断だ。
  • Device(デバイス)。バイトがどこに住むか —— cpucuda:0cuda:1 など。違うデバイス上のテンソル同士の 演算はエラー;先に .to(device) しないといけない。

時間の 95% を占める演算:

  • 要素ごと:+*tanhrelu。セルごとに適用、出力形状 = 入力形状。
  • リダクション:軸に沿って summeanmax(32, 512, 768).mean(dim=-1)(32, 512) —— 隠れ次元が潰れる。
  • Reshape:viewreshapepermutetranspose。データを変えずに軸を並べ替える。 Transformer の中の batch / seq / head 軸の入れ替えは全部これ。
  • 行列積:PyTorch / NumPy では @(32, 512, 768) @ (768, 768) → (32, 512, 768)。最大の演算; GPU はそのために作られている。
  • インデックス / スライス:x[:, 0, :] で各サンプルの 最初のトークンを取り出す。x[mask]mask が真の位置だけ。

ブロードキャストは初心者を混乱させ、その後は見えなくなる演算だ。(32, 512, 768) + (768,) と書くと、小さい方のテンソルが欠けている 次元に沿って自動的に「引き伸ばされ」、同じ (768,) のバイアス ベクトルがすべてのサンプルのすべての位置に足される。メモリはコピーされない、 ブロードキャストは仮想的だ。ニューラルネットコードのほぼすべての行が ブロードキャストで簡潔に書かれる。

Transformer では:入力トークンが(batch, seq, d_model) の形状のテンソルに埋め込まれる。 この形状はネットワーク内で保たれる —— 各層の出力は同じ形状の別のテンソルで、 数値が違うだけ。アテンション内部では、テンソルが一時的に(batch, heads, seq, d_head) に reshape されてマルチヘッドの内積を 並列計算、その後戻る。最終層が (batch, seq, vocab_size) に射影する —— 各位置で語彙上のロジットベクトル。すべてがテンソル演算。

04

バッチ、パディング、マスク

可変長テキストが実際にどう GPU フレンドリーな矩形に詰め直されるか。

現実のテキストはバラバラな長さの系列の集まり。現実の GPU は矩形のテンソルが 欲しい。3 つの実用的な仕掛けがその橋渡しをする —— Transformer 訓練ループの どの行にも出てくる。テキスト primer §1 で導入したアイデアを、ここでは仕組みごと 固める。

バッチ(Batch)。

1 文ずつ与えるのではなく、N 文を一度に与え、形状 (N, max_len)のテンソルに積み、GPU に N 個を並列処理させる。N ——バッチサイズ—— は深層学習で最も重要なハイパーパラメータの 1 つ。 バッチが大きいほど VRAM を消費するが、GPU を忙しく保ち、勾配をより平均し、 (適切な範囲で)訓練を速くする。典型値:数枚の GPU で 70B モデルを微調整するときは 32、 GPT-4 級モデルの事前訓練では数百万トークンのバッチ。具体的な数値は VRAM に 収まる範囲で決まる。

パディング(Padding)。

同じバッチ内の文は長さが違う。形状 (N, max_len) の矩形テンソルに積むには、 全文を同じ長さに揃える必要がある。短い文を特別な [PAD] トークンでパディングする —— 慣習として語彙の id 0。バッチで最も長い文がmax_len を決め、他はそこまで PAD トークンを追加する。

バッチ + パディング + マスク —— 可変長 → 矩形テンソル生バッチ(凸凹)len = 2hi.len = 5thedogrunsfast.len = 7acatsatonthemat.3 つの文、長さ 2、5、7。形が違って積めない。
1 / 3
文をテンソルに積むには長さを揃える必要がある。パディングが形を整え、マスクがモデルに「どこが本物か」を伝える。

パディングまわりの実用的なつまみが 2 つある。1 つ目、バッチごとの max_lenの選び方:データセット全体の最大長までパディングするのは無駄。各ミニバッチ内の 最大長まで(「動的パディング」)ならずっと節約できる。2 つ目、どちら側に詰めるか: 現代の Transformer の多くは訓練時に右パディング、自己回帰生成時に左パディング。 理由は繊細だが影響は大きい —— 間違えるとモデルは静かに意味のないものを学ぶ。

マスク(Mask)。

パディングは形状を直すが、新しい問題を生む:アテンションは既定で、本物トークンと PAD トークン間のアテンション重みを平気に計算する。モデルはパディング経由で情報を回すことを学んでしまう —— 無意味だ。修正はマスク —— 0/1 の並列テンソル(よく bool)で、モデルにどの位置が 本物かを伝える:

A 文:    ["hi", ".", PAD, PAD, PAD, PAD, PAD]
A mask:   [  1,   1,   0,   0,   0,   0,   0]

B 文:    ["the", "dog", "runs", "fast", ".", PAD, PAD]
B mask:   [   1,    1,     1,      1,    1,   0,   0]

アテンションブロック内で、マスクは softmax のに適用される:PAD 位置の アテンションスコアが −∞ に設定され、softmax 後の重みは正確に 0。 PAD トークンの出力への寄与は 0;モデルは「パディングなしの文だけで走らせた」のと 同じ挙動(無駄な計算を除いて)。

Transformer で一般的なマスクの 2 種類:

  • Padding mask —— 今説明したもの。本物 vs PAD を示す。 ほぼすべての Transformer に存在する。
  • 因果(autoregressive)マスク —— GPT のような decoder-only モデルで 使う。位置 t は位置 ≤ t しか参照できない。−∞ の 三角行列をアテンションスコアに加える。padding mask とは論理 AND で合成。

Transformer では:すべてのアテンション呼び出しがマスクを受け取る。 訓練時の典型バッチは両方を持つ:自己回帰構造のための因果マスクと、可変長のための padding mask。出力は正しく、GPU はきれいな矩形テンソルを見て、損失は本物トークン だけを数える(損失関数自体もマスクする —— PAD 位置の予測でモデルを罰しない)。 この目立たない配管こそが、「バッチ並列訓練」を成立させる基盤すべてだ。