大家好,我是小悟。
一、问题背景与技术选型
随着大模型参数规模突破千亿甚至万亿级别,单张GPU显存(如A100 80GB)已无法容纳完整模型。推理阶段虽然比训练显存需求低,但KV Cache仍需占用大量空间。以LLaMA-70B为例,FP16精度下模型权重约140GB,加上推理时的KV Cache,需要至少4张A100(80GB)才能流畅运行。
本文聚焦两种主流方案:
- 张量并行:将单个Transformer层的权重切分到多卡,适合单机多卡场景
- 流水线并行:按层切分,将不同层分配到不同设备,适合跨节点分布式推理
实际工程中通常混合使用:节点内张量并行,节点间流水线并行。
二、详细步骤
步骤1:环境配置与依赖安装
# 推荐使用NVIDIA PyTorch镜像 docker pull nvcr.io/nvidia/pytorch:23.12-py3 # 安装分布式推理框架(以vLLM为例,也支持Hugging Face TGI) pip install vllm ray # 验证多卡可见性 python -c "import torch; print(torch.cuda.device_count())"集群网络配置关键点:
- 使用InfiniBand或RoCE(RDMA over Converged Ethernet)降低通信延迟
- 设置
NCCL_IB_DISABLE=0启用InfiniBand - 调整
NCCL_SOCKET_IFNAME指定正确网卡
步骤2:模型并行策略设计
以在4台8×A100节点(共32卡)上部署LLaMA-70B为例:
策略决策: - 单机8卡内使用张量并行(TP=8) - 跨4节点使用流水线并行(PP=4) - 总并行度 = TP × PP = 8 × 4 = 32关键配置参数:
parallel_config = { "tensor_parallel_size": 8, # 张量并行度 "pipeline_parallel_size": 4, # 流水线并行度 "worker_use_ray": True, # 使用Ray做调度 "max_num_batched_tokens": 2048 }步骤3:模型加载与分片实现
使用vLLM自动化分片:
from vllm import LLM, SamplingParams llm = LLM( model="meta-llama/Llama-2-70b-hf", tensor_parallel_size=8, pipeline_parallel_size=4, distributed_executor_backend="ray", trust_remote_code=True, max_model_len=4096 )手动实现张量并行的核心逻辑:
def column_parallel_linear(input, weight, world_size, rank): # 列切分:weight按列切为chunks chunk_size = weight.size(1) // world_size weight_shard = weight[:, rank*chunk_size:(rank+1)*chunk_size] output = torch.mm(input, weight_shard) return output def row_parallel_linear(input, weight, world_size, rank): # 行切分后需要all-reduce聚合 chunk_size = weight.size(0) // world_size weight_shard = weight[rank*chunk_size:(rank+1)*chunk_size, :] output = torch.mm(input, weight_shard) torch.distributed.all_reduce(output, op=torch.distributed.ReduceOp.SUM) return output步骤4:分布式启动与调度
单机多卡启动:
# 使用vLLM内置启动器 python -m vllm.entrypoints.api_server \ --model meta-llama/Llama-2-70b-hf \ --tensor-parallel-size 8 \ --pipeline-parallel-size 1 \ --host 0.0.0.0 \ --port 8000跨节点启动(手动模式):
# 节点1(主节点) ray start --head --num-gpus=8 --dashboard-host=0.0.0.0 # 节点2、3、4 ray start --address='节点1IP:6379' --num-gpus=8 # 所有节点就绪后执行推理脚本 python distributed_inference.py使用torchrun启动(原生PyTorch):
# 单节点8卡 torchrun --nnodes=1 --nproc_per_node=8 inference.py # 多节点(需要node1和node2均执行) # Node1: torchrun --nnodes=2 --nproc_per_node=8 \ --rdzv_endpoint=node1_ip:29500 \ --rdzv_backend=c10d \ inference.py # Node2: 相同命令,自动发现步骤5:推理请求处理与负载均衡
请求批处理:
class DynamicBatcher: def __init__(self, max_batch_size=32, max_wait_ms=100): self.queue = [] self.max_batch_size = max_batch_size self.max_wait_ms = max_wait_ms async def add_request(self, prompt): # 动态组成批次 self.queue.append(prompt) if len(self.queue) >= self.max_batch_size: return self._flush() await asyncio.sleep(self.max_wait_ms / 1000) return self._flush()前缀缓存优化:
# vLLM自动启用前缀缓存,需配置 llm = LLM( model="...", enable_prefix_caching=True, # 复用系统提示词的KV Cache block_size=16 )步骤6:监控与故障恢复
关键监控指标:
import subprocess def get_gpu_metrics(): result = subprocess.run( ['nvidia-smi', '--query-gpu=memory.used,utilization.gpu,power.draw', '--format=csv,noheader,nounits'], capture_output=True, text=True ) return result.stdout # 集成到Prometheus from prometheus_client import Gauge, start_http_server gpu_memory = Gauge('gpu_memory_used_mb', 'GPU memory used', ['gpu_id'])健康检查与自动重启:
import ray from ray.util.placement_group import placement_group # 使用Ray的容错机制 @ray.remote(num_gpus=1, max_restarts=3) class InferenceWorker: def __init__(self, rank): self.rank = rank self.model = self.load_model() def inference(self, prompt): # 推理逻辑 pass # 监控worker健康状态 def health_check(): while True: for worker in workers: if not ray.await(worker.ping.remote(), timeout=5): ray.kill(worker) workers[worker.rank] = InferenceWorker.remote(worker.rank)三、详细总结
核心经验
1. 通信开销是主要瓶颈
- 张量并行中All-Reduce操作占比可达推理时间的30-40%
- 实测数据:TP=8时,NVLink互联延迟约2-3μs;使用PCIe交换延迟升至10-15μs;跨节点走网络延迟高达50-100μs
- 优化建议:优先将大权重切分放在NVLink域内,跨节点只传递activations
2. 显存与吞吐量的平衡
- 增大batch size可以提升吞吐(矩阵乘法更密集),但会线性增加KV Cache显存
- 对于70B模型,batch_size=32时KV Cache约占用35-40GB/卡
- 经验公式:单卡batch上限 ≈ (显存 - 模型权重分片) / (2 × 序列长度 × hidden_dim / 1024^3)
3. 工程落地的关键取舍
- 精度:FP16推理比INT8精度高2-3个百分点,但显存翻倍。推荐KV Cache用INT8,权重保持FP16
- 调度:动态批处理比静态批处理平均延迟高15%,但能适应实时流量波动
- 编译优化:使用FasterTransformer或TensorRT-LLM可提升30-50%性能,但算子定制成本较高
典型性能数据
| 模型 | TP | PP | 吞吐(tokens/s) | TTFT(首次token延迟) |
|---|---|---|---|---|
| LLaMA-70B | 4 | 1 | 1420 | 0.8s |
| LLaMA-70B | 8 | 1 | 2150 | 1.2s |
| LLaMA-70B | 8 | 2 | 1890 | 1.5s |
| LLaMA-70B | 8 | 4 | 1620 | 2.1s |
测试环境:4×A100 80GB节点,NVLink+InfiniBand,batch_size=16
避坑指南
- 显存碎片问题:长序列推理后显存无法完全释放 → 使用内存池(vLLM的PagedAttention已解决)
- 跨节点负载不均:流水线中某些层计算量大导致气泡 → 使用1F1B(One-Forward-One-Backward)调度策略
- 多进程通信死锁:NCCL超时默认30分钟 → 显式设置
NCCL_TIMEOUT=1800并添加心跳检测 - Ray对象存储溢出:中间结果未及时回收 → 设置
ray.init(object_store_memory=10**9)并定期强制GC
最佳实践清单
- ✅ 推理前先做性能压测,找到最优batch size和并行度组合
- ✅ 使用离线分析工具(如NVIDIA Nsight Systems)定位通信瓶颈
- ✅ 为重要服务配置模型副本和负载均衡器(推荐使用Nginx + ngx_http_upstream)
- ✅ 定期滚动更新模型版本,避免全部节点同时重启
- ✅ 预算充足时直接使用SXM形态GPU(NVSwitch全互联),比PCIe版本性能提升约40%
总结:多卡并行推理的本质是“用通信换显存”,成功的工程实践在于找到模型规模、硬件拓扑、吞吐延迟要求之间的帕累托最优点,并利用成熟框架(vLLM/TGI)避免重复造轮子。
谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。
您的一键三连,是我更新的最大动力,谢谢
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海