
大模型推理部署从单卡到集群的工程化落地路径一、LLM 推理上线的三座大山延迟、吞吐与显存将一个大语言模型从实验环境推向生产环境面临的第一个现实问题就是 GPU 显存。一个 70B 参数的模型FP16 精度下需要约 140GB 显存单张 A10080GB根本装不下。即使采用 INT4 量化仍需约 35GB加上 KV Cache 和运行时开销单卡推理的并发能力极其有限。某智能客服平台上线 LLM 服务后发现单卡 A100 在并发 10 个请求时 P99 延迟已达 8 秒用户等待超时率超过 30%。更棘手的是不同请求的 prompt 长度差异巨大短 prompt 推理 200ms 完成长 prompt 需要 5 秒混合调度导致短请求被长请求阻塞。LLM 部署不是简单的模型加载而是一套涉及显存管理、请求调度、模型并行的系统工程。二、LLM 推理引擎的架构设计2.1 推理服务整体架构graph TD A[API Gateway] -- B[请求调度器] B -- C[Prompt长度预估] B -- D[优先级排序] B -- E[批处理组装] E -- F[推理引擎] F -- G[模型并行层] G -- G1[TP: 张量并行] G -- G2[PP: 流水线并行] F -- H[KV Cache管理器] H -- H1[PagedAttention] H -- H2[显存池化复用] F -- I[量化与加速] I -- I1[INT8/INT4量化] I -- I2[FlashAttention] I -- I3[Continuous Batching] B -- J[推理节点集群] J -- J1[Node-1: 7B模型] J -- J2[Node-2: 70B模型-TP4] J -- J3[Node-3: 13B模型]2.2 KV Cache 与显存管理sequenceDiagram participant R1 as 请求1 participant R2 as 请求2 participant PM as PagedAttention管理器 participant VRAM as GPU显存 R1-PM: 申请KV Cache(预估token512) PM-VRAM: 分配4个物理页(每页128token) PM--R1: 返回页表映射 R2-PM: 申请KV Cache(预估token256) PM-VRAM: 分配2个物理页 PM--R2: 返回页表映射 Note over PM: 请求1生成超过512 tokenbr/动态追加物理页 R1-PM: 追加KV Cache PM-VRAM: 再分配1个物理页 PM--R1: 更新页表映射 Note over PM: 请求2完成br/释放物理页供复用 R2-PM: 释放KV Cache PM-VRAM: 物理页归还显存池PagedAttention 借鉴操作系统的虚拟内存分页机制将 KV Cache 按固定大小的物理页管理不同请求的 KV Cache 不需要连续显存极大降低了显存碎片化问题。三、生产级 LLM 推理服务实现3.1 Continuous Batching 调度器 Continuous Batching 调度器 核心思想不等整个batch完成已完成请求立即移出新请求动态加入 相比static batching吞吐量提升2-4倍 import time import threading from collections import deque from dataclasses import dataclass, field from typing import List, Optional dataclass class InferenceRequest: 推理请求封装 request_id: str prompt_tokens: List[int] max_new_tokens: int 512 temperature: float 0.7 # 运行时状态 generated_tokens: List[int] field(default_factorylist) kv_cache_pages: List[int] field(default_factorylist) is_completed: bool False submit_time: float field(default_factorytime.time) class ContinuousBatchScheduler: 连续批处理调度器 核心机制每个推理步完成后移出已完成请求从等待队列补充新请求 def __init__(self, max_batch_size: int 32, max_waiting_queue: int 1000): self.max_batch_size max_batch_size self.max_waiting_queue max_waiting_queue # 等待队列按提交时间排序 self.waiting_queue: deque[InferenceRequest] deque(maxlenmax_waiting_queue) # 当前活跃batch self.active_batch: List[InferenceRequest] [] # 已完成请求的结果暂存 self.completed_results: dict[str, List[int]] {} self._lock threading.Lock() def submit_request(self, request: InferenceRequest) - bool: 提交推理请求到等待队列 with self._lock: if len(self.waiting_queue) self.max_waiting_queue: return False # 队列已满拒绝 self.waiting_queue.append(request) return True def schedule_batch(self) - List[InferenceRequest]: 调度下一批推理请求 1. 移出已完成请求 2. 从等待队列补充新请求到max_batch_size 3. 返回当前活跃batch with self._lock: # 移出已完成的请求释放其资源 remaining [] for req in self.active_batch: if req.is_completed: self.completed_results[req.request_id] req.generated_tokens else: remaining.append(req) # 从等待队列补充新请求 available_slots self.max_batch_size - len(remaining) for _ in range(min(available_slots, len(self.waiting_queue))): new_req self.waiting_queue.popleft() remaining.append(new_req) self.active_batch remaining return self.active_batch def get_result(self, request_id: str) - Optional[List[int]]: 获取已完成请求的结果 with self._lock: return self.completed_results.pop(request_id, None) def get_metrics(self) - dict: 获取调度器运行指标 with self._lock: return { waiting_queue_size: len(self.waiting_queue), active_batch_size: len(self.active_batch), completed_count: len(self.completed_results), utilization: len(self.active_batch) / self.max_batch_size if self.max_batch_size 0 else 0, }3.2 模型并行加载管理器 张量并行(Tensor Parallelism)加载管理器 将模型权重按列或行切分到多张GPU协同推理 import torch import torch.distributed as dist class TensorParallelLoader: 张量并行模型加载器 支持将Transformer层权重按列切分到多卡 def __init__(self, model_config: dict, tp_size: int 4): self.tp_size tp_size # 张量并行度 self.tp_rank None # 当前进程的并行秩 self.model_config model_config # 初始化分布式进程组 if dist.is_initialized(): self.tp_rank dist.get_rank() else: self.tp_rank 0 def load_sharded_weight(self, weight_path: str, layer_name: str) - torch.Tensor: 加载分片权重每个GPU只加载属于自己的分片 避免全量加载导致单卡OOM # 读取权重元数据确定切分维度 full_weight torch.load(weight_path, map_locationcpu) weight_tensor full_weight[layer_name] if column in layer_name or gate in layer_name: # 列切分按输出维度切分每个GPU持有1/tp_size的列 shard torch.chunk(weight_tensor, self.tp_size, dim0)[self.tp_rank] elif row in layer_name or down in layer_name: # 行切分按输入维度切分 shard torch.chunk(weight_tensor, self.tp_size, dim1)[self.tp_rank] else: # 不切分的权重如LayerNorm每卡持有完整副本 shard weight_tensor # 释放全量权重节省CPU内存 del full_weight return shard.cuda() def all_reduce_output(self, tensor: torch.Tensor) - torch.Tensor: 张量并行的输出归约行切分层需要AllReduce求和 确保多卡结果一致 if self.tp_size 1: return tensor dist.all_reduce(tensor, opdist.ReduceOp.SUM) return tensor四、LLM 部署的架构权衡4.1 量化精度与推理质量的博弈INT4 量化将显存需求降低至 FP16 的 1/4但对模型能力有不可忽视的损伤。在代码生成和数学推理任务上INT4 量化的 70B 模型可能不如 FP16 的 13B 模型。GPTQ/AWQ 等算法通过校准数据集优化量化参数在多数场景下可将精度损失控制在 1-2%但在长文本生成和低资源语言场景下退化明显。4.2 TP vs PP 的选择张量并行TP将单层计算切分到多卡通信量大但延迟低适合节点内多卡流水线并行PP将不同层分配到不同卡通信量小但存在气泡适合跨节点部署。生产中通常混合使用节点内 TP节点间 PP。4.3 推理与训练的资源冲突GPU 集群同时承载训练和推理任务时训练任务的显存占用波动大可能导致推理服务 OOM。解决方案是物理隔离推理与训练 GPU 池或通过 Kubernetes GPU 优先级抢占确保推理服务的显存保障。4.4 禁用场景请求量极低10 QPS的内部工具单卡部署即可满足对延迟不敏感的离线批处理推理无需 Continuous Batching模型参数量小于 7B单卡即可高效推理五、总结LLM 推理部署是一项从显存管理到请求调度的系统工程。PagedAttention 解决了 KV Cache 的显存碎片问题Continuous Batching 显著提升了推理吞吐张量并行与流水线并行使得大模型跨卡部署成为可能。架构选型时需在量化精度与显存节省、并行策略与通信开销、推理延迟与吞吐量之间做出精准权衡核心原则是根据模型规模、流量特征和 SLA 要求选择最小复杂度的部署方案。