LLM 推理优化实战从 KV Cache 到连续批处理降低推理成本的工程路径一、推理成本大模型落地的最大瓶颈大模型应用落地的核心矛盾是推理成本与响应速度。以一个 70B 参数模型为例单次推理的 GPU 显存占用约 140GBFP16需要 2 张 A100-80G。如果 QPS 需求为 10则需要 46 张 A100 才能满足延迟要求。按云 GPU 价格计算月成本在 35 万美元。更具体的痛点一个智能客服系统高峰期 QPS 达 50平均输入 token 数 800、输出 token 数 300。使用 GPT-4 级别模型单次推理延迟 3~5 秒月 API 费用超过 10 万美元。这个成本在大多数业务场景下无法持续。推理优化的目标很明确在保证输出质量的前提下降低单次推理的 GPU 时间。下面从底层机制到工程实践逐层拆解优化手段。二、推理瓶颈定位显存带宽才是天花板LLM 推理分为两个阶段Prefill预填充和Decode解码。Prefill 阶段并行处理所有输入 token计算密集Decode 阶段逐 token 生成输出每步都需要读取全部 KV Cache显存带宽密集。graph LR subgraph 推理两阶段 A[Prefill 阶段br/处理输入 tokens] --|计算密集| B[生成 KV Cache] B -- C[Decode 阶段br/逐 token 生成] C --|显存带宽密集| D[每步读取 KV Cache] D -- C end subgraph 瓶颈分析 E[Prefill: 算力瓶颈br/GPU 利用率高] -- F[优化方向: 算子融合/Flash Attention] G[Decode: 带宽瓶颈br/GPU 利用率低] -- H[优化方向: KV Cache 压缩/连续批处理] end style A fill:#e1f5fe style C fill:#fff3e0 style E fill:#e8f5e9 style G fill:#ffebee关键数据Decode 阶段每生成一个 token需要从显存读取整个 KV Cache与序列长度成正比。对于 70B 模型序列长度 4096 时KV Cache 约 5GB。A100 的显存带宽为 2TB/s单次读取耗时约 2.5ms。如果算力是瓶颈GPU 利用率会很高但实际 Decode 阶段 GPU 利用率通常只有 10%~30%说明显存带宽才是真正的瓶颈。因此推理优化的核心策略是减少不必要的显存读取和提高 GPU 利用率。三、工程实践四层优化策略与代码实现3.1 KV Cache 优化PagedAttention传统实现中每个请求预分配最大序列长度的 KV Cache造成大量显存浪费。vLLM 的 PagedAttention 借鉴操作系统虚拟内存的分页机制将 KV Cache 划分为固定大小的 block如 16 tokens按需分配。# PagedAttention 的核心思想伪代码 class PagedKVCache: 分页 KV Cache 管理器减少显存碎片 def __init__(self, block_size: int 16, num_blocks: int 1024): self.block_size block_size # 预分配所有 block 的显存池 self.kv_pool self._allocate_kv_pool(num_blocks) # 空闲 block 链表 self.free_blocks list(range(num_blocks)) # 每个序列占用的 block 映射 self.seq_blocks: dict[str, list[int]] {} def _allocate_kv_pool(self, num_blocks: int): 预分配显存池避免运行时动态分配 # 实际实现中这里分配 GPU 显存 return [None] * num_blocks def allocate(self, seq_id: str, num_tokens: int) - list[int]: 为序列分配 KV Cache block num_needed (num_tokens self.block_size - 1) // self.block_size if len(self.free_blocks) num_needed: raise RuntimeError( f显存不足: 需要 {num_needed} blocks, f可用 {len(self.free_blocks)} blocks ) blocks [] for _ in range(num_needed): block_id self.free_blocks.pop() blocks.append(block_id) self.seq_blocks[seq_id] blocks return blocks def free(self, seq_id: str): 序列完成后释放 block if seq_id in self.seq_blocks: for block_id in self.seq_blocks[seq_id]: self.free_blocks.append(block_id) del self.seq_blocks[seq_id] def get_utilization(self) - float: 显存利用率 total len(self.free_blocks) sum( len(b) for b in self.seq_blocks.values() ) used sum(len(b) for b in self.seq_blocks.values()) return used / total if total 0 else 0.0PagedAttention 的收益显存利用率从传统方案的 20%40% 提升到 90%同等显存下可并发处理的请求数增加 24 倍。3.2 连续批处理Continuous Batching传统批处理是静态的等所有请求的 Prefill 完成后才开始 Decode一个请求生成完毕也要等整批完成。连续批处理在迭代级别动态调度每个迭代步已完成生成的请求被移出新请求加入GPU 始终满载。import time from dataclasses import dataclass from typing import Optional dataclass class Request: 推理请求 req_id: str input_tokens: list[int] max_output_tokens: int generated_tokens: list[int] None kv_blocks: list[int] None def __post_init__(self): if self.generated_tokens is None: self.generated_tokens [] property def is_finished(self) - bool: return len(self.generated_tokens) self.max_output_tokens class ContinuousBatcher: 连续批处理调度器 def __init__(self, max_batch_size: int 32): self.max_batch_size max_batch_size self.running: list[Request] [] self.waiting: list[Request] [] def add_request(self, req: Request): 添加新请求到等待队列 self.waiting.append(req) def schedule(self) - list[Request]: 调度一个批次的请求 # 移除已完成的请求 self.running [r for r in self.running if not r.is_finished] # 从等待队列补充请求直到批次满 while ( len(self.running) self.max_batch_size and self.waiting ): req self.waiting.pop(0) self.running.append(req) return self.running def step(self): 执行一步 Decode模拟 batch self.schedule() if not batch: return # 模拟一步推理每个请求生成一个 token for req in batch: # 实际实现中这里调用模型推理 req.generated_tokens.append(0) # placeholder def get_stats(self) - dict: return { running: len(self.running), waiting: len(self.waiting), batch_utilization: ( f{len(self.running) / self.max_batch_size:.1%} ), }连续批处理的收益GPU 利用率从静态批处理的 30%50% 提升到 80%吞吐量提升 23 倍。3.3 量化用精度换速度量化是最直接的降本手段。INT8 量化将模型权重从 FP16 压缩到 INT8显存减半推理速度提升 1.5~2 倍得益于 INT8 Tensor Core 的高吞吐。INT4 量化进一步压缩但精度损失需要评估。量化方案显存占用推理速度精度损失FP16基线100%1x0INT8W8A850%1.5~2x 1%INT4GPTQ25%2~3x1%~3%INT4AWQ25%2~3x 2%生产建议对精度敏感场景如数学推理、代码生成用 INT8对精度不敏感场景如对话、摘要可用 INT4。量化前必须在业务数据集上评估精度损失。3.4 Speculative Decoding用小模型猜大模型Speculative Decoding 的思路用一个 7B 小模型快速生成 K 个候选 token大模型一次前向传播验证这 K 个 token。如果全部正确相当于一次推理生成 K 个 token如果有错误从第一个错误位置重新生成。def speculative_decode_step( draft_model, # 小模型快速 target_model, # 大模型准确 input_ids: list[int], num_speculate: int 5, ) - list[int]: 单步 Speculative Decoding # 1. 小模型快速生成 K 个候选 token draft_tokens [] current_ids input_ids[:] for _ in range(num_speculate): next_token draft_model.generate_one(current_ids) draft_tokens.append(next_token) current_ids.append(next_token) # 2. 大模型一次前向传播验证所有候选 token # 大模型同时输出每个位置的概率分布 target_probs target_model.forward(current_ids) # 3. 从左到右验证找到第一个不匹配的位置 accepted [] for i, draft_token in enumerate(draft_tokens): # 大模型在该位置的概率最高的 token target_token argmax(target_probs[len(input_ids) i]) if draft_token target_token: accepted.append(draft_token) else: # 用大模型的 token 替代 accepted.append(target_token) break else: # 所有候选都正确额外采样一个 token last_token sample(target_probs[-1]) accepted.append(last_token) return acceptedSpeculative Decoding 的收益在候选接受率 80% 时对话场景常见推理速度提升 2~3 倍。代价是需要额外部署一个小模型增加显存占用和工程复杂度。四、优化策略的权衡与适用边界4.1 优化策略的 ROI 排序按投入产出比排序量化 连续批处理 KV Cache 优化 Speculative Decoding。量化改动最小一行配置收益最确定。连续批处理需要推理框架支持vLLM/TGI 已内置。KV Cache 优化同样由框架提供。Speculative Decoding 工程复杂度最高适合对延迟极度敏感的场景。4.2 精度与速度的平衡量化引入精度损失必须在业务数据集上评估。评估指标不是通用的 perplexity而是业务相关的准确率。例如代码生成场景用 passk对话场景用人工评估或 LLM-as-Judge。4.3 框架选型框架连续批处理PagedAttention量化支持Speculative DecodingvLLM支持支持GPTQ/AWQ支持TGI支持支持GPTQ/AWQ/bitsandbytes支持TensorRT-LLM支持支持INT8/INT4支持llama.cpp不支持不支持GGUF 全系列不支持4.4 禁用场景Speculative Decoding 在候选接受率低于 50% 时反而更慢小模型与目标模型差异大时。INT4 量化在数学推理任务上精度损失可能超过 5%需谨慎评估。连续批处理在单请求场景下无收益反而增加调度开销。五、总结LLM 推理优化的核心是减少显存带宽瓶颈和提高 GPU 利用率。PagedAttention 减少显存碎片连续批处理提高 GPU 利用率量化用精度换速度Speculative Decoding 用小模型加速大模型。按 ROI 排序量化 连续批处理 KV Cache 优化 Speculative Decoding。所有优化手段都必须在业务数据集上验证精度损失通用指标如 perplexity不能替代业务评估。框架选型上vLLM 和 TGI 是当前最成熟的开源方案。