AIシステムアーキテクチャ
ノートPC上で大規模モデルのデモは動いたし、結果も良好だった。しかし、それを実際の製品にしようとすると、問題が生じる。
モデルのパラメータ数が大きすぎて、GPU1枚に収まらない場合はどうする?
-
トレーニングに数週間かかるが、途中で障害が発生した場合、どうやってチェックポイントから再開するのか?
-
毎日数百万のユーザーリクエストがあるが、どうやって遅延を1秒以内に保証するのか?
-
ユーザーフィードバックを収集して、モデルを継続的に進化させるにはどうすればよいか?
これらの問題は、AIシステムアーキテクチャが解決すべきものだ。
デモは「動くかどうか」に焦点を当てるが、本番システムは「安定して、効率的に、低コストで動くかどうか」に焦点を当てる。
本番グレードのAIシステムの特徴: 7x24時間の可用性、百万単位の同時実行のサポート、可観測性、拡張性、信頼性の高い災害復旧、コスト管理。
AIシステム特有の課題
従来のWebサービスと比較して、AIシステムには3つの独自の課題がある。
課題1: 不確実な出力
従来のシステムの出力は決定的だ。1+1を入力すれば、常に2を返す。
AIシステムの出力は確率的だ。同じプロンプトでも、2回生成すると結果が異なる可能性がある。
これにはいくつかの問題がある: 出力品質をどう保証するか? 効果をどう評価するか? 幻覚(ハルシネーション)にどう対処するか?
解決策は通常、出力層でのサンプリング戦略、結果の後処理フィルタリング、人手によるフィードバックループ、複数モデルの投票などだ。
課題2: レイテンシとコストのトレードオフ
AI推論には大量の計算が必要であり、遅延とコストの間に自然な矛盾があることを意味する。
高速化したい? より多くのGPUを使うとコストが急上昇する。
コストを抑えたい? キューで処理すると、ユーザーエクスペリエンスが悪くなる。
本番システムの核心は、SLA(サービスレベル契約)とコストの間のバランスを見つけることだ。
| 最適化の方向性 | 一般的な技術 | 効果 |
|---|---|---|
| モデル圧縮 | 量子化、プルーニング、蒸留 | 速度が2〜4倍向上、精度はわずかに低下 |
| 推論最適化 | vLLM、TensorRT、FlashAttention | スループットが3〜10倍向上 |
| アーキテクチャ設計 | バッチ処理、多層キャッシュ | リクエスト単価が50%〜80%削減 |
課題3: データフライホイールの構築
AIシステムは「リリースして終わり」ではない。継続的な反復が必要だ。
ユーザーが使うほどフィードバックデータが増え、モデルをより良くトレーニングでき、ユーザーはさらに使いたくなる。これがデータフライホイールだ。
しかし、フライホイールを回すのは簡単ではない。効果的なフィードバックをどう収集するか? データをどうアノテーションするか? どう継続的にトレーニングするか? 新バージョンをどう評価するか?
これらの問題に標準的な答えはないが、成功したAI製品はそれぞれ独自のフライホイール設計を持っている。
大規模トレーニングインフラストラクチャ
数千億から数兆のパラメータを持つ大規模モデルをトレーニングするには、スーパーコンピューティング基盤が必要だ。
GPUクラスタアーキテクチャ
現代のAIトレーニングクラスタは通常、数百から数千のGPUで構成される。
H100 GPUを例にとると、H100 1枚には80GBのメモリがあり、演算性能はFP8精度で1979 TFLOPSだ。
しかしGPU1枚では全く不十分だ。GPT-3のトレーニングには約355枚のV100を使用し、3か月かかった。
典型的なクラスタトポロジーは次のとおり:
| 階層 | デバイス | 接続方法 | 帯域幅 |
|---|---|---|---|
| 単一ノード内 | GPU-GPU | NVLink | 900GB/s |
| 同一ラック内 | サーバー間 | InfiniBand | 400Gb/s |
| ラック間 | スイッチ間 | InfiniBand Fabric | 400Gb/s |
ネットワークは分散トレーニングのボトルネックだ。通信帯域幅が不十分だと、GPU利用率が90%から30%に低下し、ほとんどの時間をデータ待ちに費やすことになる。
InfiniBand高速相互接続
通常のイーサネットは遅すぎるため、分散トレーニングではInfiniBandを使用する。
InfiniBandの特徴は、極めて低い遅延(マイクロ秒レベル)、非常に高い帯域幅、RDMA(リモートダイレクトメモリアクセス)のサポートだ。
RDMAにより、GPUが別のサーバーのGPUメモリを直接読み書きでき、OSカーネルを経由する必要がないため、速度が大幅に向上する。
ストレージシステム設計
トレーニングデータは通常TBからPBレベルであり、ストレージシステムも重要だ。
典型的な階層ストレージ設計:
- ホットデータ: SSDまたはNVMe。現在トレーニング中のバッチを格納
- ウォームデータ: 分散ストレージ(Ceph、Lustreなど)。完全なトレーニングセットを格納
- コールドデータ: オブジェクトストレージ(S3など)。履歴データとバックアップを格納
フォールトトレランスとチェックポイント
トレーニングに数週間かかるが、その間にGPUが1枚故障したらどうする? 最初からやり直すのはあまりにも無駄だ。
解決策はチェックポイント(Checkpointing)定期的にモデルの状態をディスクに保存し、エラーが発生したら最新のチェックポイントから復元する。
ただし、チェックポイントにもコストがかかる。1回の保存に数分かかり、数十GBの容量を占有する可能性がある。
一般的な戦略は、数百ステップごとに保存し、最近の数個のチェックポイントを保持して、古いものは自動的に削除するというものだ。
分散トレーニング戦略
1枚のGPUには大規模モデルを収容できないため、トレーニングタスクを複数のGPUに分割する必要がある。
主に3つの並列戦略がある。データ並列、テンソル並列、パイプライン並列で、これらを組み合わせたものが3D並列だ。

データ並列処理(Data Parallelism)
最も簡単で最も一般的な戦略だ。各GPUに完全なモデルを配置し、異なるデータを処理する。
例えば8枚のGPUがあり、バッチサイズが1024の場合、各GPUは128件のデータを処理する。
順伝播は各GPUで独立に計算し、逆伝播後に勾配を集約して平均を取り、モデルを更新する。
データ並列の問題は、メモリが依然としてボトルネックであることだ。モデルが大きすぎてGPU1枚に収まらない場合、データ並列は役に立たない。
ZeROメモリ最適化
ZeRO(Zero Redundancy Optimizer)はデータ並列の拡張版であり、GPUメモリをさらに節約できる。
通常のデータ並列では、各GPUが完全なモデルパラメータ、勾配、オプティマイザ状態を格納する。これは非常に冗長だ。
ZeROの考え方は、これらの状態を異なるGPUに分割し、必要なときに通信するというものだ。
| ZeROステージ | 分割する内容 | メモリ節約量 |
|---|---|---|
| ZeRO-1 | オプティマイザ状態 | 約4倍 |
| ZeRO-2 | オプティマイザ状態 + 勾配 | 約8倍 |
| ZeRO-3 | オプティマイザ状態 + 勾配 + パラメータ | GPU数に比例 |
DeepSpeedでZeROを設定するのは簡単だ:
"train_batch_size": 1024,
"train_micro_batch_size_per_gpu": 16,
"optimizer": {
"type": "Adam",
"params": {
"lr": 0.0001,
"betas": [0.9, 0.95],
"eps": 1e-8,
"weight_decay": 0.01
}
},
"zero_optimization": {
"stage": 3,
"allgather_partitions": true,
"allgather_bucket_size": 2e8,
"overlap_comm": true,
"reduce_scatter": true,
"reduce_bucket_size": 2e8,
"contiguous_gradients": true,
"stage3_prefetch_bucket_size": 1e8,
"stage3_param_persistence_threshold": 1e5,
"stage3_max_live_parameters": 1e9,
"stage3_max_reuse_distance": 1e9
},
"gradient_clipping": 1.0,
"fp16": {
"enabled": true,
"loss_scale": 0,
"loss_scale_window": 1000,
"initial_scale_power": 16,
"hysteresis": 2,
"min_loss_scale": 1
},
"checkpoint": {
"tag": "サンプル-checkpoint",
"load_universal": true
}
}
この設定ではZeRO-3を使用しており、モデル状態をすべてのGPUに分散できるため、メモリ使用量はGPU数に応じて線形に減少する。
テンソル並列処理(Tensor Parallelism)
Pythonは簡潔で上品な言語であり、初心者に適しています。
機械学習はコンピュータにデータから法則を学習させます。
- これはまったく別のもう一つの記事です。
- 最後に結果を連結する。
こうすると各層の計算に通信が必要になるが、メモリ使用量は半分になる。
Megatron-LM は NVIDIA 製のテンソル並列ライブラリで、PyTorch との互換性が良い。
パイプライン並列処理(Pipeline Parallelism)
テンソル並列は「層内の分割」で、パイプライン並列は「層間の分割」だ。
たとえば 32 層のモデルなら、GPU 0 に先頭 8 層、GPU 1 に中間 8 層、GPU 2 に後半 8 層、GPU 3 に最後 8 層を置く。
データは GPU 0 から GPU 3 へ流れる。まるで工場の組み立てラインのように。
しかしパイプラインには問題がある:バブル(気泡)だ。GPU 0 が計算を始めている間、GPU 1〜3 は暇で、データが GPU 1 に届くと、今度は GPU 0 が暇になる。
解決策は、データを「マイクロバッチ」に分割し、パイプラインのように詰め込むことで、バブル時間を減らすことだ。
3D並列処理(DP+TP+PP)
3 つの戦略は組み合わせて使える:
- パイプライン並列:モデルの層をノード間で分割する
- テンソル並列:ノード内で層内計算を分割する
- データ並列:より広い範囲でパイプライン全体を複製する
たとえば 64 枚の GPU があるなら、次のように計画できる:
- 8 つのパイプラインステージ(PP=8)
- 各ステージ内で 2 枚の GPU を使ってテンソル並列を行う(TP=2)
- そして 4 倍に複製してデータ並列を行う(DP=4)
- 合計:8 × 2 × 4 = 64 枚の GPU
これが 3D 並列——現代の大規模モデル学習の標準構成だ。
データエンジニアリング
良いモデルの前提は良いデータ——データエンジニアリングは AI システムの作業量の 60% 以上を占める。
データ収集・クリーニングパイプライン
学習データは通常、ウェブページ、書籍、コード、会話など複数のソースから得られる。
典型的な処理フロー:
- 重複除去:重複または類似度の高い文書を削除する
- 品質フィルタリング:低品質・有害・偏った内容を除去する
- フォーマット統一:異なるソースを統一フォーマットに変換する
- トークン化:テキストをモデルの入力系列に変換する
データ重複排除: MinHash LSH
文書間の類似度を直接計算するのは遅すぎる。よく使われるのは MinHash + LSH(局所鋭敏ハッシュ)だ。
考え方:各文書を短い「フィンガープリント」に変換する。類似文書は高い確率で同じまたは近いフィンガープリントを持つので、フィンガープリントでグループ化する。
実例
import re
from typing import List, Set, Dict, Tuple
def generate_shingles(text: str, k: int = 5) -> Set[str]:
"""k-シングルを生成:連続する k 語からなる系列
例:「我爱 サンプル 教程」、k=2 → {"我爱", "爱 サンプル", "サンプル 教程"}
"""
# 簡単な分かち書き(実際の場面では専門の分かち書きツールを使用可能)
words = re.findall(r'\w+', text.lower())
shingles = set()
for i in range(len(words) - k + 1):
shingle = ' '.join(words[i:i+k])
shingles.add(shingle)
return shingles
def minhash_signature(shingles: Set[str], num_hashes: int = 100) -> List[int]:
"""MinHash 署名を生成
複数のハッシュ関数を使い、それぞれ最小値を取る
"""
signature = []
for i in range(num_hashes):
# i をシードにして異なるハッシュ関数を生成
min_hash = None
for shingle in shingles:
# shingle と i を組み合わせてハッシュ値を生成
h = hashlib.sha256(f"{shingle}-{i}".encode()).hexdigest()
h_int = int(h, 16)
if min_hash is None or h_int < min_hash:
min_hash = h_int
signature.append(min_hash)
return signature
def lsh_banding(signature: List[int], bands: int = 20) -> List[str]:
"""バンディング法(Banding)で LSH キーを生成
署名を複数のバンドに分割し、各バンドを個別にハッシュ化する
"""
keys = []
rows_per_band = len(signature) // bands
for i in range(bands):
start = i * rows_per_band
end = start + rows_per_band
band = tuple(signature[start:end])
# このバンドをハッシュ化してキーを生成
band_hash = hashlib.sha256(str(band).encode()).hexdigest()[:16]
keys.append(f"band-{i}-{band_hash}")
return keys
def deduplicate_documents(documents: List[str],
threshold: float = 0.7) -> List[str]:
"""MinHash + LSH で文書を重複除去
重複除去後の文書リストを返す
"""
# 格納:LSH キー → 文書インデックスリスト
buckets: Dict[str, List[int]] = {}
# 格納:文書インデックス → 署名
signatures: Dict[int, List[int]] = {}
# マーク:どの文書が重複しているか
duplicates: Set[int] = set()
for idx, doc in enumerate(documents):
shingles = generate_shingles(doc)
sig = minhash_signature(shingles)
signatures[idx] = sig
keys = lsh_banding(sig)
# 類似文書がすでに存在するか確認
is_duplicate = False
for key in keys:
if key in buckets:
# このバケットには既存文書があるので、署名の類似度を逐一比較
for other_idx in buckets[key]:
other_sig = signatures[other_idx]
# 署名の類似度を計算(Jaccard 近似)
matches = sum(1 for a, b in zip(sig, other_sig) if a == b)
similarity = matches / len(sig)
if similarity >= threshold:
# しきい値を超えたら重複とみなす
is_duplicate = True
duplicates.add(idx)
break
if is_duplicate:
break
if not is_duplicate:
# 重複ではないので、自分を各バケットに追加
for key in keys:
if key not in buckets:
buckets[key] = []
buckets[key].append(idx)
# 重複しない文書を返す
return [doc for idx, doc in enumerate(documents) if idx not in duplicates]
# ============================================
# サンプル データの重複除去をテスト
# ============================================
if __name__ == "__main__":
documents = [
サンプル チュートリアルへようこそ。ここはプログラミングを学ぶのに良い場所です。,
"サンプル チュートリアルへようこそ、ここはプログラミングを学ぶのに最適な場所です。", # 高い類似度
"Python は簡潔で優雅な言語であり、初心者に適しています。",
Pythonは簡潔で優雅なプログラミング言語であり、初心者に非常に適しています。, # 高い類似度
"機械学習はコンピュータにデータから規則性を学習させる。",
"これはまったく別の記事です。",
]
print(f"重複除去前:{len(documents)} 件の文書")
deduplicated = deduplicate_documents(documents, threshold=0.6)
print(f"重複除去後:{len(deduplicated)} 件の文書"\n")
print("保持された文書:")
for i, doc in enumerate(deduplicated):
print(f" [{i}] {doc}")
# 出力:
# 重複除去前:6 件の文書
# 重複除去後:4 件の文書
#
# 保持された文書:
#
#
#
#
実際の運用では、より効率的な実装(datasketch ライブラリなど)が使われるが、核となる考え方は同じだ。
データ形式: WebDataset
小さなデータセットはどこに保存してもよいが、TB 規模のデータセットには専用のフォーマットが必要だ。
WebDataset はよく使われる形式の一つだ。ファイルを tar パッケージにまとめ、各 tar には数千のサンプルが含まれ、ランダム読み取りと順次読み取りに対応している。
利点は:
- ファイルシステムへの負荷を減らせる(数百万の小ファイルは遅い)
- ストリーミング読み取りに対応し、データセット全体をメモリに読み込む必要がない
- 分散読み込みが可能で、各ワーカーが異なる tar を読む
データフライホイールの設計
データフライホイールは AI 製品の堀(モート)だ。ユーザーが増えるとデータが増え、モデルが良くなり、さらにユーザーが増える。
ユーザーフィードバックデータの収集
フィードバックには明示的と暗黙的の2種類がある:
- 明示的フィードバック:ユーザーのいいね、低評価、編集、再生成
- 暗黙的フィードバック:ユーザーの滞在時間、コピー、共有、セッション長
明示的フィードバックは品質が高いが量が少ない。暗黙的フィードバックは量が多いがノイズが大きい。
良いフィードバックシステムは両者の組み合わせだ——明示的フィードバックで報酬モデルを訓練し、暗黙的フィードバックで A/B テストを行う。
自動データアノテーション
人手によるアノテーションは高すぎて遅すぎる。現在のトレンドは「モデルでモデルをアノテーションする」ことだ。
よく使われる戦略:
- 強モデルが弱モデルをアノテーション:GPT-4 でアノテーションしたデータを使って小さなモデルを訓練するブートストラップ(Bootstrap):既存モデルで候補を生成し、人間が選別する
- 合成データ:モデルを使って多様な学習データを生成する継続的学習戦略
- モデルは「一度訓練して終わり」ではない。世界が変わるのだから、モデルもそれに合わせて変わる必要がある。典型的な継続的学習のプロセス:
継続的トレーニング戦略
毎週小さな更新を行う(SFT、教師ありファインチューニング)
毎月大きな更新を行う(継続事前学習 + SFT + RLHF)
- 更新のたびに A/B テストを行い、効果を確認してから全量展開する
- 継続的学習では「破壊的忘却」に注意が必要だ。新しいデータで訓練しすぎると、モデルが以前の能力を忘れてしまう恐れがある。解決策は「リプレイバッファ」を保持し、毎回の訓練で新旧データを混ぜることだ。
- エンタープライズ AI プラットフォームのアーキテクチャ
- 完全なエンタープライズ AI プラットフォームには、通常以下のコンポーネントが含まれる。
モデルレジストリとバージョン管理
エンタープライズAIプラットフォームアーキテクチャ
レジストリは以下を記録する必要がある:
モデルレジストリとバージョン管理
バージョン番号と変更履歴
学習データの出典とハイパーパラメータ
- 評価指標
- デプロイ状態
- MLflow、Weights & Biases、Hugging Face Hub はすべてよく使われるツールだ。
- 评估指标
- 部署状态
MLflow、Weights & Biases、Hugging Face Hub はすべてよく使われるツールです。
Feature Store
多くの AI アプリケーションはフィーチャーエンジニアリングを必要とする——ユーザープロフィール、履歴行動、コンテキスト情報など。
フィーチャーストアは特徴管理システムであり、いくつかの問題を解決する:
- トレーニング/推論スキュー:トレーニングと推論で使用する特徴が不一致
- 特徴の再利用:異なるモデルで特徴を共有できる
- オンライン/オフラインの一貫性:オフラインで計算した特徴を、オンラインで低遅延に読み取る
典型的なアーキテクチャは:オフラインは Spark や Flink でバッチ計算し、オンラインは Redis や Cassandra で低遅延に読み取る。
オンライン/オフライン推論サービス
推論には2つのシナリオがあり、アーキテクチャが異なる:
| シナリオ | 遅延要件 | アーキテクチャ | 例 |
|---|---|---|---|
| オンライン推論 | ミリ秒レベル | リアルタイム API + バッチ処理 | チャットボット、検索 |
| オフライン推論 | 時間/日レベル | バッチタスクキュー | コンテンツモデレーション、レポート生成 |
オンライン推論サービスでは以下を考慮する必要がある:
- バッチ処理(Batching):複数のリクエストをまとめて計算し、スループットを向上させる
- 動的バッチ処理:vLLM や Text Generation Inference を使用して動的バッチ処理を行う
- キャッシュ:K/V Cache で自己回帰生成を高速化し、人気のあるプロンプトの結果をキャッシュする
- 負荷分散:複数のモデルインスタンス、スマートルーティング
エンドツーエンド監視
AI システムの問題は発見しにくい——可観測性が必要。
監視のいくつかの次元:
- システムメトリクス:GPU 使用率、メモリ、遅延、スループット、エラーレート
- モデルメトリクス:出力長、繰り返し率、ストップワード分布
- ビジネスメトリクス:ユーザー満足度、維持率、タスク完了率
定番の組み合わせ:Prometheus + Grafana でメトリクス、ELK でログ、Jaeger でトレーシングを確認。
評価ベンチマーク体系
モデルが良くなったか悪くなったかをどう判断するか?評価体系が必要。
汎用能力ベンチマーク
学界でよく使われるベンチマーク:
| ベンチマーク | テスト内容 | 代表的なタスク |
|---|---|---|
| MMLU | 多タスク言語理解 | 57 の学科の選択問題 |
| HumanEval | コード生成 | 164 のプログラミング問題 |
| MBPP | コード生成 | 974 の Python 問題 |
| TruthfulQA | 事実性 | 817 の質問、幻覚のテスト |
| GSM8K | 数学的推論 | 8000 問の小学校算数の問題 |
中国語ベンチマーク
英語ベンチマークが良いからといって中国語も良いとは限らない。中国語専用のベンチマークが必要:
- C-Eval:中国語多タスク言語理解、13948 問の選択問題
- CMMLU:中国語マルチモーダル理解(現在は主にテキスト)
- AGIEval:中国の大学入試問題、公務員試験問題
カスタムビジネス評価セット
公開ベンチマークは基礎だが、より重要なのは自社の業務評価セット。
構築方法:
- 実際のユーザーリクエストを収集(数百〜数千件)
- 「良い/普通/悪い」を手動でラベル付け、またはスコアリング
- テストセットと検証セットに分割(テストセットは固定、変更しない)
- モデル更新のたびに実行して、メトリクスの変化を確認
業務評価セットはあなたの「ゴールドスタンダード」——どの公開ベンチマークよりも重要。
マルチテナントAIサービス
ToB 製品を展開する場合、マルチテナンシーは避けて通れない話題。
テナント分離
分離にはいくつかのレベルがある:
| 分離レベル | リソース | 利点 | 欠点 |
|---|---|---|---|
| 物理分離 | 独立した GPU マシン | 完全分離、安全 | コストが高い |
| K8s 分離 | 独立した Pod/Namespace | コストとセキュリティのバランス | スケジューリングが必要 |
| 論理分離 | リソース共有、権限制御 | コスト最小 | 漏洩リスクがある |
一般的な戦略:大企業は物理分離、中小企業は論理分離。
モデル共有とファインチューニング管理
マルチテナントでよくあるニーズ:自社のデータで専用モデルを学習する。
アーキテクチャは次をサポートする必要がある:
- ベースモデルの共有ストレージ
- テナントごとに LoRA アダプタを個別に保存
- 推論時に該当テナントの LoRA を動的にロード
これにより大量の GPU メモリを節約——テナントごとにフルモデルをロードする必要がない。
課金とクォータ
AI サービスのコストは高いため、課金は細かくする必要がある:
- トークン単位の課金(入力と出力を分けて計算)
- リクエスト回数単位の課金
- GPU 時間単位の課金(ファインチューニングシナリオ)
- クォータ制限(1 分あたりのリクエスト数、1 日あたりのトークン数)
災害復旧と高可用性
エンタープライズシステムは停止できない——高可用性設計が必要。
マルチアベイラビリティゾーン展開
最も基本的な要件:AZ(アベイラビリティゾーン)をまたいだデプロイ。
例:
- プライマリクラスタを AZ A に配置
- ホットスタンバイクラスタを AZ B に配置
- データの非同期レプリケーション
- ロードバランサーがトラフィックを自動切替
目標:単一 AZ が障害しても、サービスは中断せず、データは失われない。
モデルスナップショットとロールバック
新しいバージョンのモデルは問題が発生する可能性がある——迅速なロールバックが必要。
戦略:
- リリース前に毎回モデルスナップショットを保存
- 直近 N バージョンを保持
- 異常メトリクスを監視し、自動ロールバックを発動
- カナリアリリース:まず 1% のトラフィックを切り替え、問題なければ全量展開
縮退と流量制限
トラフィックが急増したら?停止するわけにはいかない、対策が必要:
- レート制限:クォータ超過時は 429 を返す
- ダウングレード:より小さいモデルを使う、またはキャッシュ結果を返す
- キューイング:ジョブ ID を返し、後で結果を確認
核心的な原則:優雅なダウングレードを行うこと、完全に利用不可にしないこと。
その他の拡張