预计阅读 17 分钟

05. 千卡 / 万卡集群推理:规模化才暴露的工程问题

谁该读这一篇? 要把一个大模型(尤其大 MoE,如 DeepSeek 量级)部署到几百、上千、上万张卡上的人。你已经懂 TP/PP/EP/DP 怎么切(前四章),现在要回答的是另一类问题:这些机制在规模上去之后会以什么方式坏掉、怎么定位、怎么救。

前置阅读: 01-tp-pp-ep.md(并行机制 + §11 故障传染)、02-disaggregated.md(P/D 分离)、03-expert-parallel-deep-dive.md(AllToAll / EPLB)。本章是这三章在"千卡/万卡"尺度上的实战延伸。

耗时: 约 30 分钟

学完能: 1. 说清"规模上去"为什么是质变而不是量变——通信墙、故障墙、长尾墙三条。 2. 画出一个大 MoE 服务单元的真实形态(DP-attention + 大 EP),并解释它为什么动辄上百卡。 3. 对 6 类规模化典型故障,给出"现象 → 怀疑 → 验证 → 处置"的工业排查路径。 4. 解释 EPLB / DP padding 对齐 / 微批重叠 / 弹性 EP / blast radius 控制各自治的是哪个规模化病。

前四章讲"怎么把模型切到多卡"。这一章讲一件不一样的事:当卡数从 8 涨到 8000,很多在小集群上无所谓的设计,会变成压垮服务的主因。规模不是把同样的问题放大,而是引入新问题。


1. 规模上去,质变在哪:三堵墙

小集群(≤16 卡) 千卡 / 万卡
通信墙 NVLink 机内全连,通信几乎免费 跨机、跨 rail、跨 spine;网络成为吞吐的硬限制,AllToAll/AllReduce 打满网卡
故障墙 几周挂一次,手动重启 单卡年故障率 × 万卡 → 每天挂若干次,必须自动化;NCCL 是 fail-stop,一个 rank 死/慢拖垮整组
长尾墙 batch 内长尾,单实例可控 集合通信是同步屏障——最慢的那张卡决定整组的步长,一张慢卡污染上百卡

这三堵墙是后面所有具体问题的根。记住一个贯穿全章的判断:在大规模同步并行里,性能不由平均值决定,由最慢的那个 rank 决定(tail-bound);可用性不由单卡可靠性决定,由 blast radius(一次故障波及多少卡)决定。


2. 一个大 MoE 服务单元长什么样

先建立画面,否则后面问题没有落点。以 DeepSeek-V3/R1 这类"大 MoE + MLA"模型为例,单个服务单元(serving unit)通常是这样组织的(具体数字随版本/硬件变,这里取量级):

  • Attention 部分走 DP:每张卡持有完整的注意力权重(MLA 把 KV 压成低秩 latent,KV 很小),各卡处理不同请求的 attention。
  • MoE 部分走 EP:256 个 logical expert 摊到几十张卡上,每张卡只持有几个 expert 的权重;每个 token 经路由发到它命中的 expert 所在的卡(AllToAll),算完再收回(AllToAll)。
  • 于是同一批卡,在 attention 阶段是 DP、在 MoE 阶段是 EP——这就是"DP-attention + EP-MoE"形态。
  • 规模上:DeepSeek 公布过 prefill 阶段 EP32、decode 阶段 EP320(含冗余专家)这一量级。也就是说,一个模型实例的 decode 就横跨上百张卡,再把这样的实例复制 N 份填满集群。
flowchart TB
    subgraph Unit["一个 decode 服务单元(上百卡,逻辑视图)"]
        direction TB
        A["Attention 阶段:DP<br/>每卡独立算自己 batch 的 attention(MLA,KV 小)"]
        A -- "AllToAll dispatch<br/>token 按路由发到 expert 所在卡" --> M["MoE 阶段:EP<br/>每卡持 256/EP 个 expert"]
        M -- "AllToAll combine<br/>结果收回原卡" --> A
    end
    classDef dp fill:#eff5ff,stroke:#2563eb,color:#1a1f29;
    classDef ep fill:#fef3c7,stroke:#b45309,color:#1a1f29;
    class A dp;
    class M ep;

关键认知:这个单元里每一步 forward 都有两次全员参与的 AllToAll(dispatch + combine),它们是同步点。 第 3、4 节的长尾和通信问题全从这两次 AllToAll 长出来。

为什么 attention 用 DP 而不是 TP?因为 MLA 的 KV 已经很小,attention 不是显存瓶颈;DP 避免了 attention 里的 AllReduce,把宝贵的网络带宽全留给 MoE 的 AllToAll。这是大 MoE 部署的标志性取舍,参见 07-model-architectures.md(MLA)与 03-expert-parallel-deep-dive.md


3. 问题一:EP 同步长尾 —— 一张慢卡拖垮整组(最核心)

这是大规模 MoE 推理最普遍、最隐蔽的性能问题。

机理。 decode 每步有两次 AllToAll,它们是同步屏障:所有 EP rank 必须都到达才能继续。于是这一步的耗时 = max(各 rank 的计算时间),而不是平均。任何让某个 rank 变慢的因素,都会让其余几百张卡空等:

  • Expert 负载不均(hot expert):路由是数据相关的,某些 expert 天然更热门,承载它的卡 token 多、算得慢,其余卡等它。
  • DP-attention 各 rank batch 不等:DP 下不同 rank 分到的请求数/序列长度不同 → attention 阶段 token 数不等 → 到 AllToAll 处对齐时,token 少的 rank 早早算完干等。
  • 落后的慢卡(straggler):ECC 错误重试、thermal throttle 降频、邻居噪声(同机其他实例抢 PCIe/内存带宽)。

分析思路。

  1. 每个 rank 每步的计算时长直方图——是个别 rank 长期偏慢(straggler)还是随 batch 抖动(负载不均)。
  2. AllToAll 等待时间占 step 的比例——高占比说明在同步点空等。
  3. expert 命中分布(vLLM 的 RoutedExpertsCapturer 统计)——是否高度集中在少数 expert。
  4. 各 DP rank 的 batch/token 数方差——方差大说明 DP 负载没均衡。

处置(按问题对症)。

病因 解法 vLLM 里
Hot expert EPLB:给热门 logical expert 加冗余副本(redundant expert),把热度摊到多张卡;周期性 rearrange physical→device 映射 vllm/distributed/eplb/eplb_state.py;DeepSeek-R1 例:256 logical + 32 redundant = 288 physical,32 EP rank → 每卡 9 个
DP rank 负载不均 DP token 对齐(padding):每步用一次轻量 all-reduce 同步各 rank 的 token 数,padding 到一致,避免在 AllToAll 处长短不齐 vllm/v1/worker/dp_utils.py(DP padding 同步)
同步点空等本身 微批重叠(DBO / ubatch):把一个 batch 拆两个 micro-batch 交错跑,micro-batch A 在 AllToAll 通信时,micro-batch B 在算 attention——用计算把通信时间藏起来 vllm/v1/worker/ubatching.pygpu_ubatch_wrapper.py
个别慢卡 慢节点检测 + 主动驱逐(见 §5) 运维层

EPLB 的代价:rearrange 要搬 expert 权重(几百 MB~GB 级),不能太频繁;而且加冗余 expert 吃显存。所以不是默认开——只在监控确认负载不均显著、且持续时才开(判据见 03-expert-parallel-deep-dive.md §6.4)。


4. 问题二:AllToAll 撞上网络墙

EP 越大,AllToAll 越是 all-pairs 通信。万卡尺度上,网络(IB/RoCE)才是真正的天花板,不是算力。

机理。

  • All-pairs 流量:EP=320 的 dispatch 理论上是 320×320 的点对点,跨机流量极大。
  • 拓扑不友好:跨 rail(不同网卡平面)、跨 spine 的流量要走更高层交换机,带宽更窄、跳数更多。
  • ECMP hash 碰撞:多条 RDMA 流被哈希到同一条物理链路 → 单链路热点,明明总带宽够却卡在一条链路上。
  • Incast:combine 阶段多卡同时往一卡回灌 → 交换机队列爆、丢包、触发拥塞控制(DCQCN)退避,延迟飙升。

分析思路。

  1. 实测 AllToAll 时延 vs 理论值(03-expert-parallel-deep-dive.md §5 给了各 backend 的量级)。慢一个数量级 → 网络问题。
  2. 看 NIC 收发带宽是否打满、交换机有没有丢包/PFC pause、队列深度。
  3. 看是不是个别链路热(ECMP 碰撞)而非整体带宽不足。

处置。

  • 分层 AllToAll:机内走 NVLink、机间走 IB,两级聚合,把跨机流量压到最小——这正是 DeepEP 干的事(NVSHMEM + IBGDA,GPU 直发 RDMA 不经 CPU)。vLLM 通过 --all2all-backend deepep_high_throughput(prefill)/ deepep_low_latency(decode)启用。
  • 拓扑感知放置:让同一个 EP 组尽量落在同 rail/同 leaf 下,减少跨 spine;K8s 上用 topology-aware 调度 + NCCL_IB_HCA 绑定网卡平面。
  • combine 端 reduce 下沉:在交换机做 in-network reduction(SHARP)或分层规约,缓解 incast。
  • 拥塞控制调参:RoCE 下调 DCQCN/PFC,避免 pause 风暴。

一句话:大 MoE 在通用 NCCL AllToAll 上一定撞墙,这就是 DeepSeek 必须自研 DeepEP 的原因03-expert-parallel-deep-dive.md §4 行 155)。


5. 问题三:故障墙 —— 万卡每天都在挂

机理(先算一笔账)。 假设单卡 + 其网卡/光模块的年故障率合计 2%,万卡集群期望年故障 ≈ 200 次,平均一两天就挂一次;叠加链路抖动、ECC、thermal,实际"非完全健康"事件远多于此。在这个尺度,故障不是异常,是常态,必须设计成自愈。

最致命的一点:NCCL 是 fail-stop 的。 一个 rank crash 或 hang,同一个 communicator 里的其他 rank 会在下一次集合通信处一起 hang,默认要等到 watchdog timeout(几分钟)才报错退出。也就是说:

  • 一个 8 卡 TP 实例里挂 1 张卡 = 整个实例死
  • 一个 EP320 的单元里有 1 张卡 hang = 这上百卡全卡住直到超时。

这把"blast radius"变成第一设计原则。

分析思路:区分三种"坏"。

类型 现象 怎么认
Crash(卡/进程死) 某 rank 进程退出、NCCL 报 unhandled cuda error / remote process exited 进程监控 + NCCL 错误日志
Hang(卡住不动) 整组步频归零、watchdog timeout、GPU-Util 100% 但无产出 NCCL watchdog、心跳超时
Straggler(变慢不死) 整组步频下降但没报错,某 rank 每步偏慢 per-rank step timing 直方图(§3)

最难的是 straggler——不报错,只是慢,监控不盯 per-rank timing 根本发现不了。

处置。

  • 控制 blast radius:实例(TP×PP 的 communicator)尽量小。不要为省显存无脑拉大 TP——TP 越大,单卡故障炸掉的范围越大。能用 DP 多副本就别用大 TP。
  • 利用"推理无状态":推理实例除了 KV cache 没有需要持久化的状态,KV 可重算。所以恢复策略简单粗暴:从 LB 摘除 → 杀掉整个实例 → 拉新实例 → warmup 后重新入流量。配合冗余副本 + 全局 LB,单实例死对用户近乎无感(in-flight 的请求重试到别的副本)。
  • 快速失败而非长等:调短 NCCL watchdog/心跳超时,让 hang 尽快暴露并触发重建,而不是干等几分钟。
  • 慢卡主动驱逐:监控 ECC 计数、SM 时钟、per-rank timing,命中阈值就把该节点 cordon 掉,别让它当 straggler 拖整组。
  • 弹性 EP(进阶):EP 组支持在不停服的情况下动态增减 rank(重算 expert 映射),让坏卡能被在线替换而不是整单元重启——vLLM 的 EPLB 基础设施(vllm/distributed/eplb/)是这条路的起点。
  • K8s 配套:liveness/readiness 探针 + 反亲和(同实例的 rank 别全堆一台机的同一故障域)+ PodDisruptionBudget(01-tp-pp-ep.md §11.2、06-reliability-and-failure-modes.md)。

6. 问题四:冷启动 / 权重加载 / rollout 被规模放大

小集群忽略的环节,万卡上会变成事故。

机理。

  • 权重加载风暴:几百 GB 权重 × 数千张卡同时从共享存储(NFS/对象存储)拉 → 存储 IOPS 和出口带宽被打爆,启动时间从分钟变成几十分钟。
  • CUDA Graph capture × 数千实例:每个实例都要 capture 多个 batch size 的图,单实例几十秒,乘以规模 + 抢占资源 → 漫长。
  • 滚动重启慢:要换版本/改配置时,逐批重启一个万卡 fleet 可能要数小时,期间容量打折。

处置。

  • 权重不要让每张卡各自去存储拉:一处读入,再用 RDMA / tree broadcast 在集群内扩散("读一次、广播 N 份");或用节点本地只读缓存 + 预分发。
  • 分批 rollout + warmup gate:就绪探针必须等 CUDA Graph capture + warmup 完成才放流量,否则新副本一上线就长尾爆炸(10-gpu-utilization-and-tail-latency.md §6.7)。
  • sleep / wake 复用实例:vLLM 支持把实例置于 sleep(卸 KV、留权重)再快速唤醒(metric vllm:engine_sleep_state),切模型/缩容时比冷启动快得多。
  • 容量规划留 rollout 余量:滚动重启期间的容量缺口要提前用 buffer 副本补上(04-autoscaling-and-capacity.md)。

7. 问题五:跨数千副本的全局调度与路由

单副本内的调度(Scheduler)前面讲过;万卡上多了一层跨副本的全局路由问题。

机理。

  • Prefix-cache 亲和失效:同一会话/同一系统 prompt 的请求若被随机打散到不同副本,本可命中的 KV 前缀全 miss,TTFT 抬升、算力浪费。规模越大、副本越多,随机打散的代价越大。
  • P/D 分离下的 KV 路由:prefill 池算完要把 KV 传给某个 decode 实例(NIXL/RDMA),选哪个 decode 实例、怎么不打爆它,是全局决策。
  • 热点副本:负载/KV 占用不均,部分副本过载抢占、部分空闲。

处置。

  • 分层路由:上层按区域/容量做粗负载均衡,下层按 prefix 亲和 + KV 占用 + 队列深度打分选副本(02-smart-routing-and-load-balancing.md)。
  • P/D 比例动态调整:prefill 重(长 prompt 多)就加 prefill 池,decode 重就加 decode 池;workload 漂移时在线调比例(02-disaggregated.md)。
  • 全局调度器形态:llm-d / AIBrix / NVIDIA Dynamo 这类"集群级 inference gateway + 全局调度"就是冲这个问题去的(01-deployment-architectures.md)。

8. 问题六:万卡上的可观测性 —— 找到那一张慢卡

规模化让监控本身变难。

机理与难点。

  • 基数爆炸:per-rank × per-layer × 万卡的指标维度,Prometheus 直接被打爆。
  • 均值骗人:集群平均吞吐很好看,但 §3 说了性能是 tail-bound——你要找的是那一张拖后腿的卡,它淹没在均值里。

处置。

  • 盯分布而非均值:per-rank step time 的 p99/max、各 DP rank 的 token 数方差、AllToAll 等待时长——这些才暴露 straggler 和负载不均。
  • 慢节点雷达:一个专门的"哪张卡最慢"视图(按 per-rank timing 排序),比一堆均值面板有用。
  • 关键先行指标vllm:num_preemptions_total(KV 压力)、AllToAll wait 占比、NCCL timeout 计数、DP imbalance、vllm:engine_sleep_state
  • 采样式分布式 trace:全量 trace 万卡不现实,按比例采样 + 异常请求强制采样。
  • 基础打法见 05-slo-and-observability.md08-monitoring-cookbook.md,本节是它们在万卡尺度的补充。

9. 规模化排查总表(现象 → 怀疑 → 验证 → 处置)

把全章压成一张工业 runbook:

现象 第一怀疑 验证 处置
整组吞吐低、各 rank 计算时长方差大 DP/expert 负载不均 per-rank token 数与 step 时长直方图、expert 命中分布 DP padding 对齐;EPLB;微批重叠
整组吞吐低、某一两 rank 长期偏慢 straggler(ECC/throttle/邻居噪声) per-rank timing 排序、nvidia-smi 时钟/ECC cordon 驱逐慢卡 → 重建实例
forward 慢、网络带宽高/丢包 AllToAll 撞网络墙 AllToAll 实测 vs 理论、NIC/交换机队列、ECMP 热点 DeepEP/分层 AllToAll;拓扑感知放置;调拥塞控制
整组步频归零、GPU-Util 100% 无产出 NCCL hang(某 rank 死/卡) NCCL watchdog 日志、心跳 调短 timeout 快速失败 → 摘流量 → 重建整实例
启动/扩容极慢 权重加载风暴 / capture 慢 存储出口带宽、就绪耗时 广播式加载;分批 rollout;warmup gate;sleep/wake
TTFT 抬升、prefix 命中率掉 跨副本路由没做亲和 vllm:prefix_cache_hit_rate、路由日志 prefix-aware/session-sticky 路由
部分副本过载抢占、部分空闲 全局负载不均 / P/D 比例失配 各副本 num_requests_running/KV 占用 负载打分路由;动态调 P/D 比例;扩容

小结

  • 规模上去是质变:通信墙(网络成瓶颈)、故障墙(挂卡成常态)、长尾墙(最慢的 rank 决定整组)。
  • 大 MoE 服务单元 = DP-attention + 大 EP,单实例横跨上百卡,每步两次 AllToAll 是同步点。
  • EP/DP 同步长尾是最核心的性能病:hot expert → EPLB,DP rank 不均 → token padding 对齐,同步空等 → 微批重叠(DBO)。
  • AllToAll 撞网络墙靠分层通信(DeepEP/NVSHMEM)+ 拓扑感知放置 + 拥塞控制。
  • 故障墙的第一原则是控制 blast radius(别无脑拉大 TP)+ 利用推理无状态做快速重建 + 慢卡驱逐 + 弹性 EP。
  • 冷启动/路由/可观测性在万卡上都被放大,分别用广播加载/warmup gate、分层亲和路由、盯分布而非均值来治。

自检(先自答,再看要点)

1. 为什么说大规模同步并行里"性能由最慢的 rank 决定"?这对监控有什么直接含义?

要点:每步的 AllReduce/AllToAll 是同步屏障,step 耗时 = max(各 rank 时长),所以一张慢卡拖整组。含义:监控必须盯 per-rank 时长分布的 p99/max 和方差,不能只看集群均值——均值好看的同时可能有一张卡在拖后腿(straggler)。

2. 一个 EP320 的 decode 单元里有一张卡 hang 了,会发生什么?为什么这影响"该不该把 TP 拉很大"的决策?

要点:NCCL fail-stop,这上百卡会在下一次 AllToAll 一起 hang 到 watchdog timeout。blast radius = 整个 communicator。所以并行维度(尤其 TP/EP communicator)越大,单卡故障炸掉的范围越大;能用 DP 多副本分散就别为省显存无脑拉大 TP。

3. DP-attention 下发现各 rank 吞吐不齐、整体被拖慢,根因和解法?

要点:DP 各 rank 分到的请求数/序列长度不同 → attention 阶段 token 数不等 → 到 MoE 的 AllToAll 同步点时长短不齐、快的等慢的。解法:每步用一次轻量 all-reduce 同步各 rank token 数并 padding 对齐(vllm/v1/worker/dp_utils.py),再叠加微批重叠把通信藏到计算后。

4. EPLB、DP token padding、微批重叠,三者治的分别是哪个问题?

要点:EPLB 治 expert 维度的负载不均(hot expert,加冗余副本+重排);DP token padding 治 DP rank 维度的 token 数不齐;微批重叠(DBO/ubatch)治 同步点空等本身(用计算掩盖 AllToAll 通信)。三者正交,可叠加。

5. 万卡集群权重加载为什么会成为瓶颈?怎么解?

要点:几百 GB × 数千卡同时从共享存储拉 → 存储出口带宽/IOPS 被打爆,启动几十分钟。解法:一处读入再 RDMA/tree broadcast 扩散,或节点本地只读缓存预分发;配合分批 rollout 和 warmup gate(capture 完才放流量)。


下一步