Home 論文解説: DeepSeek-V2 — Multi-Head Latent Attentionによる KVキャッシュ93%削減の設計と実装
投稿
キャンセル

📄 論文解説: DeepSeek-V2 — Multi-Head Latent Attentionによる KVキャッシュ93%削減の設計と実装

本記事は DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model の解説記事です。

論文概要(Abstract)

DeepSeek-V2は、236Bの総パラメータ数に対し21Bのみを活性化する効率的なMoE型言語モデルである。著者らは2つの主要な技術革新を提案している。第一にMulti-Head Latent Attention(MLA)はKey-Valueを低次元潜在空間に圧縮してKVキャッシュを大幅に削減する。第二にDeepSeekMoEはfine-grained expertとshared expertを組み合わせた効率的なエキスパート混合設計である。著者らは、DeepSeek-V2がDeepSeek 67B比で42.5%の学習コスト削減と5.76倍の推論スループット向上を達成したと報告している。

この記事は Zenn記事: LLM Architecture Gallery徹底解説:30+モデルの内部構造を4軸で横断比較する の深掘りです。

情報源

  • arXiv ID: 2405.04434
  • URL: https://arxiv.org/abs/2405.04434
  • 著者: Aixin Liu, Bei Feng, Bing Xue, et al.(DeepSeek-AI)
  • 発表年: 2024
  • 分野: cs.CL, cs.AI, cs.LG

背景と動機(Background & Motivation)

Transformer系LLMの推論において、KVキャッシュのメモリ消費がスケーリングの主要なボトルネックとなっている。Multi-Head Attention(MHA)では、各レイヤー・各ヘッドに独立したKey・Value投射が必要であり、コンテキスト長とレイヤー数に比例してメモリが増大する。

この問題に対し、Multi-Query Attention(MQA)とGrouped-Query Attention(GQA)が提案されてきた。MQAは全ヘッドでKVを共有するため最もメモリ効率が高いが、MHAと比較して性能が低下する場合がある。GQAはこの中間に位置するが、KV削減には上限がある。

DeepSeek-V2の著者らは、MQA/GQAがヘッド数方向の共有のみでKVキャッシュを削減するのに対し、次元方向の圧縮という新たなアプローチでより大幅な削減を実現するMLAを提案した。

主要な貢献(Key Contributions)

  • Multi-Head Latent Attention(MLA): KVを低次元潜在ベクトルに圧縮し、KVキャッシュをMHA比で93.3%削減。さらにMHAを上回るモデリング性能を実証
  • DeepSeekMoE改良版: Fine-grained experts(160個のルーテッドエキスパート、うち6個を活性化)とshared expert(2個、常時活性化)を組み合わせ、エキスパートの特化度を向上
  • 経済的な学習と推論: DeepSeek 67B(密モデル)と比較して、学習コスト42.5%削減、推論スループット5.76倍(論文Table 1より)

技術的詳細(Technical Details)

Multi-Head Latent Attention(MLA)の仕組み

MLAの核心は、Key-Valueの投射を低次元の潜在空間を経由して行う点にある。通常のMHAでは、入力$\mathbf{h}_t \in \mathbb{R}^d$に対してKey・Valueを直接投射する。

\[\mathbf{k}_t^{(i)} = W_K^{(i)} \mathbf{h}_t, \quad \mathbf{v}_t^{(i)} = W_V^{(i)} \mathbf{h}_t\]

ここで$i$はヘッドインデックス、$W_K^{(i)}, W_V^{(i)} \in \mathbb{R}^{d_h \times d}$は投射行列である。KVキャッシュには全ヘッドの$\mathbf{k}_t^{(i)}$と$\mathbf{v}_t^{(i)}$を格納する必要があり、1トークンあたり$2 \times n_h \times d_h$個の値を保持する。

MLAでは、まず入力をダウンプロジェクションで低次元の潜在ベクトルに圧縮する。

\[\mathbf{c}_t^{KV} = W_{DKV} \mathbf{h}_t\]

ここで$W_{DKV} \in \mathbb{R}^{d_c \times d}$はダウンプロジェクション行列、$d_c \ll n_h \times d_h$は圧縮後の次元である。KVキャッシュに格納するのは$\mathbf{c}_t^{KV} \in \mathbb{R}^{d_c}$のみである。

推論時には、アッププロジェクションで元の次元に復元する。

\[\mathbf{k}_t^{(i)} = W_{UK}^{(i)} \mathbf{c}_t^{KV}, \quad \mathbf{v}_t^{(i)} = W_{UV}^{(i)} \mathbf{c}_t^{KV}\]

ここで$W_{UK}^{(i)} \in \mathbb{R}^{d_h \times d_c}$, $W_{UV}^{(i)} \in \mathbb{R}^{d_h \times d_c}$はアッププロジェクション行列である。

KVキャッシュ削減の定量評価: 論文の設定では$n_h = 128$, $d_h = 128$, $d_c = 512$の場合、1トークンあたりのKVキャッシュは以下のように変化する。

方式格納値/トークン/レイヤーMHA比
MHA$2 \times 128 \times 128 = 32{,}768$100%
GQA(8グループ)$2 \times 8 \times 128 = 2{,}048$6.25%
MLA($d_c = 512$)$512$1.56%

MLAはMHAと比較してKVキャッシュを93.3%〜98.4%削減する(論文Section 3.1, Table 2より)。

Decoupled RoPE

MLAでは位置エンコーディングの適用に工夫が必要である。通常のRoPEはKey投射にquery-key間の相対位置情報を埋め込むが、MLAの圧縮操作とRoPEは互換性がない。圧縮後の潜在ベクトルにRoPEを適用すると、位置情報がKey復元時に破壊されるためである。

著者らはDecoupled RoPEを提案している。具体的には、RoPEを適用する専用のQueryとKeyの投射を別途設け、MLAの圧縮経路とは独立させる。

\[\mathbf{q}_t^{R,(i)} = W_{QR}^{(i)} \mathbf{q}_t, \quad \mathbf{k}_t^{R} = W_{KR} \mathbf{h}_t\]

RoPEは$\mathbf{q}_t^{R,(i)}$と$\mathbf{k}_t^{R}$にのみ適用される。最終的なアテンションスコアは、圧縮経路のスコアとRoPE経路のスコアの和として計算される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# Decoupled RoPEを含むMLAの概念的な実装(PyTorch風の擬似コード)
import torch
import torch.nn as nn

class MLAWithDecoupledRoPE(nn.Module):
    """Multi-Head Latent Attention with Decoupled RoPE

    Args:
        d_model: モデル次元
        n_heads: アテンションヘッド数
        d_latent: KV潜在空間の次元
        d_rope: RoPE専用の次元
    """
    def __init__(
        self,
        d_model: int,
        n_heads: int,
        d_latent: int,
        d_rope: int,
    ):
        super().__init__()
        self.n_heads = n_heads
        self.head_dim = d_model // n_heads
        self.d_latent = d_latent
        self.d_rope = d_rope

        # Query投射: 圧縮経路
        self.q_proj = nn.Linear(d_model, n_heads * self.head_dim, bias=False)
        # Query投射: RoPE専用
        self.q_rope_proj = nn.Linear(d_model, n_heads * d_rope, bias=False)

        # KVダウンプロジェクション(圧縮)
        self.kv_down = nn.Linear(d_model, d_latent, bias=False)
        # KVアッププロジェクション(復元)
        self.k_up = nn.Linear(d_latent, n_heads * self.head_dim, bias=False)
        self.v_up = nn.Linear(d_latent, n_heads * self.head_dim, bias=False)

        # Key投射: RoPE専用(共有、ヘッド非依存)
        self.k_rope_proj = nn.Linear(d_model, d_rope, bias=False)

        self.o_proj = nn.Linear(n_heads * self.head_dim, d_model, bias=False)

    def forward(
        self,
        x: torch.Tensor,
        rope_fn: callable,
        position_ids: torch.Tensor,
    ) -> torch.Tensor:
        """MLAの順伝播

        Args:
            x: 入力テンソル (B, T, d_model)
            rope_fn: RoPEを適用する関数
            position_ids: 位置ID (B, T)
        Returns:
            出力テンソル (B, T, d_model)
        """
        B, T, _ = x.shape

        # 圧縮経路: Query
        q_content = self.q_proj(x).view(B, T, self.n_heads, self.head_dim)

        # RoPE経路: Query
        q_rope = self.q_rope_proj(x).view(B, T, self.n_heads, self.d_rope)
        q_rope = rope_fn(q_rope, position_ids)

        # KV圧縮 → キャッシュに格納するのはこの潜在ベクトルのみ
        kv_latent = self.kv_down(x)  # (B, T, d_latent)

        # KV復元(推論時はキャッシュから復元)
        k_content = self.k_up(kv_latent).view(B, T, self.n_heads, self.head_dim)
        v = self.v_up(kv_latent).view(B, T, self.n_heads, self.head_dim)

        # RoPE経路: Key(ヘッド非依存)
        k_rope = self.k_rope_proj(x).view(B, T, 1, self.d_rope)
        k_rope = rope_fn(k_rope, position_ids)
        k_rope = k_rope.expand(-1, -1, self.n_heads, -1)

        # アテンションスコア = 圧縮経路 + RoPE経路
        q_content = q_content.transpose(1, 2)  # (B, H, T, d_h)
        k_content = k_content.transpose(1, 2)
        q_rope = q_rope.transpose(1, 2)  # (B, H, T, d_rope)
        k_rope = k_rope.transpose(1, 2)
        v = v.transpose(1, 2)

        # スコア計算(簡略化: 実際にはscaled dot-product)
        scale = (self.head_dim + self.d_rope) ** -0.5
        scores = (
            torch.matmul(q_content, k_content.transpose(-2, -1))
            + torch.matmul(q_rope, k_rope.transpose(-2, -1))
        ) * scale

        attn = torch.softmax(scores, dim=-1)
        out = torch.matmul(attn, v)

        out = out.transpose(1, 2).contiguous().view(B, T, -1)
        return self.o_proj(out)

DeepSeekMoE設計

DeepSeek-V2はFFN層にMoEを採用している。設計の特徴は以下の通りである。

  1. Fine-grained experts: エキスパート数を多く(160個)設定し、各エキスパートのパラメータ数を小さくする。トークンあたりの活性エキスパート数は6個
  2. Shared experts: 2個のエキスパートを全トークンで常時活性化し、基礎的な知識を保持する
  3. ルーティング: トップK選択(K=6)でルーテッドエキスパートを選択し、shared expertの出力と加算
\[\text{FFN}(\mathbf{x}) = \underbrace{\sum_{s=1}^{N_s} \text{FFN}_s(\mathbf{x})}_{\text{shared experts}} + \underbrace{\sum_{i \in \text{TopK}(g(\mathbf{x}), K)} g_i(\mathbf{x}) \cdot \text{FFN}_i(\mathbf{x})}_{\text{routed experts}}\]

ここで$g(\mathbf{x})$はルーティング関数(ゲート)、$N_s$はshared expert数、$K$は活性ルーテッドエキスパート数である。

実装のポイント(Implementation)

MLAを実装する際の注意点を整理する。

FlashAttentionとの互換性: MLAのDecoupled RoPEは標準的なFlashAttentionカーネルと互換性がない。圧縮経路とRoPE経路で別々のスコアを計算し加算する必要があるため、カスタムカーネルの実装が必要になる。著者らはAbsorb(吸収)最適化を提案しており、アッププロジェクション行列をQuery投射に事前乗算することでKV復元を省略する手法を示している。

KVキャッシュの実装: キャッシュに格納するのは圧縮後の潜在ベクトル$\mathbf{c}_t^{KV}$とRoPE用Key $\mathbf{k}_t^R$のみである。復元は推論時にオンデマンドで行う。

ハイパーパラメータの選択: 論文のアブレーション実験(Section 3.1)によると、圧縮次元$d_c$はヘッド次元$d_h$の4倍程度($d_c = 4 \times d_h$)が性能と効率のバランスが良いと報告されている。

MoEのロードバランシング: DeepSeekMoEでは、エキスパート間の負荷均等化のためにバランシングロスを導入している。ただし、著者らはバランシングロスの係数が大きすぎるとモデル性能が低下すると指摘しており、慎重な調整が必要である。

実験結果(Results)

論文Table 1より、DeepSeek-V2の主要ベンチマーク結果を示す。

ベンチマークDeepSeek 67B(密)DeepSeek-V2 (236B/21B)Mixtral 8x22BLLaMA 3 70B
MMLU71.378.577.879.5
GSM8K63.479.275.1
HumanEval45.148.846.3
BBH68.778.976.4

著者らは、DeepSeek-V2が密モデルのDeepSeek 67Bを全ベンチマークで上回り、Mixtral 8x22Bとも同等以上の性能を達成したと報告している(論文Table 1)。活性パラメータ数は21Bとはるかに少なく、推論効率の面で優位性がある。

推論スループット: DeepSeek 67Bと比較して5.76倍のスループット向上を達成。1トークンあたりのKVキャッシュがMHA比93.3%削減されたことが主要因である(論文Section 5.3)。

学習コスト: 8.1T トークンで学習。DeepSeek 67Bの学習コスト比42.5%削減を達成(論文Section 5.1)。

実運用への応用(Practical Applications)

MLAは以下のユースケースで有用である。

長コンテキスト推論: KVキャッシュのメモリ制約が緩和されるため、128Kトークン以上のコンテキストでも実用的なメモリ消費に収まる。RAGシステムで大量のドキュメントチャンクを同時処理する場合に効果的である。

高スループットバッチ推論: バッチサイズを大きくした際のKVキャッシュ増加が緩やかになるため、同一GPU上でより多くのリクエストを並列処理できる。vLLM等の推論エンジンでのPagedAttentionとMLAの組み合わせが報告されている。

コスト制約のある環境: 活性パラメータ21Bで大規模モデル並みの性能を発揮するため、推論コストの制約が厳しいプロダクション環境に適している。

Production Deployment Guide

AWS実装パターン(コスト最適化重視)

MLAベースモデル(DeepSeek-V2クラス)のAWS推論環境をトラフィック量別に示す。

規模月間リクエスト推奨構成月額コスト主要サービス
Small~3,000 (100/日)Serverless$80-200Lambda + Bedrock + DynamoDB
Medium~30,000 (1,000/日)Hybrid$500-1,200ECS Fargate + ElastiCache + S3
Large300,000+ (10,000/日)Container$3,000-8,000EKS + Karpenter + EC2 GPU Spot

Small構成の詳細(月額$80-200):

  • Lambda: 1GB RAM, 60秒タイムアウト($25/月)
  • Bedrock: Claude 3.5 Haiku使用、Prompt Caching有効($100/月)
  • DynamoDB: On-Demand、KVキャッシュメタデータ管理($10/月)
  • CloudWatch: 基本監視($5/月)

Large構成の詳細(月額$3,000-8,000):

  • EKS: コントロールプレーン($72/月)
  • EC2 GPU Spot: g5.xlarge × 2-4台(平均$800/月、Spot利用で最大90%削減)
  • Karpenter: 自動スケーリング
  • S3: モデル重み・プロンプトキャッシュ保存($30/月)

コスト試算の注意: 上記は2026年3月時点のAWS ap-northeast-1(東京)リージョン料金に基づく概算値です。最新料金は AWS料金計算ツール で確認してください。

Terraformインフラコード

Small構成(Serverless):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "mla-inference-vpc"
  cidr = "10.0.0.0/16"
  azs  = ["ap-northeast-1a", "ap-northeast-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]

  enable_nat_gateway   = false
  enable_dns_hostnames = true
}

resource "aws_iam_role" "lambda_bedrock" {
  name = "mla-lambda-bedrock-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action    = "sts:AssumeRole"
      Effect    = "Allow"
      Principal = { Service = "lambda.amazonaws.com" }
    }]
  })
}

resource "aws_iam_role_policy" "bedrock_invoke" {
  role = aws_iam_role.lambda_bedrock.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"]
      Resource = "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-*"
    }]
  })
}

resource "aws_lambda_function" "mla_handler" {
  filename      = "lambda.zip"
  function_name = "mla-inference-handler"
  role          = aws_iam_role.lambda_bedrock.arn
  handler       = "index.handler"
  runtime       = "python3.12"
  timeout       = 60
  memory_size   = 1024

  environment {
    variables = {
      BEDROCK_MODEL_ID    = "anthropic.claude-3-5-haiku-20241022-v1:0"
      DYNAMODB_TABLE      = aws_dynamodb_table.cache.name
      ENABLE_PROMPT_CACHE = "true"
    }
  }
}

resource "aws_dynamodb_table" "cache" {
  name         = "mla-prompt-cache"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "prompt_hash"
  attribute { name = "prompt_hash"; type = "S" }
  ttl { attribute_name = "expire_at"; enabled = true }
}

運用・監視設定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import boto3

cloudwatch = boto3.client('cloudwatch')

# Lambda実行時間アラート
cloudwatch.put_metric_alarm(
    AlarmName='mla-lambda-duration-spike',
    ComparisonOperator='GreaterThanThreshold',
    EvaluationPeriods=2,
    MetricName='Duration',
    Namespace='AWS/Lambda',
    Period=300,
    Statistic='Average',
    Threshold=30000,
    AlarmDescription='Lambda実行時間異常'
)

コスト最適化チェックリスト

  • ~100 req/日 → Lambda + Bedrock($80-200/月)
  • ~1000 req/日 → ECS Fargate + Bedrock($500-1,200/月)
  • 10000+ req/日 → EKS + GPU Spot($3,000-8,000/月)
  • Spot Instances優先(最大90%削減)
  • Bedrock Batch API使用(50%割引、非リアルタイム処理)
  • Prompt Caching有効化(30-90%削減)
  • モデル選択: 開発→Haiku、本番簡易→Haiku、本番複雑→Sonnet
  • AWS Budgets: 月額予算設定(80%で警告)
  • CloudWatch アラーム: トークン使用量スパイク検知
  • Cost Anomaly Detection: 自動異常検知有効化

関連研究(Related Work)

  • GQA(Ainslie et al., 2023): MQAとMHAの中間設計。ヘッド方向の共有でKVキャッシュを削減するが、圧縮率はMLAに劣る。MLAはGQAをさらに拡張し、次元方向の圧縮を追加した設計と位置付けられる
  • Multi-Query Attention(Shazeer, 2019): 全ヘッドで単一のKVを共有。最もメモリ効率が高いが、品質低下のリスクがある。MLAはMQAよりも高い圧縮率を維持しつつ、品質を向上させた
  • Mixture-of-Experts(Fedus et al., 2022): Switch Transformerで提案されたスパースMoE設計。DeepSeekMoEはfine-grainedエキスパートとshared expertの組み合わせでルーティング精度を改善

まとめと今後の展望

DeepSeek-V2は、MLAによるKVキャッシュの低次元圧縮とDeepSeekMoEによる効率的なエキスパート混合を組み合わせ、推論効率とモデル性能を両立させた。MLAの設計思想は後続のDeepSeek-V3、Kimi K2、GLM-5などに継承されており、2026年時点のフロンティアモデルにおけるアテンション設計の標準的な選択肢の一つとなっている。

実務への示唆として、KVキャッシュがボトルネックとなる長コンテキスト推論や高スループットバッチ処理では、MLA採用モデルの優先度が高い。ただし、Decoupled RoPEの実装複雑性とFlashAttentionカーネルの改修コストを考慮する必要がある。

参考文献

  • arXiv: https://arxiv.org/abs/2405.04434
  • Code: https://github.com/deepseek-ai/DeepSeek-V2
  • Related Zenn article: https://zenn.dev/0h_n0/articles/72d86ab27620f2
この投稿は CC BY 4.0 でライセンスされています。