Skip to content
Published on

NPU完全解剖:トランスフォーマーアーキテクチャはシリコン上でいかに動くか

Authors

はじめに:AIがポケットの中へ

ChatGPTが登場した当初、私たちはデータセンターにあるGPUのラックにリクエストを送る必要がありました。しかし2025年現在、iPhone 16でLlama 3.2 3Bモデルがリアルタイムで動作し、Apple Neural Engineが毎秒30トークン以上を生成します。

これを可能にしたのが**NPU(Neural Processing Unit)**です。

NPUは単なる小さなGPUではありません。汎用性を犠牲にして特定の演算で極限の効率を実現する、根本的に異なる設計哲学を持つ特化型アクセラレータです。この記事では、NPUとは何か、トランスフォーマーの各演算がシリコン上でどう動くのか、そして「メモリ帯域幅がTFLOPSより重要な理由」を完全に理解できるよう解説します。


1. CPU vs GPU vs NPU:設計哲学の三角形

CPU:「複雑なタスクを素早く、一つずつ処理」
+--------------------------------------------------+
| コア数:   8-128個(大型コア)                   |
| クロック: 3-5 GHz(高いシングルスレッド性能)   |
| 得意:     複雑な制御フロー、分岐予測             |
|            OS、データベース、Webサーバー          |
| キャッシュ:L1/L2/L3階層(MB単位)               |
| 弱点:     並列演算に対してエネルギー非効率       |
+--------------------------------------------------+

GPU:「単純なことを、何百万個も同時に」
+--------------------------------------------------+
| コア数:  数千〜数万個                           |
| クロック:1-3 GHz(低いが大量並列)              |
| 得意:    あらゆる並列演算(レンダリング、AI|
| メモリ:  GDDR6/HBM、高帯域幅                   |
| 弱点:    消費電力300-700W、汎用性のコスト       |
+--------------------------------------------------+

NPU:「AI演算だけを、極限の効率で」
+--------------------------------------------------+
| コア数:  少数だが特化したMACアレイ              |
| 得意:    整数行列乗算(INT8/INT4)、量子化推論  |
| エネルギー効率:GPU10-100|
| 消費電力:1-10W(スマートフォン/ノートPC|
| 弱点:    汎用演算不可、FP32学習非対応           |
+--------------------------------------------------+

NPUが必要な理由:
- スマートフォンAI5000 mAhバッテリー、GPULLMを動かすと30分も持たない
- NPU:同じ演算を1/50の電力で実現
- 「常時オンAI」:顔認識、ウェイクワード検出、写真分類を常時実行

エネルギー効率の実数値を見てみましょう:

# INT8推論のエネルギー効率比較
perf_data = {
    'NVIDIA H100 SXM':        {'tops': 3958, 'tdp_w': 700},
    'NVIDIA A100 40GB':       {'tops': 1248, 'tdp_w': 400},
    'AMD MI300X':             {'tops': 5220, 'tdp_w': 750},
    'Apple M4 ニューラルエンジン': {'tops': 38, 'tdp_w': 4},
    'Qualcomm Hexagon NPU':   {'tops': 45,   'tdp_w': 5},
    'Intel Meteor Lake NPU':  {'tops': 10,   'tdp_w': 8},
}

print(f"{'ハードウェア':<35} {'TOPS':>8} {'TDP(W)':>8} {'TOPS/W':>8}")
print("-" * 60)
for name, data in perf_data.items():
    eff = data['tops'] / data['tdp_w']
    print(f"{name:<35} {data['tops']:>8} {data['tdp_w']:>8} {eff:>8.2f}")

# モバイルNPUは絶対性能は低いが、消費電力あたり効率で競争力を持つ

2. Apple Neural Engine(ANE)完全解剖

AppleのNeural Engineは最も知られたコンシューマーNPUの一つです。2017年のA11 Bionicに初搭載されて以来、着実に進化してきました。

Apple Neural Engine 世代別進化:

A11 Bionic(2017):2コアNeural Engine
  性能:0.6 TOPS
  用途:Face ID、Animoji

A12 Bionic(2018):8コアに拡張
  性能:5 TOPS
  追加:オンデバイスSiri音声認識

A15 Bionic(2021):16コア
  性能:15.8 TOPS
  追加:オンデバイス翻訳、カメラのLive Text

A17 Pro(2023):16コア、3nmプロセス
  性能:35 TOPSINT8  追加:Llama 3.2 3B ローカル推論(~25 tok/s)

M42024):16コアNeural Engine
  性能:38 TOPS
  ユニファイドメモリ:CPU/GPUと共有

ANE内部アーキテクチャ(公開特許より推定)

Apple Neural Engine ダイ構成(推定):

+-------------------------------------------------------------+
|                    Neural Engine ブロック                    |
+-------------------------------------------------------------+
|  コマンドプロセッサ(実行ユニットへのスケジューリング)     |
|  +--------------------------------------------------------+  |
|  |  16個の実行ユニット(コア)                            |  |
|  |  [MACアレイ] [MACアレイ] ... [MACアレイ] x 16         |  |
|  |EUINT8/INT16行列乗算 + 活性化関数                 |  |
|  |         Layer Norm、Softmaxのハードウェアアクセラレーション|  |
|  +--------------------------------------------------------+  |
+-------------------------------------------------------------+
|  専用L1 SRAM~30 MB                                       |
|GPUとは共有しない → キャッシュ汚染なし)                 |
+-------------------------------------------------------------+
|  DMAエンジン(メモリ移動専用ハードウェア)                  |
+-------------------------------------------------------------+
|  メモリインターフェース:ユニファイドメモリ(CPU/GPU/ANE共有)|
+-------------------------------------------------------------+

重要な制約:
- プログラミング:CoreMLのみ(直接アクセスAPIなし)
- データ形式:INT8INT16のみ(FP32非対応、学習不可)
- バッチサイズ制限あり(大バッチは非効率)
- 非対応オペレーターは自動的にGPU/CPUにフォールバック

CoreMLでANEを活用する

# PyTorchモデルをCoreMLに変換 -> Apple Neural Engineで実行
import torch
import coremltools as ct

class SmallTransformer(torch.nn.Module):
    def __init__(self, d_model=512, n_heads=8, n_layers=6):
        super().__init__()
        self.layers = torch.nn.ModuleList([
            torch.nn.TransformerEncoderLayer(d_model, n_heads, batch_first=True)
            for _ in range(n_layers)
        ])
        self.output = torch.nn.Linear(d_model, 32000)

    def forward(self, x):
        for layer in self.layers:
            x = layer(x)
        return self.output(x)

model = SmallTransformer()
model.eval()

# モデルのトレーシング
example_input = torch.zeros(1, 128, 512)
traced = torch.jit.trace(model, example_input)

# CoreMLへの変換(量子化込み)
mlmodel = ct.convert(
    traced,
    inputs=[ct.TensorType(name='input',
                          shape=(1, ct.RangeDim(1, 512), 512))],
    compute_precision=ct.precision.FLOAT16,
    compute_units=ct.ComputeUnit.ALL  # ANE + GPU + CPU、自動ルーティング
)

# ポストトレーニングINT8量子化
from coremltools.optimize.coreml import (
    PostTrainingQuantizer, OptimizationConfig, OpLinearQuantizerConfig
)

config = OptimizationConfig(
    global_config=OpLinearQuantizerConfig(
        mode='linear_symmetric',
        dtype='int8',
        granularity='per_channel'  # チャンネル別スケール = 高精度
    )
)
quantizer = PostTrainingQuantizer(mlmodel, config)
quantized_model = quantizer.compress()
quantized_model.save('transformer_int8.mlpackage')
# CoreMLが実行時に自動的にANE/GPU/CPUを選択

3. トランスフォーマーの全演算をハードウェアにマッピング

トランスフォーマーのforward passの各ステップがどのハードウェアユニットで処理されるかを完全に追跡します。

Transformer Forward Pass -> ハードウェアマッピング:
+------------------------------------------------------------------+
| 入力トークンID [batch, seq_len]                                  |
|  -> 埋め込みルックアップ(Gather演算)                           |
|  ハードウェア:NPU SRAMに埋め込みテーブルをキャッシュ           |
|  コスト:ほぼゼロ(テーブル参照、計算なし)                     |
+------------------------------------------------------------------+
| Layer Normalization                                              |
|  -> mean -> variance -> normalize -> scale -> bias               |
|  ハードウェア:NPUベクトルALUXLAが単一カーネルに融合)        |
|  計算比率:~1-2%                                                 |
+------------------------------------------------------------------+
| QKV投影(Linear Layer × 3|
|  [batch, seq, d_model] x [d_model, d_head] = GEMM               |
|  ハードウェア:Systolic Array / MACアレイ(電力の40%以上)      |
|  計算比率:~38%(支配的!)                                      |
+------------------------------------------------------------------+
| アテンションスコア:Q x K^T / sqrt(d_head)                      |
|  [b, heads, seq, d_h] x [b, heads, d_h, seq]                   |
|  ハードウェア:GPU Tensor Core / NPU MACO(n^2 × d)複雑度)    |
|  計算比率:~12%(シーケンス長の二乗に比例!)                   |
+------------------------------------------------------------------+
| Softmax:exp -> sum -> divide                                    |
|  ハードウェア:NPUベクトルユニット(expは特殊関数ハードウェア) |
|  計算比率:~1%(メモリ集約的)                                   |
+------------------------------------------------------------------+
| アテンション × VGEMM|
|  [b, heads, seq, seq] x [b, heads, seq, d_h]                    |
|  ハードウェア:MACアレイ                                         |
|  計算比率:~12%                                                  |
+------------------------------------------------------------------+
| 出力投影 + FFN Layer1 + FFN Layer2(GEMM × 3|
|  ハードウェア:MACアレイ(最大行列:d_model × 4*d_model)       |
|  計算比率:~37%                                                  |
+------------------------------------------------------------------+

まとめ:
87%GEMM -> MACアレイ / Systolic Arrayで処理
13%がベクトル演算(LayerNorm、Softmax、GELU-> NPUベクトルユニット

Flash Attention:Attentionのメモリ問題を解決

def analyze_attention_memory(seq_len, d_model, n_heads, batch_size=1):
    d_head = d_model // n_heads
    dtype_bytes = 2  # FP16

    # 標準Attention:O(n^2)メモリ
    # [batch, heads, seq, seq]のアテンションスコア行列を保存
    attn_score_bytes = batch_size * n_heads * seq_len * seq_len * dtype_bytes
    attn_score_gb = attn_score_bytes / (1024**3)

    # Flash Attention:O(n)メモリ
    # タイル処理、スコア行列全体を保存しない
    flash_extra_bytes = batch_size * n_heads * seq_len * d_head * dtype_bytes
    flash_extra_gb = flash_extra_bytes / (1024**3)

    print(f"シーケンス長:{seq_len}")
    print(f"標準Attentionスコア行列:{attn_score_gb:.2f} GB")
    print(f"Flash Attention追加メモリ:{flash_extra_gb:.4f} GB")
    print(f"メモリ削減:{attn_score_gb/flash_extra_gb:.0f}倍")

# GPT-4規模(推定):seq=8192, d=12288, heads=96
analyze_attention_memory(8192, 12288, 96)
# 標準:1レイヤーあたり~49.2 GB!
# Flash Attention:~0.75 GB
# 65倍のメモリ削減

# NPUでFlash Attentionが特に重要な理由:
# NPU SRAMは通常30-100 MB
# seq=8192の標準Attentionは49 GB必要 -> 不可能
# Flash Attentionのタイル処理はSRAMに収まる -> 長いシーケンスが可能

4. なぜLLM推論はメモリバウンドなのか:ルーフラインモデル

これはLLM推論性能を理解するうえで最重要な概念です。多くのエンジニアが「TFLOPS倍増 = LLM倍速」と思いがちですが、これは間違いです。

ルーフライン分析

# ルーフラインモデルによるLLM推論ボトルネック分析

model_config = {
    'name': 'Llama 2 7B',
    'num_params': 7_000_000_000,
    'bytes_per_param': 2,  # FP16
}

model_size_bytes = model_config['num_params'] * model_config['bytes_per_param']
model_size_gb = model_size_bytes / (1024**3)
print(f"モデルサイズ:{model_size_gb:.1f} GB")  # 14.0 GB

hardware = {
    'H100 SXM':     {'mem_bw_gbs': 3350, 'compute_tflops': 1979},
    'A100 80GB':    {'mem_bw_gbs': 2000, 'compute_tflops': 312},
    'RTX 4090':     {'mem_bw_gbs': 1008, 'compute_tflops': 82.6},
    'Apple M3 Max': {'mem_bw_gbs': 300,  'compute_tflops': 14.2},
}

print(f"\n{'ハードウェア':<20} {'メモリ(ms)':>10} {'計算(ms)':>10} {'ボトルネック':>15} {'tok/s':>8}")
print("-" * 68)

for hw_name, hw in hardware.items():
    # トークンごと:全重みをメモリから読む必要あり
    mem_time_ms = (model_size_bytes / (hw['mem_bw_gbs'] * 1e9)) * 1000

    # 計算時間:2 × パラメータ数 FLOPs / 利用可能FLOPS
    flops_per_token = 2 * model_config['num_params']
    compute_time_ms = (flops_per_token / (hw['compute_tflops'] * 1e12)) * 1000

    bottleneck_time = max(mem_time_ms, compute_time_ms)
    tok_per_sec = 1000.0 / bottleneck_time

    bottleneck = "メモリバウンド" if mem_time_ms > compute_time_ms else "計算バウンド"
    print(f"{hw_name:<20} {mem_time_ms:>10.2f} {compute_time_ms:>10.4f} "
          f"{bottleneck:>15} {tok_per_sec:>8.0f}")

# 期待される出力(batch_size=1):
# H100:4.18ms vs 0.007ms -> メモリバウンド -> ~239 tok/s
# A100:7.00ms vs 0.045ms -> メモリバウンド -> ~143 tok/s
# RTX 4090:13.89ms vs 0.170ms -> メモリバウンド -> ~72 tok/s
# M3 Max:46.67ms vs 0.985ms -> メモリバウンド -> ~21 tok/s
# 結論:batch_size=1では全ハードウェアがメモリバウンド!

メモリバウンドが意味すること

メモリバウンド推論の直感に反する含意:

1. TFLOPS2倍にしても速度は変わらない
   (ボトルネックは計算ではなくメモリ帯域幅)

2. メモリ帯域幅を2倍にすると正確に2倍速くなる
   H1003.35 TB/s)がA1002.0 TB/s)より~1.6倍速い理由

3. モデルサイズを半分に(量子化)-> 正確に2倍速
   FP16 -> INT8:モデルサイズ半減 -> 2倍のtok/s
   INT8 -> INT4:さらに半減 -> さらに2倍のtok/s

4. バッチサイズを大きくすると計算バウンドに移行
   batch_size=1:重みを1回読んで1トークン(非効率)
   batch_size=64:重みを1回読んで64トークン(効率的)
   大バッチ => 重みの再利用 => 計算バウンド => TFLOPSが重要に

5. Apple Siliconが競争力を持つ理由:
   M3 Ultra:800 GB/sユニファイドメモリ(H1003.35 TB/sには劣る)
   BUT192 GBの容量で量子化なしに大型モデルを格納可能
   M3 Max(128 GB):Llama 3.1 70B FP16をローカルで実行可能

5. KVキャッシュ:長いコンテキストのメモリコスト

KVキャッシュなしでは、1000トークンの応答生成に毎新トークンごとにすべてのアテンション重みを再計算する必要があります(O(n²)の複雑度)。

# KVキャッシュのメモリ分析

def compute_kv_cache_size(model_name, context_len, n_layers,
                          n_kv_heads, head_dim, batch_size=1,
                          dtype_bytes=2):
    """KVキャッシュ = 全過去トークンのKeyとValueテンソル"""
    kv_bytes = (context_len * n_layers * 2 *
                n_kv_heads * head_dim * batch_size * dtype_bytes)
    kv_gb = kv_bytes / (1024**3)

    per_token_kb = (n_layers * 2 * n_kv_heads * head_dim * dtype_bytes) / 1024

    print(f"\n=== {model_name} ===")
    print(f"  コンテキスト長:    {context_len:>8,} トークン")
    print(f"  KVキャッシュサイズ:{kv_gb:>8.2f} GB")
    print(f"  トークンあたりKV読込:{per_token_kb:>8.1f} KB")

# 標準MHA:n_kv_heads = n_q_heads
compute_kv_cache_size("Llama 2 7B",
                      context_len=4096, n_layers=32,
                      n_kv_heads=32, head_dim=128)
# KVキャッシュ:4Kコンテキストで2.0 GB

# GQA(グループクエリアテンション):n_kv_heads < n_q_heads
compute_kv_cache_size("Llama 3.1 8B(GQA 8ヘッド)",
                      context_len=128_000, n_layers=32,
                      n_kv_heads=8, head_dim=128)
# KVキャッシュ:128Kコンテキストで16 GB(GQAなしだと64 GB!)

GQA:KVキャッシュの大幅削減

def compare_gqa_savings(seq_len, n_layers, d_model,
                        n_q_heads, n_kv_heads_gqa,
                        dtype_bytes=2):
    head_dim = d_model // n_q_heads

    # MHA(標準)
    mha_kv_gb = (seq_len * n_layers * 2 * n_q_heads *
                 head_dim * dtype_bytes) / (1024**3)
    # GQA
    gqa_kv_gb = (seq_len * n_layers * 2 * n_kv_heads_gqa *
                 head_dim * dtype_bytes) / (1024**3)
    # MQA(極端なケース)
    mqa_kv_gb = (seq_len * n_layers * 2 * 1 *
                 head_dim * dtype_bytes) / (1024**3)

    reduction_gqa = (1 - gqa_kv_gb/mha_kv_gb) * 100

    print(f"シーケンス:{seq_len}、Qヘッド:{n_q_heads}、GQA KVヘッド:{n_kv_heads_gqa}")
    print(f"  MHA KVキャッシュ:{mha_kv_gb:.2f} GB(基準)")
    print(f"  GQA KVキャッシュ:{gqa_kv_gb:.2f} GB({reduction_gqa:.0f}%削減!)")
    print(f"  MQA KVキャッシュ:{mqa_kv_gb:.2f} GB")

# Llama 3.1 8B:Qヘッド32、GQA KVヘッド8
compare_gqa_savings(128_000, 32, 4096, 32, 8)
# GQA:75%のKVキャッシュ削減!
# NPU SRAMがより長いコンテキストを処理可能に

6. 量子化がNPUをどう強化するか

量子化はオンデバイスLLM推論を可能にする核心技術です。

数値フォーマットとハードウェアサポート:

FP32[1符号][8指数][23仮数] = 32ビット
      学習の標準、全ハードウェア対応
      7Bモデル:28 GB

FP16[1符号][5指数][10仮数] = 16ビット
      推論の標準、GPU/NPU対応
      7Bモデル:14 GB

INT8[1符号][7]           = 8ビット  <- NPUのデフォルト
NPU対応、INT324倍のSIMDスループット
      7Bモデル:7 GB
      精度損失:通常 < 0.5%

INT44ビット                 <- 最新NPUA17 Pro、Hexagon)
      INT82倍スループット
      7Bモデル:3.5 GB
      精度損失:1-3%GPTQ使用時)

NPUでのINT8 SIMDの利点:
- 32ビットレジスタに4個のINT8値を格納 -> 4倍スループット
- 実際:INT8 GEMMFP32 GEMMより4-8倍高速
- メモリ帯域幅節約:INT84倍少ない読み込み

実際の量子化実装

# LLM.int8()を使用したポストトレーニング量子化
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
import torch

# INT8量子化
config_int8 = BitsAndBytesConfig(
    load_in_8bit=True,
    llm_int8_threshold=6.0,     # 外れ値の処理閾値
    llm_int8_has_fp16_weight=False
)

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct",
    quantization_config=config_int8,
    device_map='auto'
)
# FP16:16 GB -> INT8:~8.5 GB、精度損失~0.3%

# GPTQ INT4量子化(高精度)
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig

quantize_config = BaseQuantizeConfig(
    bits=4,
    group_size=128,
    damp_percent=0.1,
    desc_act=True,
)

# キャリブレーションデータが必要(精度維持の鍵!)
from datasets import load_dataset
dataset = load_dataset('wikitext', 'wikitext-2-raw-v1', split='train')
calib_data = [dataset[i]['text'] for i in range(128) if len(dataset[i]['text']) > 50]

model_gptq = AutoGPTQForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct",
    quantize_config
)
model_gptq.quantize(calib_data)
model_gptq.save_quantized("llama3-8b-gptq-4bit")
# FP16:16 GB -> GPTQ INT4:~4.5 GB、精度損失~1.2%

# AWQ(アクティベーション認識重み量子化)
from awq import AutoAWQForCausalLM

model_awq = AutoAWQForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct"
)
quant_config = {
    "zero_point": True, "q_group_size": 128,
    "w_bit": 4, "version": "GEMM"
}
model_awq.quantize(tokenizer, quant_config=quant_config)
# AWQは同じビット幅でGPTQより~0.5%高い精度を達成

7. Qualcomm Hexagon NPUとIntel NPU

Qualcomm Snapdragon X Elite:Hexagon NPU

Qualcomm Hexagon NPU(Snapdragon X Elite、2024年):

性能:45 TOPSINT8アーキテクチャ:
  HTA(Hexagon Tensor Accelerator):
    - 主要GEMMアクセラレータ
    - INT4INT8FP16対応
    - オンチップSRAM~4 MB
  HMNN(Hexagon Multi-Network Node):
    - 複数のAIネットワークを同時実行
    - リアルタイム + バックグラウンドAI同時処理
  ベクトルDSP + スカラーDSP    - 活性化関数、Softmax等を処理

サポートするLLM推論:
  Llama 3.2 3B INT4~30 tok/s
  Phi-3.5 mini 3.8B:    ~25 tok/s
  Gemma 2 2B INT4~35 tok/s

Windows Copilot Plus PCAI機能(全てNPUで実行):
  - ライブキャプション:リアルタイム音声文字起こし
  - Cocreator:AI画像生成
  - スマートスナップショット:AIシーン理解
  -> バッテリー効率的:NPUが処理するため

Intel Meteor Lake NPU

Intel NPU(Core Ultra / Meteor Lake、2023年):

性能:10-11 TOPSINT8アーキテクチャ:
  - NN演算エンジン:MACアレイ
  - スライスアーキテクチャ:独立した処理タイル

得意なこと:
  - 常時オンAI処理(最小限の電力)
  - Windows Studio Effects(画面が開いているときNPUを使用)
  - リアルタイムノイズキャンセル、視線補正

OpenVINOで活用:
from openvino.runtime import Core

ie = Core()
print(ie.available_devices)  # ['CPU', 'GPU', 'NPU']

compiled_model = ie.compile_model(
    model=onnx_model_path,
    device_name="NPU",
    config={"PERFORMANCE_HINT": "THROUGHPUT"}
)
output = compiled_model({compiled_model.input(): input_data})

主な用途:
  - Windows Studio Effects(背景ぼかし、視線補正)
  - 音声認識前処理(ウェイクワード検出)
  - 小型モデルによるリアルタイム翻訳
  - 画像強調(計算写真)

8. デバイス別LLM実行可能モデルサイズ

2025年現在、実用的なオンデバイスLLM推論:

iPhone 16(8GB RAMA18 Pro、~35 TOPS ANE):
  実行可能:Llama 3.2 3B INT4~2.0 GB~25 tok/s)
  実行可能:Phi-3.5 mini 3.8B INT4~2.3 GB~20 tok/s)
  実行可能:Llama 3.1 8B INT4~5.0 GB~12 tok/s)
  不可:   Llama 3.1 8B FP1616 GB必要、RAM不足)
  不可:   70Bモデル(INT4でも35 GB以上必要)

MacBook Air M3 16GB:
  実行可能:Llama 3.1 8B Q4~5 GB~40 tok/s)
  実行可能:Mistral 7B Q4~4.5 GB~45 tok/s)
  不可:   Llama 3.1 70B(Q4でも40 GB必要)

MacBook Pro M3 Max 128GB(400 GB/s):
  実行可能:Llama 3.1 70B Q4~40 GB~22 tok/s)
  実行可能:Llama 3.1 70B Q8~75 GB~11 tok/s)

Mac Studio M3 Ultra 192GB(800 GB/s):
  実行可能:Llama 3.1 70B FP16~140 GB~35 tok/s)
  実行可能:Llama 3.1 405B Q4~230 GB必要)
  GPT-4クラスのローカル推論マシンとして機能

Snapdragon X Elite PC(32GB):
  実行可能:Llama 3.2 3B Q4NPU~30 tok/s)
  実行可能:Phi-3.5 mini(NPU~25 tok/s)
  実行可能:Llama 3.1 8B Q4GPU~15 tok/s)

9. 未来:LLMチップ戦争

汎用GPUだけでなく、専用推論チップが猛追しています。

専用LLM推論チップの現状:

1. Groq LPU(Language Processing Unit)
   革新:決定論的データフロー
   - コンパイル時に全メモリアクセスと演算を静的スケジューリング
   - 実行時:スケジューラーオーバーヘッドゼロ、キャッシュミスゼロ
   実測:Llama 2 70Bで~500 tok/s(H100~4倍!)
   理由:LPUは決してメモリ待ちでストールしない
   弱点:特定のモデルアーキテクチャのみ対応、柔軟性低い

2. Cerebras WSE-3(ウェーハスケールエンジン)
   チップサイズ:46,225 mm^2(ウェーハ全体)
   AIコア:90万個
   オンチップSRAM900 MB(超高速、超大容量)
   核心洞察:モデル全体をチップ上に載せる -> HBMアクセス不要
   弱点:1台数百万ドル

3. SambaNova 再構成可能データフローアーキテクチャ
   概念:FPGAのようなプログラマビリティ + ASIC性能
   顧客:米国政府機関、大型研究所

4. Etched Sohu
   特徴:トランスフォーマー専用ハードワイヤードチップ
   予想性能:H10020倍の効率

なぜ専用チップが汎用GPUを上回れるのか:
- GPU:汎用性のオーバーヘッド(スケジューラ、レジスタファイル、複雑なキャッシュ)
- 専用チップ:既知のワークロードに合わせてハードウェア自体が最適化
- トランスフォーマー推論は決定論的 -> コンパイル時最適化を最大化

10. 実践:llama.cppでオンデバイスLLM推論

# llama.cppをApple Silicon対応でビルド
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp

# Metal(Apple GPU/ANE)サポートでビルド
cmake -B build -DLLAMA_METAL=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)

# 量子化済みモデルをダウンロード(GGUF形式)
# Llama 3.1 8B Q4_K_M:~5.0 GB
pip install huggingface-hub
huggingface-cli download \
  bartowski/Meta-Llama-3.1-8B-Instruct-GGUF \
  Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf \
  --local-dir ./models

# 推論実行(Metal加速あり)
./build/bin/llama-cli \
  -m ./models/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf \
  -n 512 \
  --n-gpu-layers 35 \
  --ctx-size 4096 \
  -p "トランスフォーマーアーキテクチャのルーフラインモデルを説明してください"

# Apple M3 Max 16コアでの期待結果:
# ロード時間:~3秒
# スループット:~45 tok/s(GPUオフロード時)
# メモリ:~5.5 GB
# llama-cpp-pythonを使ったPythonバインディング
from llama_cpp import Llama
import time

# モデルロード
llm = Llama(
    model_path="./models/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf",
    n_gpu_layers=35,    # GPUにオフロードするレイヤー数
    n_ctx=4096,         # コンテキストウィンドウ
    n_threads=8,        # CPU スレッド数
    flash_attn=True,    # Flash Attention使用
    verbose=False
)

# 推論実行
def benchmark(llm, prompt, n_tokens=200):
    start = time.perf_counter()
    output = llm(prompt, max_tokens=n_tokens, echo=False)
    elapsed = time.perf_counter() - start
    n_out = output['usage']['completion_tokens']
    return n_out, elapsed, n_out / elapsed

n_tok, t, speed = benchmark(
    llm,
    "NPUがGPUよりLLM推論で効率的な理由を説明してください:",
    n_tokens=200
)
print(f"{n_tok}トークンを{t:.2f}秒で生成 = {speed:.1f} tok/s")

# シンプルなAPIサーバー
from fastapi import FastAPI
from pydantic import BaseModel
import asyncio
from concurrent.futures import ThreadPoolExecutor

app = FastAPI()
executor = ThreadPoolExecutor(max_workers=1)

class Request(BaseModel):
    prompt: str
    max_tokens: int = 256

@app.post("/generate")
async def generate(request: Request):
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(
        executor,
        lambda: llm.create_chat_completion(
            messages=[{"role": "user", "content": request.prompt}],
            max_tokens=request.max_tokens
        )
    )
    return {"response": result['choices'][0]['message']['content']}

まとめ

NPUは「AIをすべての人の手に届ける」というビジョンを実現するハードウェア革命です。

LLMインフラエンジニアとして重要な教訓:

  1. 電力が制約条件:スマートフォンでAIを動かすにはGPUの1/50の電力が必要 -> NPUが唯一の答え
  2. LLM推論は常にメモリバウンド:batch_size=1では、メモリ帯域幅がTFLOPSより性能を決定する。この一つの洞察があらゆるハードウェア選択を変える。
  3. 量子化がアンロック:INT4 vs FP16 = メモリ使用量1/4 = tok/s 4倍。適切なキャリブレーションが「使える」と「使えない」の差を生む。
  4. KVキャッシュがメモリ予算を決める:長コンテキストモデルでは、KVキャッシュがモデル重みを超えることも。GQAで4-8倍削減できる。
  5. 専用チップの時代が来る:Groq、Cerebras、Etched -- 専用推論ハードウェアはすでに効率でGPUを上回っている。問題はいつコスト競争力が生まれるかだけ。

ハードウェアとソフトウェアが共進化するAI時代において、TPUとNPUを深く理解することは単なる好奇心を超え、競争力のあるAIエンジニアになるための基礎知識です。


参考文献

  • Apple Neural Engine特許文書(米国特許庁、2017-2024)
  • Qualcomm AI Engine Direct SDK ドキュメント
  • "FlashAttention-2: Faster Attention with Better Parallelism" (Dao, 2023)
  • "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers" (Frantar et al., 2022)
  • "AWQ: Activation-aware Weight Quantization for LLM Compression" (Lin et al., 2023)
  • "Roofline: An Insightful Visual Performance Model" (Williams et al., 2009)
  • llama.cpp: github.com/ggerganov/llama.cpp
  • Groq LPU 技術白書: groq.com
  • Cerebras WSE-3 アーキテクチャ: cerebras.net