Home 論文解説: DistServe — Prefill/Decode分離によるLLMサービングのGoodput最適化
投稿
キャンセル

📄 論文解説: DistServe — Prefill/Decode分離によるLLMサービングのGoodput最適化

本記事は DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving(arXiv 2024)の解説記事です。

論文概要(Abstract)

DistServeは、LLM推論のPrefill(プロンプト処理)とDecode(トークン生成)を物理的に異なるGPUインスタンスに分離(Disaggregate)し、それぞれに最適なリソース割り当てと並列化戦略を適用するシステムである。著者らは、SLO(Service Level Objective)を達成するスループットを”Goodput”と定義し、vLLMと比較してGoodputを最大4.48倍向上させたと報告している。

この記事は Zenn記事: Vertex AI Model GardenでオープンLLMを本番デプロイする実践ガイド の深掘りです。Zenn記事で解説したPrefill/Decode段階の特性差と、それがコスト・性能最適化にどう影響するかを掘り下げます。

情報源

背景と動機(Background & Motivation)

LLM推論のPrefillとDecodeは本質的に異なる計算特性を持つ:

  • Prefill(計算律速): 入力プロンプト全体のAttention計算を一括処理。計算量はシーケンス長の二乗に比例し、GPU演算能力がボトルネックになる
  • Decode(メモリ帯域律速): 1トークンずつ逐次生成。モデル重みとKVキャッシュの読み出しI/Oが支配的

従来のLLMサービングシステム(vLLM、Orcaなど)は、PrefillとDecodeを同一GPUインスタンス上でコロケーション(同居)していた。この方式にはPrefill-Decode干渉という深刻な問題がある:長いプロンプトのPrefill処理中は、同じGPU上のDecode処理が待たされ、TPOT(Time Per Output Token)が悪化する。逆に、Decodeが多数並行する状況ではPrefillのTTFT(Time to First Token)が遅延する。

著者らはこの干渉問題を定量的に分析し、PrefillとDecodeを物理的に分離することで両方のSLOを同時に最適化できると主張している。

主要な貢献(Key Contributions)

  • 貢献1: “Goodput”の概念を提案。SLO(TTFT < 2秒、TPOT < 200ms等)を達成したリクエストのスループットのみを評価指標とし、単純なスループットとは区別した
  • 貢献2: Prefill/Decode分離アーキテクチャの設計と実装。各段階に独立したGPUプール、並列化戦略、スケーリングポリシーを割り当て可能にした
  • 貢献3: KVキャッシュ転送のオーバーヘッド分析。NVLink環境ではPrefill時間の10%未満に収まることを実証した

技術的詳細(Technical Details)

分離アーキテクチャの全体像

flowchart LR
    Client["クライアント"] --> Router["ルーター"]
    Router --> PF["Prefillインスタンス群<br/>(計算最適化GPU)"]
    PF -->|"KVキャッシュ転送<br/>(NVLink/RDMA)"| DC["Decodeインスタンス群<br/>(帯域最適化GPU)"]
    DC --> Client

DistServeの核心は、PrefillとDecodeを独立したGPUインスタンスプールで処理する点にある。各プールは異なるリソース構成を持つことができる:

  • Prefillプール: 計算能力重視。Tensor Parallelism(TP)を大きく設定し、TTFT SLOを達成
  • Decodeプール: メモリ帯域重視。Pipeline Parallelism(PP)が有効な場合もある。TPOT SLOを達成

Goodputの定義

著者らが提案するGoodputは以下のように定義される:

\[\text{Goodput} = \frac{|\{r \in R : \text{TTFT}(r) \leq T_{\text{TTFT}} \land \text{TPOT}(r) \leq T_{\text{TPOT}}\}|}{T_{\text{total}}}\]

ここで、

  • $R$: 処理されたリクエスト集合
  • $\text{TTFT}(r)$: リクエスト$r$のTime to First Token
  • $\text{TPOT}(r)$: リクエスト$r$のTime Per Output Token
  • $T_{\text{TTFT}}, T_{\text{TPOT}}$: 各SLO閾値
  • $T_{\text{total}}$: 総観測時間

通常のスループット(全リクエスト数/時間)とは異なり、SLOを達成できなかったリクエストはGoodputにカウントされない。これにより、「高スループットだがレイテンシが悪い」構成と「適切なスループットでSLOを達成する」構成を正しく比較できる。

KVキャッシュ転送メカニズム

PrefillインスタンスからDecodeインスタンスへのKVキャッシュ転送は、DistServeの性能を左右する重要な要素である。

KVキャッシュの転送データ量は以下で計算される:

\[D_{\text{KV}} = 2 \times n_{\text{layers}} \times n_{\text{heads}} \times d_{\text{head}} \times s \times \text{sizeof(dtype)}\]

ここで、

  • $n_{\text{layers}}$: Transformerレイヤー数
  • $n_{\text{heads}}$: Attentionヘッド数
  • $d_{\text{head}}$: ヘッド次元数
  • $s$: シーケンス長(プロンプト長)
  • 係数2はKeyとValueの両方を転送するため

たとえばLLaMA-13B(40レイヤー、40ヘッド、128次元、FP16)でプロンプト長1024トークンの場合:

\[D_{\text{KV}} = 2 \times 40 \times 40 \times 128 \times 1024 \times 2 \text{ bytes} \approx 838 \text{ MB}\]

論文の実験によれば、NVLink環境ではこの転送がPrefill時間の10%未満で完了すると報告されている。PCIe環境ではオーバーヘッドが大きくなるため、高速インターコネクトが前提となる。

スケジューラ設計

DistServeのスケジューラはSJF(Shortest Job First)ベースで実装されている。Prefillキューではプロンプト長の短いリクエストを優先し、TTFT SLOの達成率を最大化する。

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
from dataclasses import dataclass
from typing import Optional
import heapq

@dataclass
class InferenceRequest:
    """推論リクエスト"""
    request_id: str
    prompt_length: int
    max_output_tokens: int
    ttft_slo_ms: float = 2000.0
    tpot_slo_ms: float = 200.0

class DistServeScheduler:
    """Prefill/Decode分離スケジューラの簡略化実装"""

    def __init__(self, num_prefill_instances: int, num_decode_instances: int):
        self.prefill_queue: list[tuple[int, InferenceRequest]] = []
        self.decode_queue: list[InferenceRequest] = []
        self.num_prefill = num_prefill_instances
        self.num_decode = num_decode_instances

    def submit(self, request: InferenceRequest) -> None:
        """リクエスト投入: SJF (Shortest Job First) でPrefillキューに追加"""
        heapq.heappush(self.prefill_queue, (request.prompt_length, request))

    def schedule_prefill(self) -> Optional[InferenceRequest]:
        """Prefillスケジューリング: 最短プロンプトを優先"""
        if self.prefill_queue:
            _, request = heapq.heappop(self.prefill_queue)
            return request
        return None

    def transfer_to_decode(self, request: InferenceRequest) -> None:
        """Prefill完了後、KVキャッシュとともにDecodeキューへ転送"""
        self.decode_queue.append(request)

    def compute_goodput(
        self, completed: list[dict], total_time_s: float
    ) -> float:
        """Goodput計算: SLO達成リクエスト数 / 総時間"""
        slo_met = sum(
            1 for r in completed
            if r["ttft_ms"] <= r["ttft_slo_ms"]
            and r["tpot_ms"] <= r["tpot_slo_ms"]
        )
        return slo_met / total_time_s

実装のポイント(Implementation)

DistServeを本番運用する際の重要な設計判断:

  • Prefill/Decode GPU比率: トラフィックパターンに強く依存する。プロンプトが長くDecodeが短いワークロード(要約タスク等)ではPrefillインスタンスを多く、逆にチャットボット的な用途ではDecodeを多くする
  • インターコネクト要件: NVLink/InfiniBand/RDMAが前提。PCIe接続のみの環境ではKVキャッシュ転送がボトルネックとなり、分離のメリットが減少する
  • TP構成の非対称性: Prefill側はTP数を大きく(計算分散)、Decode側はTP数を小さく(メモリ帯域活用)設定することで、Goodputを最大化する

Production Deployment Guide

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

トラフィック量別の推奨構成:

規模月間リクエスト推奨構成月額コスト主要サービス
Small~3,000 (100/日)Serverless$50-150Lambda + Bedrock
Medium~30,000 (1,000/日)Hybrid$500-1,500ECS Fargate (Prefill/Decode分離)
Large300,000+ (10,000/日)Container$4,000-10,000EKS + Prefill/Decode専用ノードプール

Large構成の詳細(Disaggregated推論):

  • Prefillノード: p5.48xlarge (H100 x8) × 2台, TP=8
  • Decodeノード: g5.48xlarge (A10G x8) × 4台, TP=2
  • EFA: インスタンス間KVキャッシュ転送用
  • S3: モデル重みストレージ

コスト試算の注意事項: 上記は2026年5月時点のAWS料金に基づく概算値です。GPUインスタンスの料金はリージョン・Spot可用性により変動します。

Terraformインフラコード

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
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"

  cluster_name    = "distserve-cluster"
  cluster_version = "1.31"

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  cluster_endpoint_public_access = true
  enable_cluster_creator_admin_permissions = true
}

resource "kubectl_manifest" "prefill_nodepool" {
  yaml_body = <<-YAML
    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: prefill-gpu-pool
    spec:
      template:
        metadata:
          labels:
            inference-stage: prefill
        spec:
          requirements:
            - key: node.kubernetes.io/instance-type
              operator: In
              values: ["p5.48xlarge"]
          limits:
            nvidia.com/gpu: "16"
      disruption:
        consolidationPolicy: WhenEmpty
        consolidateAfter: 60s
  YAML
}

resource "kubectl_manifest" "decode_nodepool" {
  yaml_body = <<-YAML
    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: decode-gpu-pool
    spec:
      template:
        metadata:
          labels:
            inference-stage: decode
        spec:
          requirements:
            - key: karpenter.sh/capacity-type
              operator: In
              values: ["spot"]
            - key: node.kubernetes.io/instance-type
              operator: In
              values: ["g5.12xlarge", "g5.48xlarge"]
          limits:
            nvidia.com/gpu: "32"
      disruption:
        consolidationPolicy: WhenEmpty
        consolidateAfter: 30s
  YAML
}

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

  • Prefill/Decode比率をトラフィック分析に基づいて設定
  • Decodeノードは Spot Instances 優先(中断耐性が高い)
  • PrefillノードはOn-Demand/Reserved(計算中断不可)
  • EFA有効化でKVキャッシュ転送レイテンシ削減
  • AWS Budgets: 月額予算設定(80%で警告)
  • CloudWatch: Goodput、TTFT、TPOT監視
  • Cost Anomaly Detection有効化
  • 日次コストレポート: SNS/Slack送信
  • 未使用GPU削除: Trusted Advisor活用
  • タグ戦略: prefill/decode別コスト可視化
  • S3ライフサイクル: 古いモデル自動削除
  • 開発環境: 夜間/週末にGPUノード停止
  • KMS暗号化: S3/EBS
  • TLS 1.2以上使用
  • CloudTrail有効化
  • Prefill/Decode比率の定期見直し(月次推奨)
  • Spot割り込み時のKVキャッシュ再計算コスト試算
  • インターコネクト帯域のモニタリング
  • 負荷テストによるGoodput測定(デプロイ前必須)
  • GPU世代更新検討(半年ごと)

実験結果(Results)

著者らが報告した主要なベンチマーク結果(論文Table 1, Figure 6より):

評価項目vLLMDistServe改善率
Goodput(OPT-13B, ShareGPT)1x4.48x4.48倍
P99 TTFT(LLaMA-30B)1x1.7x改善1.7倍削減
SLO達成率(TTFT < 2s, TPOT < 200ms)~60%~95%+35pp

分析ポイント:

  • Prefill/Decode分離により、長プロンプトのPrefill処理がDecode中のリクエストに干渉しなくなった
  • Goodputの劇的な改善は、SLO違反リクエストの大幅削減に起因する
  • KVキャッシュ転送のオーバーヘッドはNVLink環境でPrefill時間の10%未満であり、分離のメリットが上回った

実運用への応用(Practical Applications)

DistServeのPrefill/Decode分離アーキテクチャは、Vertex AI Model GardenのvLLMカスタムビルドやllm-d(Google Cloud発のオープンソースプロジェクト)にも取り入れられている概念である。

Zenn記事で解説したScale-to-ZeroやAuto Scalingの設定を行う際、Prefill/Decodeの特性差を理解することで、より効率的な構成が可能になる:

  • Scale-to-Zeroが適するのはDecodeリソース: Decodeインスタンスは比較的軽量で、スケールイン/アウトが容易
  • Prefillは常時稼働が推奨: コールドスタート時のPrefill遅延がTTFT SLOを直撃するため、最低1インスタンスの常時稼働が望ましい
  • 異種GPU構成の可能性: Prefillに計算特化GPU(H100)、Decodeにコスト効率GPU(L4)を使い分ける構成が考えられる

関連研究(Related Work)

  • vLLM/PagedAttention(Kwon et al., SOSP 2023): DistServeのベースとなるサービングエンジン。PrefillとDecodeが同一GPU上で動作する従来方式
  • Splitwise(Patel et al., 2024): DistServeと独立に同様のPrefill/Decode分離を提案した研究
  • Sarathi-Serve(Agrawal et al., 2024): Chunked Prefillにより分離せずにPrefill-Decode干渉を軽減するアプローチ

まとめと今後の展望

DistServeは、LLM推論のPrefillとDecodeを物理的に分離することで、Goodputを最大4.48倍向上させた。この分離アーキテクチャは、llm-dやTensorRT-LLMなど最新の推論フレームワークにも取り入れられつつあり、本番LLMサービングの次世代標準アーキテクチャとなる可能性が高い。

参考文献

この投稿は CC BY 4.0 でライセンスされています。