文章

LLM Tensor Parallel 推理部署生产实战:用并行度、NCCL 与拓扑感知降低跨卡抖动

本文讲解大模型跨卡推理部署中的 Tensor Parallel、Pipeline Parallel、NCCL 通信与拓扑感知调优,覆盖并行度选择、节点网络、压测指标和上线检查,帮助团队降低多 GPU 服务抖动。

背景问题:模型放得下,不等于服务跑得稳

当模型规模从 7B、13B 增长到 70B、100B 以上时,单张 GPU 往往不再足够。团队通常会先想到两个方案:换更大显存的 GPU,或者把模型切到多张 GPU 上。前者简单但成本高,后者灵活但会把问题从”显存够不够”转成”跨卡通信稳不稳”。

在生产推理里,Tensor Parallel 的主要价值不是让系统看起来更分布式,而是把同一个模型副本的矩阵计算拆到多张 GPU 上,使单个大模型能够以一个逻辑副本对外服务。它解决的是”单副本放不下或单卡计算太慢”的问题,而不是普通水平扩容的问题。

这也是很多 LLM Serving 项目第一次上多卡时容易翻车的地方:

  • 模型能启动,接口能返回,但 P95/P99 延迟非常抖
  • 压测单请求正常,并发一上来就 NCCL timeout
  • 同一组 GPU 换了槽位或节点,吞吐差异明显
  • 跨节点部署后 TTFT 变长,TPOT 也不稳定

本文重点讨论:如何选择 tensor_parallel_sizepipeline_parallel_size,如何理解 NCCL 通信成本,如何按 GPU / NIC 拓扑做部署约束,以及上线前应该压测什么。

核心原理:Tensor Parallel 本质上是在每层制造通信

Tensor Parallel 切的是层内矩阵

在 Transformer 推理中,注意力层和 MLP 层都包含大量矩阵计算。Tensor Parallel 会把一层中的权重矩阵按列或按行切分到多张 GPU 上,让这些 GPU 同时计算各自的分片,然后再通过集合通信把结果合并。

这与 Data Parallel 完全不同:

并行方式切分维度解决的问题通信特点
Data Parallel多副本处理不同请求吞吐不足梯度同步(训练)/ 无同步(推理)
Tensor Parallel单副本层内矩阵切分单卡放不下/算力不足每层都可能引入跨卡同步
Pipeline Parallel单副本层间切分单节点放不下层间传递,存在 pipeline bubble

Hugging Face Transformers 的 Tensor Parallel 文档将其描述为”将模型层切成多个片段,让多个加速器同时工作”,并明确提示需要快速的节点内通信,因为 GPU 会在每一层交换部分结果。这个描述非常适合生产团队理解它的成本边界:Tensor Parallel 的收益来自并行计算,成本来自每层通信

NCCL 是跨 GPU 通信的关键路径

在 NVIDIA GPU 集群里,NCCL 是最常见的集合通信库。它提供 AllReduce、Broadcast、Reduce、AllGather、ReduceScatter、AlltoAll 等通信原语,也支持点对点 send/receive。Tensor Parallel 推理中,常见瓶颈并不是某个普通 HTTP 接口,而是这些集合通信在每层、每个 batch、每个 decode step 上的累计开销。

典型链路可以抽象为:

request -> tokenizer -> GPU shard 0/1/2/3 compute
  -> NCCL collective communication
  -> next layer compute
  -> NCCL collective communication
  -> logits -> sampling -> token response

如果 GPU 之间有 NVLink 或 NVSwitch,节点内 Tensor Parallel 往往更容易跑稳。如果只是 PCIe,尤其是跨 CPU socket 或跨 NUMA 域,通信延迟和带宽都会更敏感。跨节点 Tensor Parallel 对网络要求更高,通常需要 InfiniBand、RoCE、GPUDirect RDMA、正确的 NIC 选择和容器共享内存配置。

Pipeline Parallel 切的是层间阶段

Pipeline Parallel 与 Tensor Parallel 的切分维度不同。它把模型按层拆成多个阶段,例如前 20 层在节点 A,后 20 层在节点 B。这样可以减少某些层内 AllReduce,但会引入 pipeline bubble:某些阶段等待前一阶段输出,GPU 不能一直满负荷工作。

vLLM 的并行扩展文档给出的实用建议是:

  • 如果模型适合单 GPU,不需要分布式推理
  • 如果模型太大但能放进单节点多 GPU,使用 Tensor Parallel
  • 如果模型太大单节点放不下,把 Tensor Parallel 与 Pipeline Parallel 结合起来
  • 通常将 tensor_parallel_size 设置为每节点 GPU 数,将 pipeline_parallel_size 设置为节点数

这个经验适合绝大多数首版生产部署。

工程落地:先做并行度选择,再做拓扑约束

第一步:按显存和模型副本选择并行策略

可以按下面的顺序做初始判断:

  1. 单卡能放下模型、KV cache 和目标并发:优先单卡副本 + Data Parallel 水平扩容
  2. 单卡放不下,但单节点多卡能放下:优先 Tensor Parallel
  3. 单节点多卡仍放不下:Tensor Parallel + Pipeline Parallel
  4. GPU 数不能均匀切分模型,或节点内没有高速互联:评估 Pipeline Parallel 是否比 Tensor Parallel 更稳定

vLLM 文档中还有一个很实用的细节:启动后查看日志里的 GPU KV cache sizeMaximum concurrency。前者表示 GPU KV cache 能同时存储多少 token,后者估算在给定 max_model_len 下能支持多少并发。如果这个估算低于业务要求,继续调并行度或加 GPU 才有意义;否则单纯调入口队列和网关限流不会解决底层容量问题。

第二步:把并行参数显式固化

不要让生产服务”自动猜”并行度。并行度应该成为发布配置的一部分,并纳入回滚记录。

一个 vLLM 单节点 4 卡 Tensor Parallel 示例:

vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 \
  --max-model-len 32768 \
  --gpu-memory-utilization 0.90

一个 2 节点、每节点 8 卡的组合示例:

vllm serve /models/llama-70b \
  --tensor-parallel-size 8 \
  --pipeline-parallel-size 2 \
  --distributed-executor-backend ray

这些参数不是一次性命令行细节,而是发布版本的一部分。建议把它们写入模型服务配置表,并与模型版本、量化版本、max_model_len、batch 参数、GPU SKU、节点池标签一起记录。

第三步:给 GPU 和 NIC 做亲和性约束

多 GPU 推理的稳定性高度依赖拓扑。生产部署时至少要记录以下信息:

nvidia-smi topo -m
nvidia-smi -L
ibstat || true
NCCL_DEBUG=INFO vllm serve ...

如果是 Kubernetes,应避免调度器把多卡任务随意放到不合适的节点或跨 NUMA 拓扑较差的位置。vLLM 文档中提到,为 GPUDirect RDMA 配置容器时,需要关注 IPC_LOCK/dev/shm、共享内存挂载等条件;并建议通过 NCCL_DEBUG=TRACE 查看日志,确认是否出现 NET/IB/GDRDMA,而不是退化为 NET/Socket

一个 Kubernetes 侧的简化约束示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-tp-server
spec:
  template:
    spec:
      nodeSelector:
        gpu.interconnect: nvlink
        rdma.enabled: "true"
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          securityContext:
            capabilities:
              add: ["IPC_LOCK"]
          resources:
            limits:
              nvidia.com/gpu: 8
          volumeMounts:
            - mountPath: /dev/shm
              name: dshm
      volumes:
        - name: dshm
          emptyDir:
            medium: Memory

关键是把 GPU 互联、RDMA 能力、节点池隔离、容器共享内存这些条件配置化,而不是靠人工记忆。

压测方法:把 prefill、decode 和通信分开看

不要只看总 tokens/s

总吞吐很容易掩盖问题。一个多卡部署可能在平均 tokens/s 上很好看,但 P99 TPOT 非常差,原因是某些请求触发长上下文 prefill、某些 batch 下通信拥塞、某些 NIC 路径退化为 TCP socket。

建议至少拆出这些指标:

指标关注点
TTFT(首 token 时间)排队、prefill、模型加载状态
TPOT / ITL(每输出 token 间隔)decode 阶段的跨卡同步成本
P95 / P99 latency尾延迟,不要只看平均值
GPU compute utilization算力是否真的被利用
GPU memory / KV cache 使用率是否快接近 eviction 或 OOM
NCCL collective 耗时通信是否成为主瓶颈
跨节点网络吞吐、丢包、重传、RoCE ECN/PFC 状态跨节点 TP 必看

压测矩阵要覆盖并行度组合

建议建立一个小型矩阵,而不是只测一个配置:

model: 70B instruct
input tokens:  512 / 4k / 16k / 32k
output tokens: 128 / 1k / 4k
parallelism:
  - TP=1, DP=N
  - TP=2, DP=N/2
  - TP=4, DP=N/4
  - TP=8
  - TP=8, PP=2
metrics:
  - TTFT p50/p95/p99
  - TPOT p50/p95/p99
  - tokens/s
  - request/s
  - GPU memory
  - NCCL time
  - error / timeout

如果 TP 从 4 增加到 8 后吞吐没有明显提升,甚至尾延迟变差,通常说明通信开销已经抵消了计算收益。此时不应继续盲目增加 TP,而应评估更合适的模型量化、减少 max_model_len、改用更多 Data Parallel 副本,或者把跨节点 TP 改成单节点 TP + 多副本负载均衡。

让 NCCL 日志进入灰度检查

NCCL 文档强调 collective operation 需要每个 rank 以相同 count 和 datatype 调用,否则可能出现 hang、crash 或数据损坏。线上表现往往不是”立刻报错”,而是卡住、超时、重启、某些 rank 长时间无响应。

灰度时建议启用临时详细日志:

export NCCL_DEBUG=INFO
export NCCL_DEBUG_SUBSYS=INIT,GRAPH,NET,COLL

观察重点包括:

  • 是否使用期望的网络接口
  • 是否启用 InfiniBand / RDMA / GDRDMA
  • 是否出现 socket fallback
  • 是否有 rank 初始化慢或 communicator 超时
  • 是否跨 NUMA、跨 socket 或跨低速链路

日志量较大,不建议长期全量开启 TRACE,但灰度、压测和故障复盘阶段必须保留样本。

常见误区

误区一:GPU 越多越快

Tensor Parallel 不是免费的。每增加一张 GPU,计算切得更细,但通信同步也更多。对于小 batch、短输出或没有高速互联的节点,更多 GPU 可能降低单请求延迟稳定性。

误区二:Tensor Parallel 可以替代 Data Parallel

两者解决的问题不同。Tensor Parallel 解决单副本模型放不下或单卡计算不足;Data Parallel 解决多请求吞吐。实际生产中经常是 TP 组成一个大模型副本,再用多个副本做 DP。

误区三:只要模型启动成功就可以上线

多卡模型启动成功只说明权重加载和基本通信可用。它不能证明长上下文、并发 batch、错误恢复、节点重启、网络退化、NCCL 超时都可控。上线前必须压测目标请求分布。

误区四:跨节点 TP 和单节点 TP 一样配置

跨节点 TP 对网络非常敏感。没有 InfiniBand、RoCE、GPUDirect RDMA 或正确 NIC 亲和性的情况下,跨节点 Tensor Parallel 可能产生明显尾延迟抖动。很多场景下,单节点 TP + 多副本 DP 比跨节点 TP 更稳定。

适用场景

适合使用 Tensor Parallel 的场景:

  • 单卡显存无法容纳模型权重和目标 KV cache
  • 需要服务 70B、100B 以上模型,且节点内有高速 GPU 互联
  • 单副本需要更高 decode 吞吐,且业务能接受一定跨卡通信开销
  • 需要结合 Pipeline Parallel 运行单节点放不下的大模型

不适合优先使用 Tensor Parallel 的场景:

  • 模型单卡已经稳定,瓶颈只是请求量大
  • 节点没有高速互联,PCIe 拓扑复杂
  • 请求多为短上下文、短输出,通信开销占比高
  • 团队还没有 NCCL、GPU 拓扑、RDMA、压测和故障恢复经验

上线检查清单

上线前至少确认以下事项:

  1. 模型版本、量化版本、max_model_len、TP/PP/DP 配置已固化
  2. 已记录 GPU SKU、显存、NVLink/NVSwitch/PCIe 拓扑和 NIC 位置
  3. 已完成短输入、长输入、短输出、长输出的压测矩阵
  4. 已分别记录 TTFT、TPOT、P95/P99、tokens/s、request/s
  5. 已检查 vLLM 日志中的 GPU KV cache size 和 Maximum concurrency
  6. 已验证 NCCL 使用期望网络路径,没有意外 socket fallback
  7. Kubernetes 节点标签、GPU 资源、IPC_LOCK、/dev/shm、RDMA 条件已纳入部署模板
  8. 灰度阶段保留 NCCL、GPU、网络和服务层日志
  9. 已定义 NCCL timeout、rank hang、GPU OOM、节点重启时的自动摘除与回滚策略
  10. 已准备降级方案:降低 max_model_len、减少并发、切换小模型、回退单节点 TP 或多副本 DP

参考资料

  1. vLLM Parallelism and Scaling
  2. vLLM Engine Arguments
  3. Hugging Face Transformers: Tensor Parallelism
  4. NVIDIA NCCL Overview
  5. NVIDIA NCCL Collective Operations
  6. TensorRT-LLM Useful Build-Time Flags
  7. gLLM: Global Balanced Pipeline Parallelism System for Distributed LLM Serving with Token Throttling
  8. Parallel Track Transformers: Enabling Fast GPU Inference with Reduced Synchronization

常见问题

Tensor Parallel 和 Data Parallel 应该怎么选?
如果单个模型副本无法放进一张 GPU,优先考虑 Tensor Parallel 或 Pipeline Parallel;如果单副本已经能稳定运行,只是吞吐不足,通常先增加 Data Parallel 副本。
为什么多加 GPU 后延迟反而变差?
跨卡推理会引入 AllReduce、AllGather、ReduceScatter 等通信开销。GPU 拓扑、NVLink、PCIe、InfiniBand、GPUDirect RDMA 和 NCCL 配置都会影响尾延迟。
上线前最关键的压测指标是什么?
不要只看 tokens/s。应同时观察 TTFT、TPOT、P95/P99 延迟、GPU 利用率、NCCL 通信耗时、KV cache 容量、最大并发估算和错误重试率。
Tensor Parallel 和 Pipeline Parallel 可以同时使用吗?
可以。常见做法是节点内使用 Tensor Parallel,节点间使用 Pipeline Parallel。这样每个节点内部承担一段模型,同时节点内多 GPU 分摊该阶段的层内计算。实际收益取决于模型大小、层数、GPU 拓扑、网络和请求分布。