当单卡放不下一个 70B 模型,或单卡无法满足线上吞吐时,分布式推理就从"可选优化"变成"必选项"。本文系统讲解张量并行、流水线并行与专家并行三大策略的通信代价与适用场景,介绍连续批处理在集群中的落地,并深入 KServe 与 Ray Serve 两类 GPU 调度器的架构与配置。
一、为什么需要分布式推理
1.1 单卡装不下的显存账本
推理显存需求 = 模型权重 + KV Cache + 激活值。以 Llama-2-70B(FP16)为例:
模型权重:70e9 × 2 字节 ≈ 140 GiB
KV Cache:4096 长度、8 kv heads、80 层 ≈ 单请求约 20 MiB(可忽略)
一张 A100 80G 只能放下一半权重。即便用 https://plumephp.com/ai-llm-quantization/ 中的 INT4 量化把权重压到 ~35 GiB,还需要留出 KV Cache 与计算余量,仍显局促。单卡方案在模型规模面前天花板太低。
| 模型 | FP16 权重 | INT4 权重 | 最小可用显存 |
|---|---|---|---|
| Llama-2-7B | 14 GiB | 3.5 GiB | 24 GiB |
| Llama-2-13B | 26 GiB | 6.5 GiB | 40 GiB |
| Llama-2-70B | 140 GiB | 35 GiB | 4×80G 或 2×H100 |
| Mixtral-8x7B | 112 GiB | 28 GiB | 8×80G(EP 场景) |
1.2 分布式推理 vs 分布式训练
| 维度 | 分布式训练 | 分布式推理 |
|---|---|---|
| 目标 | 提升吞吐、缩短训练时间 | 单请求延迟低 + 集群吞吐高 |
| 数据流 | 前向 + 反向,梯度通信 | 仅有前向 |
| 批处理 | 大批量、静态形状 | 连续批处理、动态形状 |
| 通信频率 | 每个训练 step 一次 | 每请求 / 每 decode step |
| 热点关注 | 吞吐(MFU) | 延迟(TTFT/ITL)+ 吞吐 |
分布式推理的特殊之处在于:它是延迟敏感的、请求驱动的。不能简单照搬训练的 AllReduce 通信模式,而要考虑一次请求生命周期内的通信开销占比。
二、三种并行策略
2.1 张量并行(Tensor Parallel,TP)
原理:把单个 Transformer 层的权重矩阵按行/列切分到多张卡上。例如 Attention 的 Q/K/V 投影按列切分,输出投影按行切分。每张卡各自计算一部分,通过 AllReduce 汇总。
通信量:每个 Transformer 层两次 AllReduce(Attention 输出 + MLP 输出),通信量与 hidden size 成正比,与 batch 无关。因此 TP 对单请求延迟友好,但通信成本高,一般建议 TP ≤ 8。
# vLLM 张量并行:单卡放不下,用 4 卡
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-70B-chat \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.90
2.2 流水线并行(Pipeline Parallel,PP)
原理:把模型按层切段,每张卡负责连续的一段层。数据像流水线一样依次流过各段。通信量极小(仅层间激活传递),但存在"气泡"(流水线首尾填充的空闲时间)。
推理场景:PP 天然适合预填充与解码分离。理想状态下 Prefill 阶段跑段 1,同时 Decode 阶段跑段 2,交错执行减少气泡。
# TensorRT-LLM:TP=2, PP=2(2 张卡张量并行,另外 2 张卡接后半段)
trtllm-build --model_config config.pbtxt \
--tensor_parallel_size 2 \
--pipeline_parallel_size 2 \
--max_batch_size 128 \
--use_fused_mlp
2.3 专家并行(Expert Parallel,EP)
原理:MoE 模型的 FFN 由多个专家(Expert)组成,每个 token 只激活少数专家。EP 把不同专家放在不同卡上,token 通过 All2All 通信路由到对应专家所在卡。
关键点:EP 的通信量 = 路由到专家层的 token 数。因为 MoE 中每个 token 可能触发多次 All2All,EP 对单请求延迟影响比 TP 更显著,通常与 TP 组合使用(TP×EP)。
# vLLM MoE 模型:专家并行 4 + 张量并行 2
python -m vllm.entrypoints.openai.api_server \
--model mistralai/Mixtral-8x7B-Instruct \
--tensor-parallel-size 2 \
--expert-parallel-size 4
2.4 并行策略对比
| 维度 | 张量并行 TP | 流水线并行 PP | 专家并行 EP |
|---|---|---|---|
| 切分对象 | 层内权重矩阵 | 层序列 | MoE 专家 |
| 通信频率 | 每层 2 次 AllReduce | 仅层间(低频) | 每 token 多次 All2All |
| 通信量与 batch 关系 | 无关 | 相关(随 batch 增大) | 强相关 |
| 单请求延迟影响 | 小(低延迟友好) | 中(气泡) | 大(多次路由) |
| 典型规模 | ≤8 | 2~16 | 4~64 |
| 适用 | 单卡放不下、追求低延迟 | 超大模型、prefill/decode 分离 | MoE 模型(Mixtral、DeepSeek) |
工程上推荐组合策略:TP × PP 用于稠密大模型,TP × EP 用于 MoE 模型。具体配置逻辑与 https://plumephp.com/ai-tensorrt-llm/ 中的部署方案一脉相承。
2.5 通信原语与互连拓扑
并行策略的通信效率受制于底层互连。NCCL(NVIDIA Collective Communications Library)是 GPU 间集合通信的事实标准,负责执行 AllReduce、AllGather、All2All 等原语。
| 原语 | 方向 | 用途 | 通信量 |
|---|---|---|---|
| AllReduce | 多卡 → 汇总 → 广播回 | TP 中 Attention/MLP 输出合并 | 2 × hidden × batch |
| AllGather | 各卡广播自己的分片 | TP 中 QKV 前的输入聚合 | (N-1)/N × 数据量 |
| All2All | 每卡发给其他所有卡 | EP 中 token 专家路由 | 与路由分布相关 |
| P2P Send/Recv | 相邻两卡 | PP 层间激活传递 | 单次激活切片 |
NVIDIA 平台的两级互连:
NVLink(卡内多卡互联,900 GB/s H100)
└─────────────── IB / RoCE(跨节点,200~400 Gb/s)
拓扑感知调度是 GPU 集群调度的进阶能力:调度器优先把同一 TP 组的卡放在同一节点、同一 NVSwitch 域,以最大程度利用 NVLink 而非慢得多的跨节点网卡。
# 检查 NVLink 拓扑
nvidia-smi topo -m
# 输出示例(同一节点内 TP 组)
# GPU0 GPU1 GPU2 GPU3
# GPU0 NV NV NV NV
# GPU1 NV NV NV NV
# ...
一个经验法则:TP=8 的组建议放在同一节点;跨节点 TP 通信会因网卡带宽受限,显著拉高 decode 单步延迟。PP/EP 对跨节点相对宽容,但仍建议尽量减少跳数。
三、连续批处理与多实例部署
3.1 连续批处理在集群中的角色
https://plumephp.com/ai-vllm-system/ 中介绍的连续批处理(Continuous Batching)解决的是"单引擎内如何高效服务多请求"。放到集群层面,调度器还需要决定:
- 哪个请求去哪个副本:基于副本的负载与 token 余量
- 每个副本跑什么模型:模型分片(多副本 × 多并行度)
- 副本如何扩容缩容:按队列深度 / GPU 利用率触发
┌──────────── 请求队列 ────────────┐
│ │
LB + 路由 ────────────> vLLM 副本 A (TP=4, 70B) │
│ vLLM 副本 B (TP=4, 70B) │
│ vLLM 副本 C (TP=8, 8x7B MoE) │
└──────────────────────────────────┘
3.2 GPU 多实例与多副本
- MIG(Multi-Instance GPU):物理 GPU 切成多个隔离实例,适合"小模型多副本"场景,但 MIG 无法参与跨实例张量并行
- 多副本 + 队列:同一模型部署 N 个 vLLM 副本,前端负载均衡分发;每个副本独立连续批处理,集群吞吐 ≈ 单副本吞吐 × 副本数(线性扩缩)
# 每个副本的 vLLM 服务
replicas: 4
spec:
containers:
- name: vllm
command: ["python", "-m", "vllm.entrypoints.openai.api_server"]
args: ["--model", "Qwen/Qwen2-72B-Instruct", "--tensor-parallel-size", "4"]
resources:
limits:
nvidia.com/gpu: 4
3.3 显存分片与副本数规划
给定一个拥有 N 张 GPU 的集群,两种常见编排方式:
- 大副本(多卡 1 副本):模型并行度高,单副本吞吐高,但副本少 → 故障影响面大、弹性粒度粗
- 小副本(少卡多副本):副本数多,路由与伸缩粒度细,但并行度低时每副本吞吐下降
规划公式(粗略):
可部署副本数 ≈ 集群总 GPU 数 ÷ 单副本 TP 数
示例:32 张 A100,70B FP16 模型 TP=4
→ 最多 8 个副本,每副本提供约 70B/4=35 GiB/卡 的 KV 余量
实践建议:用 TP 数把单副本显存余量控制在 20%~35%(留给 KV Cache 与激活值),再按线上峰值吞吐估算副本数,最后叠加 1~2 个冗余副本。
四、GPU 集群调度器
4.1 KServe:Kubernetes 上的模型服务
KServe(原 KFServing)是基于 Kubernetes 的标准化推理平台,核心是 InferenceService 自定义资源(CRD)。它把模型服务建模为 Deployment + 自动扩缩(HPA/KPA)+ 灰度发布(Canary)。
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: llama-70b
spec:
predictor:
model:
modelFormat:
name: vllm
storageUri: s3://models/llama-70b/
args:
- --tensor-parallel-size=4
- --gpu-memory-utilization=0.9
resources:
limits:
nvidia.com/gpu: 4
scaleSettings:
minReplicas: 2
maxReplicas: 8
target: 50 # 基于 RPS 的自动扩缩目标
优势:与 K8s 生态(Prometheus、HPA、Ingress)无缝整合,多模型管理、金丝雀发布开箱即用。劣势:需要 GPU 节点池与 Kubeflow 全家桶,运维心智较重。
4.2 Ray Serve:Python 原生的推理调度
Ray Serve 是 Ray 生态提供的模型服务框架,把"模型副本"抽象为 Python 里的 Deployment,配合 Ray 的分布式运行时在集群中调度。对 GPU 的调度通过 ray_actor 的 num_gpus 声明完成。
from ray import serve
import vllm
@serve.deployment(
num_replicas=2,
ray_actor_options={"num_gpus": 4}, # 每个副本 4 张卡
autoscaling_config={
"min_replicas": 1, "max_replicas": 8,
"target_num_ongoing_requests_per_replica": 16,
},
)
class Llama70B:
def __init__(self, model_path: str):
self.llm = vllm.LLM(
model=model_path,
tensor_parallel_size=4,
gpu_memory_utilization=0.90,
)
async def __call__(self, request: dict):
result = self.llm.generate(request["prompt"],
sampling_params=request["params"])
return result
app = Llama70B.bind(model_path="s3://models/llama-70b")
serve.run(app)
优势:GPU 资源由 Ray 调度器统一管理,模型副本自动伸缩、异常自动重建,与 Python 生态(训练、数据管线)天然衔接。劣势:需要独立部署 Ray 集群,与 K8s 的网络/存储要自己打通。
4.3 KServe vs Ray Serve 对比
| 维度 | KServe | Ray Serve |
|---|---|---|
| 底层 | Kubernetes + CRD | Ray 分布式运行时 |
| 模型格式 | 存储路径(HuggingFace/S3) | Python 对象 + 模型路径 |
| GPU 调度 | K8s 节点池 + 显存配额 | ray_actor num_gpus |
| 自动伸缩 | KPA/HPA(RPS 或并发) | 按 ongoing requests / 队列 |
| 灰度发布 | 内置 Canary | 需自建(deployment strategy 有限) |
| 多模型 | 多 InferenceService | serve 图 + 路由策略 |
| 运维复杂度 | 中高(Kubeflow 生态) | 中(Ray 集群运维) |
| 典型场景 | 企业级标准化平台 | Python 团队、训练推理一体化 |
选型建议:已有成熟 K8s 平台的团队选 KServe;模型团队(尤其是从训练平滑过渡到推理)选 Ray Serve。两者都可以在前方再加一层网关实现统一路由。
五、推理集群架构设计
5.1 分层架构
┌─────────────────────────────────────────────────────┐
│ 接入层:Nginx / Envoy / 自研网关(鉴权、限流、计费) │
├─────────────────────────────────────────────────────┤
│ 路由层:按模型维度路由到对应 InferenceService / Serve │
├─────────────────────────────────────────────────────┤
│ 调度层:KServe 或 Ray Serve(弹性伸缩、GPU 分配) │
├─────────────────────────────────────────────────────┤
│ 执行层:vLLM / TensorRT-LLM 副本(TP/PP/EP 并行) │
└─────────────────────────────────────────────────────┘
5.2 弹性伸缩与冷启动
GPU 推理冷启动是集群性能的隐形杀手:模型权重加载(70B 从 S3 拉取 + 加载)+ 引擎初始化可能需要数十秒到数分钟。
| 冷启动环节 | 耗时量级 | 优化手段 |
|---|---|---|
| 权重下载 | 10~60s | 节点本地 NVMe 缓存 / 预拉取镜像 |
| 权重加载到显存 | 5~30s | safetensors mmap、并行加载 |
| CUDA Graph / 引擎编译 | 1~30s | 预热队列 + 后台常驻冷副本 |
| 首请求显存分配 | <1s | vLLM 启动时预分配 |
因此生产集群通常维护 1 个常驻冷副本 + 若干热副本,用"预热队列"让扩容出的副本在服务流量前完成模型加载。
5.3 路由、限流与容错
- 路由键:按模型名 + 推理后端版本路由,避免新旧模型混跑
- 限流:网关层按 API Key 的 QPS/token 配额限流,防止单请求洪峰拖垮整个副本
- 容错:副本健康检查失败后自动摘除;请求超时重试时避免放大(Jitter Retry)
- 队列背压:当所有副本排满时,网关返回 429 而非无限排队
这些治理能力与 https://plumephp.com/ai-llm-inference-architecture/ 中的企业级中台设计互相补充,一个侧重集群内部调度,一个侧重外部服务治理。
5.4 多租户与 GPU 配额
当多个团队共享同一个 GPU 集群时,需要配额治理,避免"一个团队跑满全部卡"。
- Namespace 级配额:K8s
ResourceQuota限制某团队可申请的 GPU 总数 - 优先级与抢占:生产任务优先级高于实验任务,低优先级任务在资源紧张时被抢占回收
- 显存隔离:
nvidia.com/gpu以整卡为单位分配;MIG 可提供 1/7、1/4 等细粒度切片,适合小模型租户
apiVersion: v1
kind: ResourceQuota
metadata:
name: gpu-quota
namespace: team-ml
spec:
hard:
requests.nvidia.com/gpu: "16"
limits.nvidia.com/gpu: "16"
合理的配额策略能让一个 GPU 集群同时服务多个模型、多个团队,而不至于因某个突发流量拖垮全局。
六、总结
| 知识点 | 核心要点 |
|---|---|
| 为什么分布式 | 单卡显存天花板 + 吞吐需求 |
| 张量并行 TP | 层内切分,AllReduce 密集,低延迟友好 |
| 流水线并行 PP | 层间切分,通信少,存在气泡 |
| 专家并行 EP | MoE 路由,All2All 密集,需与 TP 组合 |
| 连续批处理 | 集群吞吐 = 单副本吞吐 × 副本数 |
| GPU 调度器 | KServe(K8s 生态)/ Ray Serve(Python 原生) |
| 冷启动治理 | 预热队列 + 常驻冷副本 + 本地权重缓存 |
分布式推理的工程要点是"把模型切得下去、把请求调度得动"。切分策略决定单副本延迟下限,调度器决定集群吞吐上限。建议先在一台多卡机上用 vLLM 的 --tensor-parallel-size 打通 70B 推理,再引入 Ray Serve 做多副本编排,最后按需迁移到 KServe 纳入企业 K8s 平台。集群层面的容量与成本如何测算,可以参考 https://plumephp.com/ai-inference-benchmark/ 一文。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。