第 10 章 AI InfraGPU分布式训练

第 10 章 推理优化与服务

第10章 推理优化与服务

本章从推理核心指标与 KV Cache 内存管理入手,依次覆盖推理引擎与调度、高级优化技术、vLLM 调优实战、推理服务 架构、网关与模型管理、SLA 与可观测性、平台实战与端侧推理。

10.1 推理核心基础

推理核心基础覆盖推理系统指标体系、KV Cache 内存管理、KV Cache 卸载与分层以及注意力架构演进,为后续推理引擎 与优化技术提供度量口径与内存模型。

10.1.1 推理系统核心指标体系

推理系统是为用户提供模型服务的前沿阵地,其性能直接决定用户体验和运营成本。建立一套标准化的指标体系是推理系 统设计、优化和运维的前提。

  1. 延迟指标 生成式推理的延迟分为首 token 延迟和后续 token 延迟两个维度。 •TTFT(Time To First Token):从请求到达系统到第一个输出 token 生成的时间。主要贡献因素为 Prefill 计算时间 (对输入 prompt 的全量前向传播)、调度队列等待时间与 KV Cache 分配和初始化时间。
 # Source: TTFT measurement example
 import time
 def measure_ttft(engine, prompt: str) -> float:
     t0 = time.perf_counter()
     # Send request and wait for the first token
     first_token = next(engine.generate(prompt, max_tokens=1))
     t1 = time.perf_counter()
     return (t1 - t0) * 1000 # Convert to ms

•TPOT(Time Per Output Token):解码阶段每生成一个 token 的平均时间。对于生成 N 个 token 的请求: Total Time − TTFT TPOT = N •ITL(Inter-Token Latency):相邻两个 token 之间的时间间隔。ITL 的方差影响用户感知的“流畅度”,抖动大会产生 卡顿体感。 2) 吞吐量指标 •TPS(Tokens Per Second):系统每秒处理的总 token 数(含输入和输出)。对于推理服务,TPS 是反映系统整体处理 能力的核心指标。 •RPS(Requests Per Second):系统每秒处理的请求数。RPS 和 TPS 之间的关系取决于请求的输入/输出长度分布(见 图10-1)。 吞吐量与延迟的权衡关系如图10-1所示: Low Load Throughput▲Latency▲ Linear Zone Throughput▲Latency▲▲ Inflection Zone Throughput▲Latency▲▲ Saturation Zone ▲ 图10-1 推理系统的吞吐-延迟相图 推理服务的运行点应设在拐点区(Inflection Zone),此时延迟在 SLA 约束内,吞吐量接近峰值。运行在饱和区意味着请 求在队列中严重堆积。 3) SLA 层级设计 推理系统需要为不同服务等级定义 SLA,如表10-1所示: 表10-1 推理服务 SLA 层级 指标 交互型 (Chat) 批处理型 (Batch) 实时型 (RAG) TTFT P50 200ms 2s 100ms TTFT P95 500ms 5s 300ms TTFT P99 1s 10s 500ms TPOT P50 20ms 50ms 15ms TPOT P95 40ms 100ms 30ms 吞吐量目标 1000 RPS 100 RPS 500 RPS 可用性 99.9% 99.5% 99.95% 每百万 token 成本 $0.50 $0.50 $0.20 SLA 违反的代价随服务类型而异: •交互型:TTFT 超过 1s 时用户体验明显下降,会话放弃率上升(具体数值因产品和用户群体而异) •RAG 型:TTFT 超过 500ms 时下游应用超时,链路失败 •批处理型:吞吐量低于目标时任务积压,TAT(Turnaround Time)超标 4) 成本效率指标 CPMT(Cost per Million Tokens)衡量每百万输出 token 的综合服务成本,取 GPU 小时成本与基础设施小时开销之 和,除以单位时间输出的百万 token 数: GPU 成本/小时 + 基础设施成本/小时 CPMT = 总输出 tokens / 1,000,000 该指标把吞吐与单价折算为统一的商业口径,是部署选型与容量规划的核心依据。 GPU 效率(GPU Efficiency)指实际吞吐量与理论上限吞吐量的比值: 实际吞吐量 (tokens/s) GPU Efficiency = 理论上限吞吐量 (tokens/s) 理论上限是指在完美批处理(所有请求长度相同、同时到达)条件下可达到的吞吐量。实际 GPU 效率通常在 60-85%。 5) 指标体系的关系图 如图10-2所示,各指标通过 SLA 驱动延迟、吞吐与成本的闭环: SLA Definition Latency Requirement Batch Size Selection Throughput Availability Requirement Actual Latency GPU Utilization Redundant Deployment SLA Violation? Yes No Cost Efficiency CPMT Reduce Batch Size Increase Batch Size 图10-2 推理系统指标关系图 理解这套指标体系后,下一节将深入讨论对延迟和吞吐量同时有核心影响的 KV Cache 管理优化。

10.1.2 KV Cache 内存管理

KV Cache 是自回归生成推理中最大的内存消费者。对于长上下文推理,KV Cache 甚至可能超过模型权重本身。本节讨论 KV Cache 的内存模型和主要优化技术。

  1. KV Cache 内存分析 每个 token 的 KV Cache 占用由层数、KV 头数、头维度和精度决定: Mkv = 2 × L × Hkv × dh × dtype_size

其中 H 为 KV 头数,GQA 模型代入 KV 头数而非总头数(如 Llama-2-70B 的 H = 8)。按此估算,Llama-2-70B (FP16)约 320 KB/token:一条 4096 token 的序列占 1.25 GB,batch 32 时超过 60 GB,已接近单卡显存的上限。 kv kv 2) KV Cache 量化 将 KV Cache 量化到低精度是减少内存占用的最直接方法。KV Cache 是解码阶段最大的内存消费者,将 KV 从 FP16 压到 FP8/INT8 可近似翻倍可容纳的上下文长度或并发数。 FP8/INT8 KV Cache 量化:vLLM 通过 --kv-cache-dtype 参数支持 KV Cache 的低精度量化,主推 FP8(H100 原生支 持, fp8_e4m3 ),也支持 INT8(FP16 到 8bit 为 2x 压缩,FP32 到 8bit 为 4x 压缩):

 # Source: vLLM KV Cache
 from vllm import LLM, SamplingParams
 llm = LLM(
     model="meta-llama/Llama-2-70b-hf",
     quantization="fp8",           # Model weight quantization
     kv_cache_dtype="fp8",         # KV Cache FP8 quantization
     max_model_len=8192,
     gpu_memory_utilization=0.95,

) FP8 KV Cache 在 H100 上可将 KV Cache 压缩 2x(FP16 到 FP8),精度损失通常小于 0.1 PPL。量化对精度的影响如表 10-2所示: 表10-2 KV Cache 量化精度影响 KV Cache 类型 Llama-2-7B PPL (WikiText-2) 内存占用 (单 token) 相对 FP16 FP16 5.47 100% baseline BF16 5.47 100% baseline FP8 (E4M3) 5.48 50% -0.2% PPL INT8 (per-channel) 5.52 50% -0.9% PPL INT8 (per-token) 5.56 50% -1.6% PPL INT4 (per-channel) 6.10 25% -11.5% PPL 数据来源:FP8/INT8 数据来自 vLLM 和 SGLang 社区基准测试;INT4 数据来自学术研究(KIVI、KVQuant 等),当前 vLLM/SGLang 不支持 INT4 KV Cache。 3) Prefix Caching 前缀缓存 在服务化推理中,多个请求共享相同的前缀(如 system prompt、few-shot examples)。前缀缓存的工作原理如下: Request 1: "You are a helpful assistant. | What is 2+2?" ▶ KV Cache prefix "You are a helpful assistant." Request 2: "You are a helpful assistant. | Translate: Hello" ▶ Reuse prefix KV Cache Request 3: "You are a helpful assistant. | Summarize: ..." ▶ Reuse prefix KV Cache 前缀缓存减少了重复 prefill 的计算量,将 TTFT 降低为仅处理新前缀部分的 prefill 加全量 decode。 vLLM 的 Automatic Prefix Caching(APC)基于 token 序列的哈希识别共享前缀:

 # vLLM Automatic Prefix Caching Internals
 class PrefixCache:
     def __init__(self):
         self.cache = {} # hash(token_prefix) ▶ BlockTable
     def match_prefix(self, token_ids: List[int]) -> Optional[BlockTable]:
         # Use sliding window hash to find the longest matching prefix
         prefix_hash = compute_prefix_hash(token_ids)
         while len(token_ids) > 0:
             if prefix_hash in self.cache:
                 return self.cache[prefix_hash]
             token_ids = token_ids[:-1]
             prefix_hash = recompute_hash(token_ids)
         return None

SGLang 使用 Radix Tree(压缩前缀树)管理共享前缀,比哈希表更高效地匹配任意长度的共享前缀:

 # Source: SGLang RadixAttention Concept
 from sglang.srt.managers.schedule_batch import RadixCache
 radix_cache = RadixCache()
 # Radix Tree auto-detects
 # Supports partial prefix reuse:

前缀缓存的实际效果如表10-3所示: 表10-3 前缀缓存收益 场景 共享前缀比例 TTFT 降低 吞吐量提升 Chat(短 system prompt) 20-40% 15-30% 10-20% Few-shot prompting(100-shot) 60-80% 50-70% 30-50% RAG(长 context 多轮对话) 30-50% 25-40% 15-25% 批量推理(同一 template) 80-95% 70-90% 50-80% 4) KV Cache 淘汰与压缩 当 HBM 容量不足时,需要淘汰或压缩 KV Cache。 vLLM 使用块级别的 ALLOW_RECOMPUTATION 预占模式。当内存不足时,可以选择驱逐某些块的 KV Cache,需要时重 新计算:

 # Source: vLLM Block Manager
 class BlockSpaceManager:
     def allocate_block(self, seq_id: int) -> Block:
         if self.free_blocks:
             return self.free_blocks.pop()
         # No free blocks, trigger eviction
         victim = self.eviction_policy.select_victim()
         victim.mark_evicted()
         return victim
     def eviction_policy(self):
         # Hybrid strategy based on LRU + sequence priority (actual implementation uses weighted composite sorting, not
 short-circuit logic)
         return CompositePolicy([LRUEviction(), FIFOEviction()])

H2O(Heavy Hitter Oracle)观察到注意力分数分布是长尾的:少数的“重击者”(Heavy Hitter)token 占据了大部分 注意力权重。H2O 仅保留这些重击者的 KV Cache,将 KV Cache 压缩 5-10x。 StreamingLLM 发现最初的几个 token(Attention Sink)和最近的 token 对生成质量最关键。保留这二者的 KV Cache 即可支持无限长序列的生成(恒定缓存大小)。 5) 跨请求共享优化 在多轮对话中,每轮请求的 prompt 包含之前所有轮次的历史。通过维护会话级别的 KV Cache,每轮只需 prefill 新的用 户输入和 AI 回复: Round 1: [System] [User Q1] ▶ KV Cache for Q1 Round 2: [System] [User Q1] [AI A1] [User Q2] ▶ Reuse Round 1 KV Cache, only prefill [AI A1] [User Q2] Round 3: ... ▶ Continue reusing, only prefill new part KV Cache 管理的优化与注意力架构选择紧密耦合。当 HBM 仍不足以覆盖长上下文时,需要将 KV Cache 卸载到更慢但更 大的存储层;注意力架构的演进同样从源头影响推理内存与计算。

10.1.3 KV Cache 卸载与分层

KV Cache 是 LLM 推理中最大的显存消耗者之一。对于 Llama-70B,在 batch_size=64、序列长度=4096 的配置下,KV Cache 需约 80 GB 显存,超过模型权重本身(约 140 GB FP16)的一半。当推理服务需要支持更长上下文(32K-128K tokens)或更大 batch size 时,KV Cache 内存需求急剧膨胀,成为推理系统的首要瓶颈。

  1. 卸载的内在动机 KV Cache 卸载(KV Cache Offloading)的核心思想是并非所有 KV Cache 条目都需要驻留在 GPU HBM 中。将不活跃或 非紧急的 KV Cache 卸载到更大但更慢的存储层(CPU DRAM、NVMe SSD,甚至远程节点),可以在不明显影响推理延迟 的前提下支持更大的上下文和更高的并发。 关键权衡: •GPU HBM 访问延迟:约 1 μs(约 2 TB/s 带宽,H100) •CPU DRAM(GPU 通过 PCIe 访问):约 3-10 μs(约 50 GB/s 单向,PCIe 5.0 x16) •NVMe SSD 访问延迟:约 10-100 μs(约 7 GB/s 顺序读,PCIe 4.0 x4) •远程节点(RDMA):约 5-10 μs(约 400 Gbps 单向) 卸载策略的核心优化问题:在给定延迟 SLA 下,决定哪些 KV Cache 条目卸载到哪一层存储,以最大化支持的上下文长度 或并发请求数。
  2. vLLM 交换空间 vLLM 将 KV Cache 管理形式化为虚拟内存问题。PagedAttention 将 KV Cache 划分为固定大小的 page(如 16 tokens),页的物理位置可以在 GPU HBM 和 CPU DRAM 之间透明迁移。

vLLM config for KV Cache

vllm serve meta-llama/Meta-Llama-3-70B \

   --swap-space 16 \           # 16 GB CPU memory for KV Cache swap
   --gpu-memory-utilization 0.85 \
   --max-model-len 32768       # Support 32K context

vLLM 的交换策略:当 GPU HBM 中的 KV Cache 页空闲(对应请求已完成或处于等待队列中),vLLM 调度器将其换出到 CPU 内存的交换空间。当相同请求恢复时,换入回 GPU HBM。交换粒度是 page(而非整个 request),最小化数据传输 量。 3) 交换迁移开销 以 page_size=16, head_dim=128, num_layers=80, num_kv_heads=8 的 Llama-70B 为例,单 page 的 KV Cache 大小 为 16 × 128 × 2 × 80 × 8 × 2(K+V × layers × kv_heads × FP16 bytes)约 5.2 MB。在 PCIe 5.0 x16(约 64 GB/s) 下,单 page 从 CPU 到 GPU 的迁移延迟约 80 μs。若每个 decode step 需要换入 8 个 page(对应 8 个恢复的请求),总 开销约 640 μs,在 decode step 的 20-50 ms 时间尺度中仍可接受。 4) FlexGen SSD 卸载调度 FlexGen(2023, Stanford/UC Berkeley)开创性地将 KV Cache 卸载到 NVMe SSD,并设计了最优卸载调度算法以最小 化 SSD 读延迟对推理吞吐的影响。 FlexGen 的之字形(Zig-Zag)调度策略将模型计算与 KV Cache 的 SSD I/O 进行流水线交织。考虑 GPU 计算的 4 层 (Attention → MLP → Attention → MLP)与 SSD 读取的交错: Time ▶ GPU: [Attn1][MLP1][Attn2][MLP2][Attn3][MLP3] SSD: __[Read KV1][Read KV2]______[Read KV3] 通过将 SSD 读取操作安排在 GPU 计算层之间,实现计算与 I/O 的最大重叠。FlexGen 在 OPT-175B 上展示了:单 16 GB GPU(RTX 3090)加 1 TB NVMe SSD 可以以约 1 token/s 的速度进行推理。该速度虽然极慢,但证明了在极端内存约束 下运行 175B 模型的可能性。 5) InfiniGen 推测性卸载 InfiniGen(2024, CMU)提出推测性 KV Cache 卸载:并非将当前的 KV Cache 卸载,而是预先推测(speculate)未来 可能需要的 KV Cache 条目并提前从 CPU 预取到 GPU。 核心机制:在自回归生成的每个 step,基于当前的查询向量 Q 使用一个轻量级注意力近似(如 Sliding Window Attention 或 Top-K Sparse Attention)推测未来几个 step 可能需要的 KV 条目,提前从 CPU RAM 加载到 GPU HBM。推 new 测准确率在 80-90% 范围内,提供了净吞吐提升。 6) CacheGen KV Cache 压缩 CacheGen(2024, MIT/UChicago)从另一个角度解决 KV Cache 传输瓶颈:压缩 KV Cache 后再传输。关键发现:KV Cache 具有大量的时间冗余和通道冗余,相邻 token 的 KV 向量高度相关,同一 head 内不同通道的 KV 值分布相似。 CacheGen 使用自定义的编码方案: •时间差分编码:编码当前 token KV 与前一个 token KV 的差异,利用相邻 token 的高相关性 •通道预测:使用一个轻量 MLP 预测 KV 向量中低频变化的通道,仅编码预测残差 •量化:将残差量化为 INT4 实验表明:KV Cache 可压缩至原来的 1/4-1/5,解压缩开销小于 1 ms。在分离式 Prefill-Decode 架构中,Prefill 节点向 Decode 节点传输的 KV Cache 可用 CacheGen 压缩,将网络传输量降低 4-5x,直接降低端到端延迟。 7) 分离式 KV Cache 传输 在 Prefill-Decode 分离架构中,Prefill 实例完成输入 Prompt 处理后,需将 KV Cache 传输给 Decode 实例。传输数据 量:2 × num_layers × num_heads × head_dim × prompt_length × element_size。 以 Llama-70B(80 layers, GQA 8 KV heads, 128 head_dim, prompt=8192 tokens, FP16)为例:KV Cache 大小 = 2 × 80 × 8 × 128 × 8192 × 2 bytes 约 2.7 GB。 在 400 Gbps 网络下,裸传输需约 54 ms;加上网络协议开销和序列化,实际约 80-120 ms。对于请求延迟敏感的聊天应 用,这 100 ms 的额外成本是不可忽视的。 优化方案: •RDMA + GPU Direct:直接从 GPU HBM 通过 RDMA 写至远端 GPU HBM,避免 CPU bounce buffer •CacheGen 压缩:将 2.7 GB 压缩至约 600 MB,传输时间降至约 15 ms •分层 KV Cache Store:将 Prefill 节点的 KV Cache 写入一个共享的 KV Cache 存储(基于 NVMe 或分布式内存), Decode 节点按需拉取 8) NVMe KV Cache 性能 NVMe SSD 作为 KV Cache 介质的一个关键问题是随机读延迟是否满足 Token Generation 的实时性要求。典型 NVMe 4K 随机读延迟: •TLC NAND:80-120 μs •Optane DC Persistent Memory(已停产):10-15 μs •3D XPoint:约 10 μs 单次 decode step 需要读取的 KV Cache 量与 prompt length 成正比。以 prompt=4096 tokens, head_dim=128, num_layers=80, num_kv_heads=8, generate 1 token 为例:每次 decode 需读取 2 × 80 × 8 × 128 × 4096 × 2 bytes 约 1.34 GB 的 KV Cache 数据。 NVMe 7 GB/s 顺序读带宽下,1.34 GB 读取需约 191 ms,该延迟远大于一次 decode step(通常 10-30 ms),直接导致 decode 延迟翻倍甚至更多。因此,NVMe 卸载仅在 KV Cache 总量超出 HBM 容量时作为“最后的手段”,且通常需要配 合稀疏化或 selective retrieval 等辅助技术来减少实际读取量。 Swap 与 Recompute 的权衡:当 HBM 不足时,除换出(swap)外另一条路径是重算(recompute):丢弃部分历史 KV Cache,需要时重新执行前向传播恢复。Swap 的代价是迁移带宽(CPU 内存带宽约 20-50 GB/s,远低于 HBM 的 TB/s 级),Recompute 的代价是算力(恢复一个 block 的前向计算量随层数与序列长度线性增长)。经验法则:算力充沛而迁 移带宽受限时,对较早 token 采用 recompute;HBM 接近满载而算力紧张时,对访问频率低的历史 KV 采用 swap。 DeepSpeed Inference 与部分推理框架提供 recompute 与 swap 的混合模式,由调度器按剩余 HBM 水位动态分配两者 的比例。

10.1.4 注意力架构演进

注意力机制是 Transformer 的核心计算模块,其架构设计直接影响推理时的内存占用、计算复杂度和吞吐量。本节追踪 注意力架构从 MHA 到 MLA 的演进,重点分析对推理系统的影响。

  1. MHA 多头注意力 标准 MHA 中,每个注意力头拥有独立的 Q、K、V 投影:
 Q = W_q × x   Dim: (h, d_h)   ◀ h heads, each of dim d_h
 K = W_k × x   Dim: (h, d_h)
 V = W_v × x   Dim: (h, d_h)

内存占用: •KV Cache per token: 2 × L × h × d × dtype_size h •例如 Llama-2-7B(L=32, h=32, d_h=128, FP16):2 × 32 × 32 × 128 × 2 = 512 KB/token 推理瓶颈:在 decode 阶段(自回归生成),每个新 token 需要与所有历史 token 的 KV 做 attention。MHA 的 KV Cache 随序列长度和头数线性增长。 2) MQA 多查询注意力 MQA(Shazeer, 2019)将多个 Q 头共享同一个 K、V 头: Q: (h, d_h) ◀ h query heads, each independent K: (1, d_h) ◀ only 1 key head V: (1, d_h) ◀ only 1 value head KV Cache 压缩比:h 倍(即头数倍压缩)。 推理收益: •KV Cache per token(Llama-2-7B 规模,MQA):2 × 32 × 1 × 128 × 2 = 16 KB/token •相比 MHA:512 KB → 16 KB,32x 压缩 质量代价:MQA 将多个查询强制共享同一组 KV,损失了注意力多样性。在较大模型上,MQA 会导致困惑度升高 0.3-0.8 点。 3) GQA 分组注意力 GQA(Ainslie et al., 2023)在 MHA 和 MQA 之间折衷:将 Q 头分组,每组共享一个 KV 头。 Q: (h, d_h) ◀ h query heads K: (h/g, d_h) ◀ h/g key heads, g is group size V: (h/g, d_h) ◀ h/g value heads Llama-2 的 GQA 配置如表10-4所示: 表10-4 Llama-2 的 GQA 配置 模型 Q Heads KV Heads 组大小 g KV Cache 压缩比

Llama-2-7B                           32                           32 (MHA)                   1                     1x
Llama-2-13B                          40                           40 (MHA)                   1                     1x
Llama-2-70B                          64                           8 (GQA)                    8                     8x
Llama-3-8B                           32                           8 (GQA)                    4                     4x
Llama-3-70B                          64                           8 (GQA)                    8                     8x
Llama-3-405B                         128                          8 (GQA)                    16                    16x

GQA 的推理影响(以 Llama-2-70B g=8 为例): •KV Cache per token = 2 × 80 × 8 × 128 × 2 = 320 KB •相比 MHA (g=1):2 × 80 × 64 × 128 × 2 = 2.5 MB/token,8x 压缩 •实际生产中,GQA 使 70B 模型能在单台 8×A100 节点上运行批量 32 的长上下文推理 4) MLA 潜在注意力 MLA 是 DeepSeek-V2/V3 提出的创新注意力架构,通过低秩压缩将 KV 映射到潜在空间,实现极致压缩。其核心思想如 下: Traditional KV Cache:(K, V) ∈ R^(seq_len × (h/g) × d_h) MLA Down Projection: c_kv = W_dkv × h_t ◀ Compress hidden state to low-dimensional latent space MLA Up Projection: K = W_uk × c_kv ◀ Recover K from latent space during inference V = W_uv × c_kv ◀ Recover V from latent space during inference Only cache c_kv, whose dimension is much smaller than K and V. MLA 的内存分析(以 DeepSeek-V2 为参考,d_model=5120,kv_lora_rank=512,q_lora_rank=1536): •传统 GQA KV Cache:2 × L × num_kv_heads × d_h × dtype_size •MLA Cache:仅存储压缩后的 c_kv,维度 = kv_lora_rank(512 vs 128 × num_heads) DeepSeek-V3 报告中,MLA 相比 MHA 将 KV Cache 压缩约 7-14x,同时保持与 MHA 相当的模型质量。 MLA 的推理架构如图10-3所示: W_uk: c_kv ▶ K Input Token Generate c_kv Latent Repr Store c_kv in KV Cache Generation Phase QK^T Attention Computati Output esentation on W_uv: c_kv ▶ V 图10-3 MLA 推理数据流 MLA 的压缩优势在 prefill 阶段已充分体现,但在 decode 阶段存在一个容易被忽略的代价:上投影矩阵的在线计算。在 每一轮 decode 中,MLA 需要先将压缩的 c_kv(维度 kv_lora_rank=512)通过 W_uk 和 W_uv 恢复到 K、V 的原始维度 才能执行标准 attention。以 DeepSeek-V2 为例:decode 阶段每个 token 的恢复计算量为 2 × d_model × kv_lora_rank 约 2 × 5120 × 512 = 5.2 MFLOPS。看似微小,但在大批量 decode(batch=256)下,这一开销随 batch 线性放大至约 1.3 GFLOPS/step,叠加 attention 本身的 memory-bound 特性后,上投影成为 decode 延迟中不可忽视 的常数项。 DeepSeek-V3 技术报告指出,他们通过将 W_uk/W_uv 吸收到后续的 O 投影矩阵中(Absorbed MLA)来消除这一开 销:在推理时直接将 c_kv 作为 attention 的隐式 K、V 状态参与计算,绕过显式的 K、V 恢复步骤,将 decode 阶段的 MLA 开销降至与传统 GQA 相当。这一优化是 MLA 从学术设计走向生产部署的关键一步,但目前在主流开源推理框架 (vLLM、SGLang)中尚未完全支持。 注意力架构的选择直接影响推理引擎的设计。 5) 注意力架构对比总结 各架构在 KV Cache、decode 计算量与模型质量上的对比如表10-5所示: 表10-5 注意力架构对比 架构 KV Cache (相对 MHA) Decode 计算量 模型质量 代表模型 MHA 1x (基准) 1x 最佳 Llama-2-7B/13B, GPT-3 MQA 1/h (h头数倍压缩) 1x 轻微退化 PaLM, Falcon GQA 1/g (g组大小倍压缩) 1x 接近 MHA Llama-2/3-70B+, Mistral MLA 1/7-1/14 (动态压缩) 1.1x (上投影开销) 接近 MHA DeepSeek-V2/V3 实际吞吐量影响(Llama-2-70B 类比,4×A100,批量 32,4K 序列)如表10-6所示: 表10-6 注意力架构吞吐量对比 架构 (模拟) KV Cache (GB) 可用批次大小 吞吐量 (tokens/s) MHA (g=1) 40 GB 16 2,800 GQA (g=8) 10 GB 32 4,200 GQA (g=8) + KV 量化 FP8 5 GB 64 5,600 MLA (DeepSeek-V2 风格) 约3 GB 96 7,200 6) FlashAttention IO 感知内核 标准 Attention 的内存瓶颈不仅来自 KV Cache,还来自 QK 中间矩阵的 HBM 读写开销。FlashAttention(Dao et al., ⊤ NeurIPS 2022)通过重新组织计算顺序,从根本上减少了高带宽内存(HBM)的读写次数。 标准 Attention 的 IO 瓶颈:标准实现将 S = QK (形状 n × n)写入 HBM,再读回做 Softmax,最后乘 V 写出结果 O。 ⊤ 对 n = 4096、FP16,中间矩阵 S 占 4096 × 2 ≈ 32 MB。此过程算术强度低,完全 memory-bound,GPU 算力大量浪费 在等待数据。 FlashAttention 的核心思想是 Tiling 与 Online Softmax 的组合:将 Q、K、V 切分为 tile,在 SRAM(共享内存)内完成 每个 tile 的 Q K V 计算,并通过在线 Softmax 累积归一化统计量,全程无需将 n × n 矩阵写入 HBM,从而将 HBM 访 ⊤ 问量从 O(n ) 降至 O(nd)。 i j j 各代 FlashAttention 演进如表10-7所示: 表10-7 FlashAttention 版本演进 版本 发表 主要改进 FlashAttention-1 NeurIPS 2022 Tiling + Online Softmax,IO 复杂度 O(nd) FlashAttention-2 2023 减少 non-matmul FLOPs,提升并行度与 warp 调度,A100 上利用率约 70% FlashAttention-3 2024 针对 H100 WGMMA/TMA 的异步流水线,支持 FP8 FlashAttention-2 相比 PyTorch 标准实现,在 A100 上 prefill 加速约 2-4x,内存占用从 O(n ) 降至 O(n),使 128K 上下 2 文推理在单 GPU 上从“不可行”变为“可行”。 对推理引擎的影响:vLLM 的 PagedAttention 基于 FlashAttention 的 block-sparse 变体 ( flash_attn_with_kvcache )支持非连续物理块的高效访问;SGLang、TensorRT-LLM 均以 FlashAttention-2/3 作 为默认注意力内核;Ring Attention 的每个本地 tile 计算也依赖 FlashAttention 的在线 Softmax。

10.2 推理引擎与调度

推理引擎承载批量推理的核心调度与显存管理。本节从 vLLM 的 PagedAttention 出发,说明连续批处理与抢占调度机 制,并从源码级视角剖析引擎的模块划分与二次开发入口。

10.2.1 PagedAttention 与 vLLM 架构

PagedAttention 是 vLLM 推理引擎的核心创新,将操作系统虚拟内存的思想应用于 KV Cache 管理,从根本上解决了传统 推理系统的内存碎片问题。

  1. 推理内存问题 在 PagedAttention 之前,推理系统为每个请求预分配连续的最大长度 KV Cache: Request 1: ████████████████████████░░░░░░░░░░░░░░ (Pre-allocated 4096 slots, actual usage 1500) Request 2: ██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░ (Pre-allocated 4096 slots, actual usage 500) Request 3: ████████████████████████████████████░░ (Pre-allocated 4096 slots, actual usage 3000) Total allocated: 12 GB, actual usage: 5 GB, wasted: 7 GB (58% internal fragmentation) 这种“预留”模式的三大缺陷:
  1. 内部碎片(Internal Fragmentation):预分配超过实际需要,浪费大量内存。
  2. 外部碎片(External Fragmentation):频繁分配和释放导致内存碎片化,无法分配大块连续内存。
  3. 无法内存共享:共享前缀的多个请求独立维护 KV Cache,无法复用。
  1. PagedAttention 设计 PagedAttention 将 KV Cache 划分为固定大小的 Block(物理页),每个 Block 存储固定数量 token 的 KV cache: KV Cache = Block Table + Physical Blocks Block Table (per sequence): [Block_0] ▶ [Block_1] ▶ [Block_2] ▶ [Block_3] ▶ NULL ▼ ▼ ▼ ▼ Physical Blocks (GPU): Phys_5 Phys_2 Phys_8 Phys_1
 # Source: PagedAttention Core Data
 class Block:
     def __init__(self, block_size: int, num_heads: int, head_size: int):
         # K Cache for block_size tokens
         self.k_cache = torch.zeros(block_size, num_heads, head_size)
         # V Cache for block_size tokens
         self.v_cache = torch.zeros(block_size, num_heads, head_size)
         self.ref_count = 0 # Reference count, supports sharing
 class BlockTable:
     def __init__(self, max_blocks: int):
         self.blocks: List[Block] = []
         self.max_blocks = max_blocks
     def append_block(self, block: Block):
         self.blocks.append(block)
     def get_blocks(self) -> List[Block]:
         return self.blocks

PagedAttention 的核心优势: •消除内部碎片:Block 按需分配,不预留未使用的空间。对于 16-token block,最差碎片仅为 15 tokens × KV Cache per token。 •消除外部碎片:所有 Block 大小相同,通过自由链表管理,无外部碎片。 •内存共享(Copy-on-Write):多个序列共享相同 Block 时,通过引用计数实现零拷贝共享。 3) vLLM 架构 vLLM 的整体架构如图10-4所示: Centralized Scheduler Request Queue Block Manager Allocate Block Allocate Block Allocate Block GPU Workers Worker 0: GPU 0 Worker 1: GPU 1 Worker N: GPU N KV Cache Blocks KV Cache Blocks KV Cache Blocks Physical Blocks GPU0 Physical Blocks GPU1 Physical Blocks GPUN 图10-4 vLLM 架构:中心调度器 + GPU Worker + 块式 KV Cache 核心组件包括: •中心调度器(Centralized Scheduler):维护全局请求队列,决定每个 step 执行哪些请求(iteration-level batch selection),通过 Block Manager 管理 KV Cache 块的分配和回收 •Block Manager(块管理器):维护空闲块链表,管理块的引用计数(支持 COW 共享),执行抢占(Preemption):当 内存不足时,将块的 KV Cache 交换到 CPU 内存或直接丢弃(需重计算) •GPU Workers:执行模型前向推理(单 worker 对应单 GPU),管理 GPU 上的 Block 数据,报告 GPU 内存使用状态 序列生命周期: Request arrives ▶ Scheduler assigns sequence_id ▶ Block Manager allocates first block (Physical Block) ▶ Worker executes prefill, fills block's KV Cache ▶ [Generation loop] ▶ Scheduler adds sequence to decode batch ▶ Worker executes decode step, generates 1 token ▶ If current block is full ▶ Block Manager allocates new block ▶ New token's KV written to current block ▶ [Generation complete or reached max_tokens] ▶ Block Manager releases all blocks (ref count decremented, recycled at 0) 4) 抢占机制 当 GPU 内存不足时,vLLM 需要抢占(Preempt)部分序列以释放内存。两种基础策略: •Swap to CPU:将序列的物理块拷贝到 CPU 内存,释放 GPU 上的块供其他序列使用,序列恢复时将块拷回 GPU。优 点:保留计算进度,无需重计算。缺点:PCIe 带宽瓶颈,swap 延迟高。 •Recomputation:丢弃序列的所有 KV Cache 块和生成结果,序列恢复时重新 prefill 输入 prompt 并重新 decode。 优点:释放内存更快,无 swap 开销。缺点:重计算浪费 GPU 算力。 vLLM 的混合策略: •短序列优先 swap to CPU •长序列优先 recomputation(swap 代价太高) •优先级较低的请求优先被抢占 5) 调度循环 vLLM 的迭代级别调度(Iteration-level Scheduling):

 # Source: vLLM Scheduler Core
 def schedule(self) -> SchedulerOutputs:
     scheduled_seqs = []
     token_budget = self.max_num_batched_tokens
     # 1. Prioritize waiting prefill requests
     for seq in self.waiting:
         if token_budget < seq.get_prompt_len():

break if not self.block_manager.can_allocate(seq): break

         scheduled_seqs.append(seq)
         token_budget -= seq.get_prompt_len()
     # 2. If token budget remains, fill running sequences (decode)
     for seq in self.running:
         if token_budget <= 0:

break

         scheduled_seqs.append(seq)
         token_budget -= 1 # Decode produces only 1 token per sequence
     # 3. Check if preemption is needed
     if self.block_manager.free_blocks < self.watermark:
         self.preempt_sequences()
     # 4. Allocate/extend physical blocks for scheduled sequences
     block_tables = self.block_manager.prepare_blocks(scheduled_seqs)
     return SchedulerOutputs(
         scheduled_seqs=scheduled_seqs,
         block_tables=block_tables,

) 6) 与传统 Batching 的对比 vLLM 与传统静态批处理的对比如表10-8所示: 表10-8 静态批处理与 PagedAttention 对比 特性 传统 Static Batching vLLM PagedAttention 内存分配 预分配全序列长度 按需分配 Block 内存碎片 大量内部/外部碎片 几乎无碎片 前缀共享 不支持 内置 COW 共享 批次填充 需等待凑批量 迭代级调度,及时处理 序列长度混合 效率低 (padding) 天然支持变长 特性 传统 Static Batching vLLM PagedAttention 抢占 难以实现 Block 级 Swap/Recompute 最大吞吐量* baseline (1x) 最高 24x *吞吐量对比:基于 vLLM 论文(Kwon et al., SOSP 2023)在 OPT-13B 上的测试,同等 GPU 配置下,vLLM 相比 HuggingFace Transformers(静态批处理)吞吐量最高提升 24x,相比 FasterTransformer 提升约 3.5x,相比 Orca 提升 约 2.7x。实际提升幅度取决于序列长度分布与批处理实现质量,朴素静态批处理下收益最显著。 vLLM 的块式内存管理和迭代级调度是连续批处理(Continuous Batching)技术的基础,将在下一节深入讨论。

10.2.2 连续批处理与抢占调度

连续批处理(Continuous Batching / In-flight Batching)是打破传统“队列-批次”模式的核心技术,使推理引擎能够在 生成过程中动态调整批次组成。本节系统讨论连续批处理的原理、策略和演进。

  1. 传统批处理的局限 传统推理系统的批处理模式如图10-5所示: Request Queue Inference Engine GPU Accumulate N requests Wait to fill batch_size=32 Prefill batch (32 requests) KV Cache + First Token Decode Step 1 (32 requests) Next tokens Decode Step 2 (32 requests) Request "A" finishes early (EOS) Request "A"'s slot wasted until batch ends ... Continue until longest request finishes Request Queue Inference Engine GPU 图10-5 传统批处理:批次粒度的等待浪费 传统模式的核心缺陷:
  1. 批次锁定:一旦批次启动,新请求必须等待当前批次完成。
  2. 早完成浪费:提前生成 EOS 的请求占据 GPU 槽位但不产生有用输出。
  3. 填充延迟:高负载下,请求等待凑批次的延迟可占 TTFT 的 50-80%。
  1. 连续批处理原理 连续批处理将调度粒度从“批次级别”细化到“迭代级别”:
 # Source: Continuous Batching Scheduling
 def continuous_batching_loop():
     waiting_queue = RequestQueue()
     running_sequences = []
     while True:
         # Re-decide batch composition each iteration
         batch = []
         # 1. Take prefillable requests from waiting queue
         while not waiting_queue.empty():
             req = waiting_queue.peek()
             if can_allocate_blocks(req):
                 batch.append(req)
                 waiting_queue.pop()
             else:

break

         # 2. Take decodable requests from running sequences
         for seq in running_sequences:
             if not seq.is_finished():
                 batch.append(seq)
         # 3. Execute single inference step
         outputs = engine.execute_step(batch)
         # 4. Update status
         for seq, output in zip(batch, outputs):
             if output.is_finished():
                 seq.mark_finished()
                 release_blocks(seq)
         # 5. Restart next iteration

连续批处理带来的改进: •请求完成即释放资源,槽位立即分配给等待中的新请求 •新请求无需等待当前批次完成,可以在下一个迭代加入 •显著提升 GPU 利用率(平均提升 30-50%) 3) vLLM 迭代级调度 vLLM 在每个迭代做出调度决策,核心约束是 token budget:

 # Source: vLLM Scheduling Logic
 class Scheduler:
     def __init__(self, max_num_batched_tokens=8192):
         self.max_num_batched_tokens = max_num_batched_tokens
         self.waiting = deque()     # Requests waiting for prefill
         self.running = set()       # Requests currently decoding
         self.block_manager = BlockManager()
     def _schedule_prefills(self, budget: int) -> List[SequenceGroup]:
         """Schedule prefill requests from waiting queue"""
         scheduled = []
         while self.waiting and budget > 0:
             seq = self.waiting[0]
             num_tokens = seq.get_seqs()[0].get_len()
             if num_tokens > budget:

break if not self.block_manager.can_allocate(seq): break

             self.waiting.popleft()
             scheduled.append(seq)
             budget -= num_tokens
         return scheduled
     def _schedule_decodes(self, budget: int) -> List[SequenceGroup]:
         """Schedule decode steps from running sequences"""
         scheduled = []
         for seq in sorted(self.running, key=lambda s: s.arrival_time):
             if budget <= 0:

break

             scheduled.append(seq)
             budget -= 1 # Each decode consumes only 1 token budget
         return scheduled
     def schedule(self) -> Tuple[List, List]:
         budget = self.max_num_batched_tokens
         prefills = self._schedule_prefills(budget)
         budget -= sum(seq.get_seqs()[0].get_len() for seq in prefills)   # deduct actual token count
         decodes = self._schedule_decodes(budget)
         return prefills, decodes

Chunked Prefill(分块预填充):对于极长的 prefill 请求(如 32K tokens),如果不分块处理,单个 prefill 会独占 GPU 相当长时间,导致 decode 请求在队列中堆积。Chunked Prefill 将长 prefill 切分为多个较小块(如 2048 tokens/chunk),分多个迭代执行:

 # Source: Chunked Prefill Strategy
 class ChunkedPrefillScheduler:
     def __init__(self, max_prefill_chunk=2048):
         self.max_prefill_chunk = max_prefill_chunk
     def schedule(self):
         # Only prefill a fixed number of tokens per iteration
         for req in self.waiting:
             num_new_tokens = min(req.remaining_tokens, self.max_prefill_chunk)
             # Only prefill current chunk

yield PrefillChunk(req, num_tokens=num_new_tokens) 4) 高级调度策略 Orca(OSDI’22): •提出迭代级调度(Iteration-level Scheduling):以单次迭代(而非整个请求)为调度粒度,请求一旦完成即释放槽位 并让新请求加入,这是连续批处理的原型 •提出 Selective Batching:由于不同请求的序列长度不同,Attention 操作无法直接跨请求合并 batch;Orca 对 Attention 之外的算子(如 FFN、LayerNorm)跨请求合并执行,Attention 则各请求独立计算,兼顾吞吐与灵活性 •在 decode 阶段可以处理更大的批次(因为 decode 是 memory-bound 而非 compute-bound) Sarathi-Serve(Agrawal et al., OSDI 2024): •Chunked-prefill + Decode-max-batching 的融合 •将长 prefill 拆分为 chunk,与多个 decode 请求并行执行 •核心洞察:GPU 计算能力在 prefill 和 decode 之间可以互补利用 Sarathi 的 Chunked-Prefill 与 Decode 混合调度如图10-6所示: Sarathi Scheduling Iteration 1 Prefill Chunk A1 + 16 Decodes Iteration 2 Prefill Chunk A2 + 16 Decodes Iteration 3 Prefill B + + 32 Decodes 图10-6 Sarathi 的 Chunked-Prefill + Decode 混合调度 各调度策略的适用场景对比如表10-9所示: 表10-9 调度策略对比 策略 Prefill 延迟 Decode 延迟抖动 吞吐量 适用场景 FCFS(先到先服务) 公平 低 中 简单场景 SPF(最短 Prefill 优先) 低(对短 prompt) 中 中高 Chat(短 prompt 多) Chunked Prefill(分块预填充) 适中 低 高 混合长/短 prompt Sarathi(混合) 适中 低 最高 高并发混合负载 5) 抢占策略 当内存或计算资源不足时,系统需要抢占某些请求。vLLM 抢占逻辑:

 # Source: vLLM Preemption Logic
 def preempt_sequences(self, num_blocks_to_free: int):
     preempted = []
     # Sort by priority (low priority ▶ preempt first); within same priority, most recently admitted first (LIFO)
     candidates = sorted(
         self.running,
         key=lambda s: (s.priority, -s.arrival_time), # Low priority, latest arrival preempted first (LIFO minimizes w

asted KV work) ) for seq in candidates: if num_blocks_to_free <= 0: break

         num_blocks = len(seq.blocks)
         # Try swap to CPU (preserve progress)
         if self.cpu_swap_enabled:
             swap_to_cpu(seq.blocks)
         # Otherwise recompute (discard KV Cache)
         else:
             free_blocks(seq.blocks)
             seq.mark_for_recompute()
         num_blocks_to_free -= num_blocks
         preempted.append(seq)
     return preempted

抢占策略选择指南如表10-10所示: 表10-10 抢占策略对比 策略 释放速度 恢复延迟 计算浪费 内存开销 Swap to CPU 慢 (PCIe) 中 (swap back) 无 需 CPU 内存 Recomputation 快 高 (重 prefill) 高 无额外 Hybrid (swap short, recompute long) 中 中 低 适中 连续批处理是实现高吞吐量推理的基础设施,配合投机解码技术,可进一步加速单请求延迟。

10.2.3 推理引擎源码级架构

对推理引擎的二次开发(改调度策略、换显存管理、加自定义算子)几乎都落在引擎层。本节以 vLLM 为对象,把前述的 PagedAttention 机制与连续批处理概念落到数据结构、调度算法与代码模块层面,从二次开发视角说明各模块的职责与 扩展点。

  1. 请求状态机与数据结构 vLLM 用明确的请求状态机驱动调度,调度器在每个 step 根据状态决定请求的去留:
 # Source: vLLM request states and data structures (simplified)
 class RequestStatus(enum.Enum):
     WAITING = "waiting"                 # waiting for prefill
     RUNNING = "running"                 # actively decoding
     SWAPPED = "swapped"                 # KV blocks on CPU, paused
     FINISHED_STOPPED = "finished_stopped"
     FINISHED_LENGTH_CAPPED = "finished_length_capped"
     FINISHED_ABORTED = "finished_aborted"
 class Sequence:
     def __init__(self, seq_id, prompt_token_ids):
         self.seq_id = seq_id
         self.prompt_token_ids = prompt_token_ids    # input tokens
         self.output_token_ids = []                  # generated tokens
         self.status = RequestStatus.WAITING
         self.arrival_time = time.time()
         self.priority = 0                           # 0 = highest
         self.num_batched_tokens = 0                 # progress for chunked prefill
 class SequenceGroup:
     def __init__(self, request_id, seqs):
         self.request_id = request_id
         self.seqs = seqs              # usually 1; >1 when n > 1 sampling
         self.sampling_params = None
         self.arrival_time = min(s.arrival_time for s in seqs)

状态流转构成调度的骨架,如图10-7所示: 请求到达 WAITING 调度 prefill 抢占 recompute 模式 RUNNING 抢占 swap 模式 swap-in EOS / max_tokens SWAPPED 图10-7 vLLM 序列状态机与抢占流转 SequenceGroup 是调度单元,Sequence 是执行单元,BlockTable 是显存单元。调度器只操作 SequenceGroup 列表 (waiting / running / swapped 三组),模型执行只消费单条 Sequence 的 token,三者解耦是引擎可扩展的根基。 2) 调度策略实现 默认调度为 FCFS(先到先服务)变体:waiting 队列按到达时间排序,prefill 优先于 decode,并优先调度前缀命中的请 求以提升 prefix cache 命中率:

Source: vLLM default scheduling (simplified)

def _schedule_prefills(self, budget): scheduled, token_budget = [], budget

     while self.waiting:
         seq_group = self.waiting[0]
         num_tokens = seq_group.get_seqs()[0].get_len()
         if num_tokens > token_budget:

break # budget exhausted if not self.block_manager.can_allocate(seq_group): break # watermark reached

         self.waiting.popleft()
         scheduled.append(seq_group)
         token_budget -= num_tokens
     return scheduled

SJF(最短 prefill 优先)是常见的自定义变体:把 waiting 按 prompt 长度升序排序,把 TTFT 尾延迟从“长 prompt 拖 累短 prompt”中解耦,代价是长请求的公平性下降。两种策略只改 waiting 的排序键,属于调度器内部可替换策略,不 改动数据流。实际部署常用“优先级 + FCFS”组合,SequenceGroup.priority 参与排序。 3) 抢占模式实现 内存触及 watermark(默认保留总块数的 1% 作为余量)时,调度器按“低优先级、后到达者优先”选择被抢占对象, 并按模式处理:

 # Source: vLLM preemption (simplified)
 def _preempt(self, seq_group, blocks_to_swap_out, mode):
     if mode == PreemptionMode.SWAP:
         for seq in seq_group.get_seqs():
             blocks_to_swap_out.extend(self.block_manager.swap_out(seq))
         seq_group.suspend()              # RUNNING -> SWAPPED
     else:                                # RECOMPUTE
         self.block_manager.free(seq_group)   # drop KV blocks
         for seq in seq_group.get_seqs():
             seq.status = RequestStatus.WAITING # re-prefill later

swap 模式把序列的物理块拷贝到 CPU(cpu_swap_space,默认 4 GiB),恢复时 swap-in 回 GPU,保留计算进度; recompute 模式直接释放块并把状态退回 WAITING,恢复时整段重 prefill。短序列优先 swap、长序列优先 recompute 是默认混合策略,对应 swap 保进度与 recompute 免 PCIe 拷贝的取舍。 4) PagedAttention 代码级 块分配与释放落在 BlockManager(旧版在 vllm/worker/block_manager.py,新版在 vllm/_core/blocks/)。逻辑块 (BlockTable 中的序号)经 BlockTable 映射到物理块(GPU 缓存数组的槽位),物理块的 ref_count 记录共享数(COW 共享、prefix caching 复用)。prefix caching 用内容哈希实现:对块内 token id 序列做滚动哈希得到 block hash,作为 哈希表键:

 # Source: vLLM prefix caching allocator (simplified)
 class PrefixCachingBlockAllocator:
     def __init__(self, num_blocks):
         self.free_blocks = deque(range(num_blocks))
         self.hash_to_block = {}      # block hash -> physical block id
         self.hash_to_ref = defaultdict(int)
     def allocate(self, token_ids):
         h = rolling_hash(token_ids)          # content hash of the block
         if h in self.hash_to_block:          # prefix cache hit
             self.hash_to_ref[h] += 1
             return self.hash_to_block[h]     # share the physical block
         phys_id = self.free_blocks.popleft()
         self.hash_to_block[h] = phys_id
         self.hash_to_ref[h] = 1
         return phys_id

调度器与块分配器的交互在调度前完成:can_allocate 判断是否有足够空闲块,can_append_slots 判断能否为正在生成 的序列追加槽位;真正的物理块写入由 worker 的 CacheEngine 在模型前向时完成。改显存管理(如换自定义淘汰策 略、加块级压缩)的主战场就在 block manager 层。 5) 连续批处理引擎层实现 连续批处理在引擎层体现为 step() 每步重组 batch:调度器产出本步的 SequenceGroup 集合,worker 对其做单次前 向,再根据输出更新状态:

 # Source: vLLM LLMEngine.step (simplified)
 def step(self):
     outputs = self.scheduler.schedule()      # 1. pick this step's batch
     if not outputs:
         return []
     self._swap_blocks(outputs.blocks_to_swap_in, outputs.blocks_to_swap_out)
     self._copy_blocks(outputs.blocks_to_copy)   # 2. COW for prefix sharing
     model_outputs = self.worker.execute_model( # 3. single forward pass
         outputs.scheduled_seq_groups)
     self.scheduler.process_model_outputs(model_outputs) # 4. update states
     return self.scheduler.get_request_outputs()

batch 大小由调度器预算决定:max_num_seqs(默认 256)限制单步序列数,max_num_batched_tokens(默认 8192)限制单步 token 总量,二者共同决定每步 batch 上界。prefill 按实际 prompt 长度扣 token 预算,decode 每条 序列只扣 1。长 prefill 通过 chunked prefill 切成多步,因此 batch 组成在每一步都可能变化,这正是连续批处理的引擎 实现。 6) 多卡与张量并行在引擎内 tensor_parallel_size > 1 时,引擎在初始化阶段用 torch.distributed 建立 NCCL 进程组,每个 worker 持有一份 TP 分 片: •权重按层内切分(attention 按头、FFN 按行或列),每个 rank 只加载自己的分片 •KV cache 按 GPU 划分:CacheEngine 为每张卡分配等分的 KV 缓存数组,每个 rank 的块表指向本卡物理块 •模型前向中,attention 与 FFN 的输出经 TP 组内的 AllReduce 合并(NCCL 走 NVLink),调度器对块表的操作在各 rank 独立进行

 # Source: vLLM TP init (simplified)
 torch.distributed.init_process_group(backend="nccl", world_size=tp_size)
 # each rank owns model_shard[rank] and cache_kv[rank] for all layers
 # per-layer output is all-reduced within the TP group before the FFN

改 TP 相关逻辑(如自定义分片、换通信后端)应关注 worker 的模型加载与 attention backend,而非调度器。 7) 二次开发视角 各开发目标对应的核心改动模块与关键类/接口如表10-11所示: 表10-11 vLLM 二次开发改动点 开发目标 核心改动模块 关键类/接口 改调度策略 vllm/scheduler.py Scheduler._schedule_prefills / _schedule_sequences 换显存管理 vllm/worker/block_manager.py(新版 vllm/_core/blocks/) BlockSpaceManager、 PrefixCachingBlockAllocator 加自定义算子 vllm/model_executor/ 与 vllm/attention/backends/ 模型层替换 op 注册与 attention 后端 调 KV 缓存量 vllm/attention/backends/ KV cache 写回时量化、读取时反量化 化 开发目标 核心改动模块 关键类/接口 自定义抢占 vllm/scheduler.py + worker PreemptionMode、cpu_swap_space 一句话定位:调度策略在 scheduler 层、显存分配在 block manager 层、算子替换在 model_executor/attention 层, 三者正交,可独立替换。engine 层只负责编排 step() 的调度、执行、更新循环,这是推理引擎二次开发的通用心智模 型。

10.3 推理高级优化

推理高级优化覆盖投机解码、长上下文推理与 MoE 推理三个专项。三者与调度机制正交,分别从解码延迟、上下文长度 与模型结构三个维度提升推理能力。

10.3.1 投机解码技术树

投机解码(Speculative Decoding)是降低自回归生成延迟的重要技术:用一个小的“草稿模型”快速生成候选 token, 然后用大模型并行验证,在保证输出质量的前提下实现 2-3x 的延迟加速。

  1. 投机解码基本原理 自回归生成的本质瓶颈是 memory-bound:每个 decode step 生成 1 个 token,GPU 大量时间浪费在读取权重而非计 算。投机解码通过“先猜测、后验证”的思路打破这一瓶颈,完整流程如图10-8所示: Draft Model (Small) Target Model (Large) Verification Logic Autoregressively generate K candidate tokens (fast) Send candidate tokens + input Single forward pass verifies all K tokens (parallel) Output logits Compare token by token, accept matching tokens Return accepted token count m (m ≤ K) Actual generation: 1 forward pass = m valid tokens Draft Model (Small) Target Model (Large) Verification Logic 图10-8 投机解码流程:草稿模型猜测 + 目标模型并行验证 接受率(Acceptance Rate): 被接受的 token 数 α= 候选 token 数 K 单步加速比(分子为每轮期望输出 token 数,含拒绝时从修正分布采样的 1 个 token,见 Leviathan et al. 2023, Proposition 2): 1−αK+1 1−α ⋅ Ttarget1 Speedup =

Tdraf tK + Ttarget1 其中 1−αK+1 是每轮期望产出的 token 数(常见的 α × K 近似仅在 α → 1 时成立,且忽略了拒绝时的修正 token 贡献), 是目标模型产生 1 个 token 的时间,T 是草稿模型产生 K 个 token 的时间。 1−α Ttarget1 draf tK

 # Source: Speculative Decoding Core
 import torch
 import torch.nn.functional as F
 def speculative_verify(target_model, draft_tokens, prefix, temperature=1.0):
     # Single forward pass of target model, verify all K candidate tokens

with torch.no_grad():

         logits = target_model(torch.cat([prefix, draft_tokens]))
         # Get prediction for next token at each position
         target_logits = logits[-(len(draft_tokens)+1):-1]
         # Compare token by token
         accepted = 0
         for i, (draft_token, t_logits) in enumerate(zip(draft_tokens, target_logits)):
             probs = F.softmax(t_logits / temperature, dim=-1)
             # Acceptance sampling: accept if draft_token probability >= random threshold
             if torch.rand(1) < probs[draft_token]:
                 accepted += 1
             else:
                 # Sample replacement token from corrected distribution
                 corrected_token = torch.multinomial(probs, 1).item()
                 return accepted, corrected_token
     return accepted, None   # All accepted
  1. 草稿模型策略 草稿模型的选择是投机解码成败的关键,各策略的对比如表10-12所示: 表10-12 草稿模型策略对比 草稿模型策略 延迟(生成 K 接受率 工程复杂度 代表工作 tokens) Small LM(独立小模型) 低 (约1ms/K) 80- 中(需训练+部署小模 Leviathan et al. 2023 90% 型) n-gram 查找 极低 (<0.1ms) 30- 低(纯查找) Lookahead (Fu et al. 2024) 50% 特征级草稿模型(Feature-level 极低 高 中(需训练草稿头) EAGLE / EAGLE-2 Draft) Jacobi 解码 中(并行迭代) 60- 低 Break the Sequential 80% Dependency Medusa(多草稿头)不依赖独立的草稿模型,而是在目标模型上额外训练多个预测头(Medusa heads),每个头预测步 数 +k 的 token。单步前向即可产生多个候选 token:
 # Source: Medusa Conceptual Code
 class MedusaModel(nn.Module):
     def __init__(self, base_model, num_heads=4):
         self.base_model = base_model            # Target model (frozen)
         # K Medusa heads, each predicting tokens at different offsets
         self.medusa_heads = nn.ModuleList([
             nn.Linear(hidden_size, vocab_size) for _ in range(num_heads)

])

     def forward(self, input_ids):
         hidden = self.base_model.get_hidden_states(input_ids)
         # Main model predicts token + 1
         logits_main = self.base_model.lm_head(hidden[-1])
         # Medusa heads predict token + 2, +3, +4, +5
         medusa_logits = [head(hidden[-1]) for head in self.medusa_heads]
         return logits_main, medusa_logits

自投机解码(Self-Speculative Decoding):独立部署草稿模型或额外的预测头会引入模型部署和维护的工程负担。自投 机解码在 2024 年成为一种重要替代方案:其核心思路是利用目标模型自身的中间层或提前退出(early exit)机制在更少 的层数下生成草稿 token,再用完整模型验证。代表性工作包括: •LayerSkip(Elhoushi et al., 2024):在模型的第 N/2 层处插入轻量草稿头,用前一半 Transformer 层生成候选 token,完整的后一半层兼任验证器。草稿和验证共享前 N/2 层的计算,KV Cache 可被验证阶段复用,内存开销为 零,在 Llama-2-70B 上实现 2.1x 加速。 •Draft & Verify(Zhang et al., 2024):通过动态选择草稿层数,在生成质量高的区域(如确定性事实)使用较浅的草 稿深度,在难度大的区域(如推理链)增加深度,自适应平衡接受率和草稿开销。 与 Medusa 和 Eagle 系列相比,自投机解码的优势在于零额外参数、零额外部署,但接受率通常略低(75-85% vs Eagle- 2 的 85-90%),适合对模型版本管理敏感的生产环境。 3) Eagle 系列 Eagle(Extrapolation Algorithm for Greater Language-model Efficiency)通过特征层面的推测实现更高效率。Eagle- 2 的关键设计:

  1. 不依赖独立的草稿模型权重,利用目标模型的中间层特征
  2. 在特征空间进行“外推”,预测未来的 hidden states
  3. Eagle-2 在 Llama-2-70B 上实现了 3.5-4.5x 加速(vs 标准自回归),优于标准投机解码的 2-3x
  1. Tree Attention 与并行验证 当草稿模型产生多个候选路径时(如 beam search 风格的草稿),Tree Attention 支持并行验证多个分支,如图10-9所 示: Current Token Candidate 1: 'the' Candidate 2: 'a' Candidate 1.1: 'cat' Candidate 1.2: 'dog' Candidate 2.1: 'big' Candidate 2.2: 'small' Candidate 1.1.1: 'sat' Candidate 1.2.1: 'ran' 图10-9 Tree Attention 并行验证多分支候选 tokens Tree Attention 通过构建候选 token 树,在一次前向传播中同时验证所有分支,找出最长的被接受路径。
  2. 主流框架集成 vLLM 投机解码:
 # Start vLLM speculative decoding mode
 python -m vllm.entrypoints.openai.api_server \
     --model meta-llama/Llama-2-70b-hf \
     --speculative-model meta-llama/Llama-2-7b-hf \
     --num-speculative-tokens 5 \
     --speculative-tensor-parallel-size 1

TGI(Text Generation Inference)投机解码: { "model_id": "meta-llama/Llama-2-70b-hf", "speculation": { "method": "medusa", "num_heads": 4 } } 6) 投机解码效果分析 各模型与草稿组合的端到端加速效果如表10-13所示: 表10-13 投机解码效果分析 模型 草稿模型 K 接受率 端到端加速 额外 GPU 内存 Llama-2-7B Llama-68M 5 85% 2.3x 约0.2 GB Llama-2-70B Llama-2-7B 5 82% 2.1x +14 GB Llama-2-70B Medusa (4 heads) 4 78% 2.5x +0.5 GB Llama-2-70B Eagle-2 4 88% 约3.0-3.5x 约0.5 GB Llama-3-70B n-gram (Lookahead) 3 45% 1.7x +0 GB DeepSeek-V3 投机解码 + MTP 1 90%+ 1.8x 极小 数据来源:各论文报告及 vLLM 社区基准。 投机解码是在保持 100% 输出质量的前提下加速生成的技术,与长上下文推理优化配合,形成完整的推理加速技术栈。

10.3.2 长上下文推理优化

随着模型上下文窗口从 4K 扩展到 128K-1M tokens,长上下文推理成为推理系统的新瓶颈。本节讨论针对长上下文场景 的系统级和算法级优化。

  1. 长上下文的挑战 长上下文推理面临的核心瓶颈如图10-10所示: Long Context (128K+) Attention O(n²) Computati KV Cache Linear Growth Prefill Latency Surge on Single Decode Step from 5 128K Sequence ≈ 30-80 G TTFT from 0.1s ▶ 10s+ ms ▶ 50ms+ B KV Cache 图10-10 长上下文推理的三重瓶颈 以 Llama-2-70B(GQA g=8)为例,不同序列长度下的资源需求如表10-14所示(Prefill 延迟以 8×A100 SXM + FlashAttention-2 为基准;单 A100 上同等配置约 10-15× 更慢且可能因 OOM 不可行): 表10-14 长上下文下的资源需求 序列长度 KV Cache (单请求) Prefill 延迟 (8×A100) Decode 延迟/step 最高批量大小 (80GB/GPU) 4K 1.25 GB 0.08s 5ms 32 32K 10 GB 1.2s 35ms 4 128K 40 GB 8s 140ms 1 256K 80 GB 25s 280ms 0.5 (需卸载)
  2. Ring Attention Ring Attention 通过分布式计算长序列的 attention,将单 GPU 的内存压力分散到多 GPU。 原理: •将 Q、K、V 按序列维度切分到 N 个 GPU •每个 GPU 计算自己负责的 Q 片段与完整 K 的 attention 是一个在线 softmax 计算 •K 和 V 通过在 GPU 间循环传递(ring communication)实现“虚拟全局 attention”
 # Source: Ring Attention Conceptual
 def ring_attention(q, k, v, num_gpus):
     # q_i, k_i, v_i: Sequence chunks on GPU i
     block_size = seq_len // num_gpus
     for step in range(num_gpus):
         # Compute attention for current chunk
         attn_i = flash_attention(q_i, k_current, v_current)
         # Accumulate online softmax statistics
         o_i = online_softmax_update(o_i, attn_i)
         # Ring transfer: Send K,V to next GPU
         send(k_current, (rank + 1) % num_gpus)
         recv(k_current, (rank - 1) % num_gpus)
         send(v_current, (rank + 1) % num_gpus)
         recv(v_current, (rank - 1) % num_gpus)
     return o_i

Ring Attention 的实际应用: •4×A100 可将 128K 上下文推理的吞吐量扩展为单 GPU 的约 4x(注意:单 GPU 因 OOM 无法支持 128K 上下文,此处 为通过减少每 GPU 序列长度做等效对比) •DeepSpeed Ulysses + Ring Attention 支持百万 token 上下文 •通信开销取决于 GPU 间的网络带宽(NVLink > IB > RoCE) 3) YaRN 与位置编码扩展 YaRN(Yet another RoPE extensioN)通过 NTK-aware 插值扩展位置编码能力:

 # Source: YaRN Position Encoding
 def yarn_rope_frequencies(original_max_len, target_max_len, dim, base=10000.0):
     # NTK-aware scaling: conservative for high frequencies, aggressive for low frequencies
     scale = original_max_len / target_max_len
     inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
     # Frequency partitioning: different scaling strategies per frequency band
     ntk_factor = target_max_len / original_max_len
     lambda_factors = compute_lambda_factors(inv_freq, ntk_factor, dim)
     scaled_inv_freq = inv_freq * lambda_factors
     return scaled_inv_freq

YaRN 和 NTK-aware scaling 是 Llama-2-7B 从 4K 扩展到 32K 上下文的关键技术。NTK-aware scaling 可零样本应用; 完整的 YaRN 方法需要在扩展上下文数据上进行少量微调(约 400 步),相比从头预训练代价极低。 4) Quest 稀疏注意力 Quest(Query-Aware Sparsity for Efficient Long-Context LLM Inference)通过选择性计算注意力来降低计算量: •对每个 Q token,通过近似方法快速识别其可能关注的 K token(top-k 选择) •仅对选中的 K token 计算完整 attention •在 128K 上下文中,仅计算 2-5% 的注意力分数,加速 4-7x Quest 等稀疏注意力方法通过减少 attention 计算量来加速 decode,但 KV Cache 的内存占用本身并未减少。 ShadowKV(Sun et al., 2024)从另一个角度解决问题:直接在 KV Cache 存储层面进行稀疏化。其核心洞察是:在长上 下文推理中,超过 90% 的 KV 对后续 token 的 attention 贡献可忽略不计,但在自回归生成中缺乏先验信息来判断哪些 KV 重要。ShadowKV 通过两步操作解决这一矛盾:

  1. 离线稀疏化:在每个 prefill 完成后,利用 Q 的 low-rank 投影快速估计每个 KV 页块的 attention 分数上界,仅保留 top-5% 的页块在 GPU HBM 中,其余 95% 卸载到 CPU 内存。
  2. 在线精确检索:在每个 decode 步骤中,用一个轻量的近似 attention 算法(基于 Q 与页面摘要向量的点积)从 CPU 侧的 KV 池中检索最相关的页面,将其异步加载回 GPU 供完整 attention 计算使用。加载与 attention 计算重叠,消除 了 CPU 到 GPU 传输的可见延迟。 在 Llama-3-8B 128K 上下文基准测试中,ShadowKV 将 KV Cache 内存占用降低约 12-16x(从约 16 GB 降至约 1 GB), 同时保持困惑度退化小于 0.06,decode 延迟仅比全量 KV Cache 方案增加约 10%。这使得单台 H100 即可在 128K 上下 文下运行批量 4 的推理。ShadowKV 代码已开源(github.com/bytedance-research/ShadowKV),集成状态请参考各框 架最新文档。
  1. 长上下文优化技术对比 各技术的优化维度与收益如表10-15所示: 表10-15 长上下文优化技术对比 技术 优化维度 加速比 内存节省 精度影响 工程复杂度 Ring Attention 分布式 + 计算 线性扩展 (N GPUs) 1/N per GPU 精确等同 中 Tree Attention 通信(替代 Ring) 约2x vs Ring 1/N per GPU 精确等同 中 YaRN / NTK-aware 位置编码扩展 无(模型能力扩展) 无 轻微退化 低 Quest(稀疏) Decode 计算量 4-7x 无 轻微退化 (<0.1 PPL) 中 MInference Prefill + Decode 3-10x 无 轻微退化 中 Infini-attention 压缩历史 KV 恒定计算 极大 中(需训练) 高 StreamingLLM 恒定 KV Cache 恒定 decode 恒定 轻微退化 低 KV Cache 量化 (FP8) 内存带宽 约2x 约2x 极微 低 技术组合建议的决策树如图10-11所示: Long Context Requirement Sequence Length? < 32K 32K-128K > 128K GQA + KV Cache FP8 Quant Ring Attention + KV Cache Ring Attention + Sparse Att ization Quantization ention + KV Cache Quantiz ation Single Node Deployment Multi-Node Deployment + Multi-Node + Sparse + Co High-Speed Interconnect mpression

图10-11 长上下文技术选择决策树 长上下文推理优化与 vLLM 参数调优直接相关,参数的合理配置是实现上述优化的前提。

10.3.3 MoE 推理专项

MoE 模型在推理侧的挑战与训练侧不同:训练关注梯度同步与通信均衡,推理关注显存驻留与在线调度。本节从推理视 角展开 MoE 的显存特征、专家调度、引擎实现与负载均衡,训练侧的容量因子、辅助损失与梯度同步机制不在本节范 围。

  1. MoE 推理的显存特征 推理没有优化器状态与梯度,但权重仍须按总参数量驻留: •全部专家常驻 vs 动态加载:主流推理引擎要求所有专家权重常驻显存。DeepSeek-V3 总参数 671B,FP8 下权重约 671 GB,单节点 8×80GB(640 GB)放不下,需 H200(141GB)或跨节点 EP;动态加载(按路由结果从 CPU/SSD 换入专家)适合显存受限场景,但单专家权重数百 MB,PCIe 换入延迟会让 decode 不可控,一般只用于离线或低 QPS •专家参数 vs 共享部分:共享专家(DeepSeekMoE 中所有 token 必经的专家)全量驻留且是每步计算热点;稀疏专家 按 EP 分片驻留在各 rank;非专家参数(attention、embedding、LayerNorm)按 TP/DP 处理,不参与 EP •KV cache 与专家显存分配:gpu_memory_utilization(默认 0.90)把显存分为权重区、KV cache 区与激活 buffer 区;EP 模式下每个 rank 还须为 token 重排预留专家 buffer(容量因子 1.25 时峰值可达平均的 1.25 倍),进一步挤压 KV 可用空间 推理侧可用 FP8 权重 + FP8 KV cache 双量化缓解:权重减半释放显存,KV 减半提升同显存下的并发容量。
  2. 专家调度 推理时路由权重固定(训练已收敛),token 到专家的映射是纯前向计算:
 # Source: MoE router forward (inference, simplified)
 def route_tokens(hidden_states, router_weight, top_k):
     logits = hidden_states @ router_weight.T   # (T, E)
     probs = F.softmax(logits, dim=-1)

top_k_weights, top_k_idx = probs.topk(top_k, dim=-1) return top_k_idx, top_k_weights # token -> expert assignment EP 在推理中的实现与训练一致:每个 MoE 层两次 All-to-All(dispatch 把 token 按路由结果送到专家所在 rank, combine 把结果按原序收回)。推理侧 batch 通常远小于训练,All-to-All 的相对开销更大,通信量正比于 token 数 × hidden dim,batch 小则计算密度低,通信占比上升。因此推理 EP 组必须限制在节点内(NVLink),跨节点 EP 在低并 发下不划算。 负载不均衡在推理侧的表现是 straggler:路由偏向使少数专家承载更多 token,该专家所在 rank 成为整步延迟的短板。 训练侧的辅助损失已把路由拉向均匀,但推理流量(如某类 prompt 集中于部分专家)仍会制造热点,推理侧无法再改路 由权重,只能靠运行时监测与调度缓解。 3) MoE 推理引擎实现 vLLM 与 SGLang 都原生支持 MoE 推理: •vLLM:–model 加载 Mixtral / DeepSeek / Qwen-MoE 等;–tensor-parallel-size 切分非专家部分,–expert-parallel- size 把专家按 EP 分到多卡;vLLM V1 引擎内置 fused MoE 算子(dispatch、combine 与专家 FFN 融合),减少 kernel 启动与中间显存往返 •SGLang:同样支持专家并行配置,overlap 调度在 MoE 场景同样生效,token 重排与专家计算重叠 •EP 推理的通信实现:引擎层维护 EP 进程组,dispatch / combine 用 All-to-All 原语;每 GPU 上专家数 = E / EP,路由 后的 token 在本地按专家重排,对非本地专家发起跨 rank 传输 引擎对 EP 与 TP 的取舍:EP 把专家分到多卡(每卡只驻留部分专家),TP 把每个专家切分到多卡(专家仍驻留所有 卡)。EP 适合专家总参数远大于单卡显存、且并发足以填满通信的场景;TP 适合并发低、希望复用现有 TP 通信链路的 场景。 4) 负载均衡与性能 推理侧的负载均衡没有训练侧那样丰富的调节手段,主要靠三件事: •专家过热 hot expert:监测每个专家每步的 token 数。推理延迟由最热专家决定,热点专家负载达到普通专家的 1.5-3 倍时,整批吞吐按热点衰减,需要把负载调度到热点集中较弱的节点或降低该路径并发 •路由 hash:在路由 logits 上叠加确定性哈希扰动,把同构负载打散,缓解固定 prompt 模式造成的专家集中,副作用 是轻微的采样分布偏移 •负载均衡正则的推理侧代价:辅助损失是训练期手段,推理时已固化在路由权重里,无法在线调整;推理侧若在线重路 由(把溢出 token 改发次优专家),需额外一次路由计算与 buffer 预留,且改变采样分布,代价需与 straggler 收益权 衡 吞吐与显存的权衡如表10-16所示: 表10-16 MoE 吞吐与显存权衡 配置 显存 每 token 计算量 说明 FP16 权重 + FP16 KV 全量(约 2× 参数量) top-k 专家 显存门槛最高 FP8 权重 + FP8 KV 减半 top-k 专家 工业主流,H100/H200 原生支持 EP 分片 不变(分摊到多卡) 不变 + 通信 以通信换单卡容量 动态加载专家 显著降低 不变 + 换入延迟 显存受限 / 低 QPS MoE 推理的总收益来自激活参数量小:同一算力下每 token 的 FLOPs 远低于同总参数的稠密模型,吞吐按激活参数计可 数倍于稠密;代价是显存按总参数计、EP 通信与热点拖累。MoE 推理规划的本质是在总参数量、EP 拓扑与并发度之间 找平衡,显存决定卡数下限,EP 决定通信上限,热点决定吞吐上限。

10.4 vLLM 参数调优实战

vLLM 是目前最广泛使用的开源大模型推理引擎。本节通过一个完整的调优实战,覆盖从基础部署、参数调优到性能基准 测试的全流程。

10.4.1 vLLM 部署基础

单 GPU 部署 Llama-3-8B:

Requires: Python 3.10+, NVIDIA GPU with CUDA 12

Install vLLM

pip install vllm

 # Start OpenAI-compatible API
 python -m vllm.entrypoints.openai.api_server \
     --model meta-llama/Llama-3-8B-Instruct \
     --dtype auto \
     --max-model-len 8192 \
     --gpu-memory-utilization 0.90 \
     --port 8000
 # Verify service

curl http://localhost:8000/v1/chat/completions
-H "Content-Type: application/json"
-d '{ "model": "meta-llama/Llama-3-8B-Instruct", "messages": [{"role": "user", "content": "Hello!"}], "max_tokens": 100 }'

10.4.2 核心参数深度解析

  1. max_num_seqs(最大并发序列数): 这是影响吞吐量和延迟的核心参数。vLLM 在一个 GPU 上同时处理的请求数量上限。
 # High throughput config: allow
 --max-num-seqs 256
 # Low latency config: limit
 --max-num-seqs 64

选择原则: •若用户期望低延迟(chatbot 场景),设较低值 (32-64) •若用户不关心延迟(批量推理),设较高的值 (128-256) •值过高会导致抢占频繁,反而降低吞吐量 2) max_model_len(最大模型上下文长度): 限制单个请求的最大 token 数(prompt + generation)。直接影响内存预分配:

 # Source: Impact of max_model_len on memory allocation
 # vLLM pre-allocates enough blocks to cover max_model_len tokens per sequence
 # Per-token KV = 2 × num_layers × num_kv_heads × head_dim × dtype_bytes
 # Per-block KV = per-token KV × block_size
 # Estimate required GPU memory
 def estimate_vllm_memory(model_size_gb, max_model_len, max_num_seqs, kv_cache_dtype_bytes=2):
     # Model weights
     weight_memory = model_size_gb * 1.2 # 1.2x for engine overhead
     # KV Cache memory = block_size × num_blocks × kv_per_token
     num_layers = get_num_layers(model_size_gb)
     num_kv_heads = get_num_kv_heads(model_size_gb)
     head_dim = 128
     block_size = 16
     per_block_kv = (2 * num_layers * num_kv_heads * head_dim * block_size * kv_cache_dtype_bytes)
     max_blocks = (max_model_len + block_size - 1) // block_size
     total_kv = max_blocks * max_num_seqs * per_block_kv / (1024**3)
     # Extra overhead (activations, CUDA context, etc.)
     overhead = 4 # GB
     total_gb = weight_memory + total_kv + overhead
     return total_gb
  1. gpu_memory_utilization(GPU 内存利用率): vLLM 使用该比例决定分配多少 GPU 内存用于 KV Cache。留出空间用于 CUDA graph、activations 等:
 # For 80GB H100, 90% utilization
 # 72 GB for model weights + KV Cache
 # 8 GB reserved for CUDA context, activations, etc.
 --gpu-memory-utilization 0.90
 # For 48GB A40/L40
 # Need more conservative ratio
 --gpu-memory-utilization 0.85
  1. block_size(块大小): Block 中存储的 token 数量:
 # Small blocks ▶ finer memory management
 --block-size 8
 # Large blocks ▶ less management overhead
 --block-size 32
 # Recommended default: 16
 --block-size 16
  1. swap_space(CPU 交换空间): vLLM 为被抢占序列的 KV Cache 在 CPU 内存中预留的空间:

10.4.3 调优决策树

 # Disable CPU swap
 --swap-space 0
 # Allow 20 GB swap space
 --swap-space 20

调优决策树如图10-12所示: Deployment Scenario Primary Goal? Max Throughput Min Latency Balance High Concurrency Config Low Latency Config Balanced Config max-num-seqs: 256 max-num-seqs: 32 max-num-seqs: 128 gpu-memory-utilization: 0. gpu-memory-utilization: 0. gpu-memory-utilization: 0. 95 85 90 max-model-len: 4096 max-model-len: 8192 max-model-len: 8192 enable-prefix-caching: tru enable-chunked-prefill: tr enable-prefix-caching: tru e ue e 图10-12 vLLM 参数调优决策树

10.4.4 性能基准测试

使用 vLLM 官方 benchmark_serving.py 进行准确的性能测试:

Requires: benchmark_serving.py from the vLLM repository

Download benchmark dataset

wget https://huggingface.co/datasets/anon8231489123/ShareGPT_V3_unfiltered_cleaned_split/resolve/main/ShareGPT_V3_unfi ltered_cleaned_split.json

Run benchmark

python benchmarks/benchmark_serving.py \

10.4.5 多 GPU 并行部署

     --backend vllm \
     --model meta-llama/Llama-3-8B-Instruct \
     --dataset-name sharegpt \
     --dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \
     --num-prompts 1000 \
     --request-rate 16 \
     --save-result \
     --result-dir ./benchmark_results
 # Example output
 # ==========
 # Successful requests:                    1000
 # Benchmark duration (s):                 62.43
 # Total input tokens:                     215234
 # Total generated tokens:                 198876
 # Request throughput (req/s):             16.02
 # Input token throughput (tok/s):         3447.53
 # Output token throughput (tok/s):        3185.41
 # ----------
 # Mean TTFT (ms):                         124.35
 # Median TTFT (ms):                       98.21
 # P99 TTFT (ms):                          452.13
 # -----Time per
 # Mean TPOT (ms):                         22.18
 # Median TPOT (ms):                       20.45
 # P99 TPOT (ms):                          48.92
 # ==========
 # Requires: multi-GPU node with NVLink (H100/A100 SXM)
 # 8 GPU Tensor Parallel Deploy Llama
 python -m vllm.entrypoints.openai.api_server \
     --model meta-llama/Llama-3-70B-Instruct \
     --tensor-parallel-size 8 \
     --max-model-len 8192 \
     --gpu-memory-utilization 0.92 \
     --max-num-seqs 64 \
     --enable-prefix-caching \
     --port 8000
 # 4 GPU Tensor Parallel + Data
 # Instance 0
 CUDA_VISIBLE_DEVICES=0,1,2,3 python -m vllm.entrypoints.openai.api_server \
     --model meta-llama/Llama-3-70B-Instruct \
     --tensor-parallel-size 4 \
     --port 8000 &
 # Instance 1
 CUDA_VISIBLE_DEVICES=4,5,6,7 python -m vllm.entrypoints.openai.api_server \
     --model meta-llama/Llama-3-70B-Instruct \
     --tensor-parallel-size 4 \
     --port 8001 &

vLLM 自 v0.4 起已原生支持流水线并行( --pipeline-parallel-size ),可与张量并行组合使用(TP×PP)。对于极致 的流水线优化(如气泡消除、micro-batch 精细流水线),TensorRT-LLM 和 DeepSpeed-Inference 提供更细粒度的控 制。

10.4.6 生产部署最佳实践

配置文件(vllm_config.yaml):

Source: vLLM Production Config

model: "meta-llama/Llama-3-70B-Instruct" host: "0.0.0.0" port: 8000 dtype: "auto" max_model_len: 8192 gpu_memory_utilization: 0.92 max_num_seqs: 128 tensor_parallel_size: 8 enable_prefix_caching: true enable_chunked_prefill: true max_num_batched_tokens: 8192 block_size: 16 swap_space: 10 trust_remote_code: true

Performance optimization

enforce_eager: false # Enable CUDA graph (recommended) disable_custom_all_reduce: false num_scheduler_steps: 1 # Scheduling steps per iteration vLLM v1 引擎(2024 Q4)的关键变化:vLLM 在 v0.6.x 后推出了全新的 v1 引擎架构(通过 VLLM_USE_V1=1 环境变量启 用),对核心推理循环进行了自底向上的重写。以下变化直接影响调优参数的选择: •零开销前缀缓存(Zero-overhead Prefix Caching):v1 将前缀哈希计算嵌入到 prefill 的 attention kernel 中(利用 FlashAttention 的 tile 边界),消除了 v0 中单独的哈希计算和树查找开销。对于 system prompt 固定的 API 服务,前 缀缓存命中时的 TTFT 可进一步降低。无需额外参数配置。 •异步输出处理:v1 引入异步 tokenizer 解码流水线,将 CPU 侧的 detokenization 与 GPU 侧的下一轮 attention 计算 重叠。在输出 token 较长的场景下,端到端延迟有 5-12% 的改善。可通过 --async-output-proc 启用。 •调度器重构:v1 将 prefill 和 decode 的调度决策分离为独立的调度器线程,消除 v0 中单线程调度的锁竞争。在高并发 (>128 并发请求)下,v1 的调度延迟方差降低约 3-5x,直接改善 P99 TTFT。 max_num_seqs 的调优值在 v1 下通常 可提高 20-30%。 •向后不兼容的变更: num_scheduler_steps 参数在 v1 中废弃, enforce_eager 改为 --compilation-config 统 一管理 CUDA graph 和 torch.compile 策略。生产环境建议先在 staging 环境下用 v1 重新基准测试,再迁移线上服 务。 Prometheus 监控导出:vLLM 自动在 /metrics 端点导出 Prometheus 格式的指标:

Key metrics

vllm:num_requests_running # Current running requests vllm:num_requests_waiting # Waiting requests vllm:num_requests_swapped # Requests preempted to CPU vllm:gpu_cache_usage_perc # GPU KV Cache usage (%) vllm:cpu_cache_usage_perc # CPU swap cache usage (%) vllm:time_to_first_token_seconds # TTFT histogram vllm:time_per_output_token_seconds # TPOT histogram vllm:request_prompt_tokens # Request prompt token count vllm:request_generation_tokens # Request generation token count vllm:request_success # Total successful requests

10.4.7 常见问题与解决方案

常见问题与对应诊断、解决方案如表10-17所示: 表10-17 vLLM 常见问题与解决方案 问题 诊断方法 解决方案 OOM(Out of nvidia-smi 显示接近 100% 使用,日志含 降低 gpu-memory-utilization → 0.85;降低 max-num- Memory) CUDA out of memory seqs ;降低 max-model-len 低 GPU 利用率 (< nvidia-smi dmon 看 SM 利用率 增大 max-num-seqs ;启用 prefix caching;检查 60%) DataLoader 是否饥饿 TTFT 过高 基准测试 P95 TTFT > 1s 启用 chunked prefill;降低 max-num-batched-tokens ; 启用 prefix caching TPOT 抖动大 P99 TPOT >> P50 TPOT 降低 max-num-seqs ;检查是否频繁 swap/preempt;增 大 swap-space 前缀缓存不生效 监控 prefix cache hit rate < 10% 检查请求是否确实共享前缀;确认 enable-prefix- caching 已开启 NVLink 利用率不 dcgmi dmon 检测 NVLink 带宽 确认 tensor-parallel-size 匹配物理拓扑(同一 足 NVSwitch 域内) 调优基础流程:

  1. Deploy baseline version (default params)
  2. Run benchmark_serving.py to get baseline performance
  3. Adjust max_num_seqs to find throughput inflection point
  4. Adjust gpu_memory_utilization to maximize KV Cache capacity
  5. Adjust max_num_batched_tokens to balance prefill and decode
  6. Enable prefix caching + chunked prefill
  7. Final benchmark verification
  8. Production deployment + continuous monitoring 掌握 vLLM 调优后,下一步是将推理引擎部署为生产级的推理服务平台,涉及架构模式、网关路由和 SLA 保障。

10.5 推理服务架构

推理服务架构决定模型部署后的延迟、吞吐与成本特性。本节对比单体、分离式与 Serverless 三种架构模式,深入分离 式 Prefill-Decode 架构,并落到零拷贝数据流与 KV Cache 跨机传输等底层共性技术。

10.5.1 推理服务架构模式对比

推理服务架构的选择直接影响系统的延迟、吞吐量、资源利用率和运维复杂度。本节系统对比三种主流推理服务架构模 式。

  1. 三种架构模式 三种主流架构模式如图10-13所示: Serverless / Event-Driven Disaggregated Architecture Monolithic Architecture

Event Queue Model Loader Ephemeral Inference Insta Result Return Request Gateway Decode Instance Request Gateway Inference Engine GPU Cluster nce Prefill Instance KV Cache Transfer Prefill + Decode 图10-13 三种推理服务架构模式对比 2) 单体服务架构 单体服务是传统的推理部署模式:一个推理实例同时处理 prefill 和 decode,内部集成模型权重管理。 特点: •架构简单,部署和运维成本最低 •无跨实例通信开销 •适合中小规模模型和中等吞吐量需求 代表实现:vLLM 单实例、TGI 单实例、SGLang 单实例、TensorRT-LLM standalone。 典型配置(vLLM 单体):

 python -m vllm.entrypoints.openai.api_server \
     --model meta-llama/Llama-2-70b-hf \
     --tensor-parallel-size 8 \
     --max-model-len 8192 \
     --port 8000

适用场景: •模型可放入单节点 GPU 内存(FP8 量化下 ≤ 405B with TP8 on H100 8×80GB;BF16 下 405B 权重约 810 GB,需多 节点) •QPS < 100 •团队规模小,运维能力有限 3) 分离式 Prefill-Decode 架构 分离式架构将 prefill(计算密集型)和 decode(内存带宽密集型)部署到不同的 GPU 实例上。 特点: •GPU 利用率更高:prefill GPU 可以用高 batch 做 compute-bound 优化,decode GPU 可以专注 memory-bound 优 化 •单请求延迟更低:prefill 和 decode 并行,减少排队 •架构复杂,需要 KV Cache 传输和调度协调 代表工作:DistServe (OSDI’24)、Splitwise (ISCA’24)、Mooncake。 适用场景: •大模型(≥ 70B)且高并发 (> 100 QPS) •对 TTFT 和 TPOT 有严格的差异化 SLA •有自建数据中心能力和运维团队 4) Serverless 事件驱动架构 Serverless 架构让模型在请求到来时才加载到 GPU 并执行推理,无请求时释放 GPU。 特点: •GPU 成本极低(按需付费) •冷启动延迟高(模型加载 + warmup) •适合间歇性、低频率的推理需求 代表实现:Modal、Banana、Replicate、云厂商的 Serverless GPU。 冷启动优化策略: •模型预热池:保持少量实例始终 warm,新请求直接路由到热实例 •快照恢复:使用 CRIU 或 GPU snapshot 加速模型恢复 •分层缓存:SSD 到 CPU 内存再到 GPU HBM 的分级加载 5) 状态化与无状态服务 状态化与无状态服务的对比如表10-18所示: 表10-18 无状态服务与状态化服务对比 特性 无状态服务 状态化服务 KV Cache 管理 由推理引擎内部管理 外部 KV Cache 存储 实例水平扩展 简单(新实例独立) 复杂(需 KV Cache 迁移) 故障恢复 重试即可 需恢复 KV Cache 状态 请求亲和性 不需要 需要(路由到持有 KV Cache 的实例) 适用场景 短对话、无前缀共享 多轮对话、前缀缓存复用 6) 模型复用 模型复用(Multiplexing)在单个 GPU 上服务多个模型,提升 GPU 利用率: GPU HBM Layout (80GB): ┌────────────────────────────────────────────────┐ │ Model A (Llama-3-8B) │~16 GB Weights (BF16) / ~8 GB (INT8)│ ├────────────────────────────────────────────────┤ │ Model B (Mistral-7B) │~7 GB Weights (INT8) │ ├────────────────────────────────────────────────┤ │ Shared KV Cache Pool │~50 GB │ │ └─ Model A Requests │ │ │ └─ Model B Requests │ │ ├────────────────────────────────────────────────┤ │ Overhead (CUDA context, ...) │~10 GB │ └────────────────────────────────────────────────┘ 7) 架构选型决策表 三种架构的选型决策如表10-19所示: 表10-19 推理架构选型决策 决策因素 单体 分离式 Serverless 模型大小 ≤ 405B on 8 GPUs (FP8) ≥ 70B 多节点 任意(冷启动限制) QPS 需求 < 100 100-10000+ < 10 持续 延迟要求 中等 (P95 < 2s) 低 (P95 < 500ms) 高 (冷启动 10-60s) 成本敏感度 中等 低(成本高) 高 运维复杂度 低 高 极低(托管) GPU 利用率 60-80% 80-95% 按需 100% / 整体低 架构模式的选择决定了后续所有组件的设计思路。

10.5.2 分离式 Prefill-Decode 架构

分离式(Disaggregated)Prefill-Decode 架构是推理服务架构的前沿方向,将计算密集的 prefill 和内存密集的 decode 部署到不同的 GPU 资源池。本节深入讨论其设计原理和工程实现。

  1. 为什么需要分离 Prefill 和 Decode 对资源的需求是根本不同的,如表10-20所示: 表10-20 Prefill 与 Decode 计算特征 阶段 计算特征 内存特征 延迟特征 最大并行度 Prefill Compute-bound (矩阵乘) 低(逐步写入 KV Cache) 主要贡献 TTFT 受 compute SM 限制 Decode Memory-bound (内存带宽) 高(全量 KV Cache 读取) 主要贡献 TPOT 受 HBM 带宽限制 在单体架构中,prefill 和 decode 竞争 GPU 资源: •Prefill 大量使用 Tensor Core,decode 无法同时加载 KV Cache •Decode 占用 HBM 带宽,Prefill 等待内存访问 分离后的优势: •Prefill 节点可以用高 SM 频率 + 大 batch 处理,MFU 可达 60-70% •Decode 节点专注内存带宽优化,批量可达 2-3x •Prefill 和 Decode 独立扩缩容(prefill 节点少但 GPU 强,decode 节点多但 GPU 可较小)
  2. DistServe 分离式调度 DistServe 是最早系统论证分离式推理优势的工作之一,其核心设计如图10-14所示: Request Gateway Prefill Scheduler Prefill GPU Pool Decode Scheduler KV Cache Transfer KV Cache Switch/Shared M emory Decode GPU Pool Token Output Aggregation 图10-14 DistServe 分离式架构 关键组件包括: •独立的 Prefill 和 Decode 调度器:Prefill Scheduler 选择合适的 prefill GPU 并优化 prefill batch 大小,Decode Scheduler 根据 KV Cache 位置路由 decode 请求 •KV Cache 传输通道:DistServe 使用 GPU-to-GPU RDMA(通过 NVSwitch 或 IB),KV Cache 传输采用流水线+压缩策 略减小时延 •Bimodal Scheduling:Prefill batch 目标是最大化 MFU(约 60-70%),Decode batch 目标是最大化 HBM 带宽利用 率(约 80-90%) DistServe 的性能报告(Llama-2-70B 在 A100 上,基于 vLLM v0.6.x 基线): •相比 vLLM 单体:延迟降低 1.3-2.1x,吞吐量提升 1.2-1.8x •TTFT P95 可以在高负载下保持稳定 •注:vLLM v0.6+ 通过 prefix caching、chunked prefill 和 V1 engine 显著提升了单体性能,实际 PD 分离收益需以最新 版本重新基准测试
  3. Mooncake 分离式实践 Mooncake 是面向超大规模推理的 KVCache 中心化分离式架构,将 KV Cache 从 Prefill 节点迁移到 Decode 节点以支撑 大并发负载。关键创新包括: •全局 KV Cache 分布式存储:Prefill 节点将 KV Cache 写入分布式 KVCache Store(基于 CPU DRAM/SSD + RDMA,通 过自研 Transfer Engine 传输),Decode 节点从全局 Store 读取 KV Cache,无需点对点传输 •Prefill-Decode 流水线化: Prefill Chunk 1 (2048 tokens) ▶ [KV Cache to Store] ▶ Decode Chunk 1 Prefill Chunk 2 (2048 tokens) ▶ [KV Cache to Store] ▶ Decode Chunk 2 •弹性扩缩容:Prefill 节点和 Decode 节点独立弹性伸缩,高峰期增加 Decode 节点处理更多并发用户,Prefill 节点数量 取决于新对话的到达速率
  4. Splitwise 阶段资源分配 Splitwise(Microsoft Azure / UW)提出将 GPU 集群按 prefill 和 decode 阶段做 phase-aware 资源分配。核心洞察: Prefill 和 Decode 使用 GPU 的模式截然不同,混合在同一 GPU 上导致资源利用率低。 Splitwise 的资源分区策略: •将 GPU 集群按 prefill:decode 比例分区(如 1:3) •Prefill GPU 配置高 TDP、高 SM 频率 •Decode GPU 可配置较低 TDP,利用内存带宽即可
 # Source: Splitwise Resource Allocation
 class SplitwiseResourceManager:
     def allocate_gpus(self, total_gpus, traffic_pattern):
         # Dynamically adjust prefill:decode ratio based on current traffic pattern
         prefill_ratio = self.compute_prefill_ratio(traffic_pattern)
         n_prefill = int(total_gpus * prefill_ratio)
         n_decode = total_gpus - n_prefill
         return {"prefill_gpus": n_prefill, "decode_gpus": n_decode}
     def compute_prefill_ratio(self, pattern):
         # Long prompt traffic needs more prefill GPUs
         avg_prompt_len = pattern["avg_prompt_tokens"]
         if avg_prompt_len > 8192:
             return 0.35 # More prefill GPUs
         elif avg_prompt_len < 1024:
             return 0.15 # More decode GPUs
         return 0.25
  1. KV Cache 传输优化 分离式架构的核心瓶颈是 Prefill 到 Decode 的 KV Cache 传输带宽。不同互联方式的带宽与耗时对比如表10-21所示: 表10-21 互联方式传输带宽对比 互联方式 单向传输带宽/GPU(P→D) 传输 4K token KV Cache(约1.3 间 GB)时 适用场景 H100 NVSwitch(节点内) 约450 GB/s 约3 ms 单节点内 PD 分离 NDR 400G InfiniBand 约50 GB/s 约26 ms 跨节点小规模集群 RoCE 100GbE 约12.5 GB/s 约104 ms 成本敏感中等规模 标准 25GbE 约3 GB/s 约433 ms 大规模延迟容忍场 景 关键洞察:NVSwitch 让节点内 PD 分离几乎无开销(约 3ms vs prefill 计算约 200ms)。跨节点 PD 分离需要 ≥100GbE 网络,否则 KV 传输延迟(>400ms)将抵消分离收益。RDMA 可将传输延迟中的软件开销从约 10ms(TCP)降至约 0.1ms。 传输量估算: •Llama-2-70B (GQA g=8), 4096 prompt tokens •KV Cache 大小:2 × 80 layers × 8 heads × 128 dim × 4096 tokens × 2 bytes = 约1.34 GB •若 TTFT SLA = 500ms,则 KV 传输需在 < 200ms 内完成 •所需带宽:1.34 GB / 0.2s ≈ 54 Gbps 传输优化策略: •分层传输:先传输浅层的 KV Cache,decode 可以边接收边开始计算 •增量传输:对于 Chunked Prefill,每完成一个 chunk 立即传输 •量化传输:传输 FP8 量化的 KV Cache,带宽需求减半 •相似前缀共享:已传输的前缀不重复传输
  2. NVIDIA Dynamo 框架 2025 年 NVIDIA 在 GTC 上开源了 Dynamo,作为数据中心规模的分布式推理服务框架。Dynamo 是分布式推理调度层, 编排 vLLM 等后端引擎并实现 PD 分离路由与 KV 传输,与 Triton Inference Server 形成互补:Triton 负责通用模型服 务,Dynamo 专攻大模型解耦推理。核心组件包括: •解耦的 Prefill/Decode Worker:支持将两阶段部署到独立 GPU 池,并根据流量动态调整配比 •KV-aware 智能路由器(Smart Router):根据各实例已缓存的 KV block 命中情况,把请求路由到前缀重用率最高的 Decode 实例,减少重复 prefill •分布式 KV Cache 管理器 KVBM:实现 GPU HBM 到 CPU DRAM 再到 SSD/对象存储的多级 KV 卸载与复用 •基于 NIXL 的低延迟点对点 KV 传输层:NIXL(NVIDIA Inference Xfer Library)屏蔽 NVLink、InfiniBand、RoCE 等异 构互联 与 Dynamo 并列,社区还有 llm-d(Kubernetes 原生分布式推理,2025 年由 Red Hat、Google、IBM 等联合发起,复 用 vLLM 引擎与 Gateway API Inference Extension)和 SGLang 的 PD 分离实现。三者共同构成 2025 年分离式推理的主 流工程选型:用 DistServe/Splitwise 理解原理,用 Dynamo/llm-d 落地生产。
  3. 分离式架构的挑战 分离式架构面临的挑战与缓解方案如表10-22所示: 表10-22 分离式架构挑战 挑战 描述 缓解方案 KV Cache 传输延迟 Prefill→Decode 的 KV 传输增加 TTFT RDMA + 分层传输 + 量化 调度复杂度 Prefill 和 Decode 的资源需要协同调度 全局调度器 + 预测性资源分配 故障恢复 Prefill 节点故障导致 Decode 节点等待 KV Cache 副本 + Prefill 冗余 负载均衡 Prefill 和 Decode 负载不匹配 动态 prefill:decode 比例调整 成本 需要更多 GPU 和高速网络 仅在大规模 (>1000 QPS) 时经济 分离式架构解决了单体的资源竞争问题,但引入了 KV Cache 传输和调度的复杂性。

10.5.3 推理数据流与零拷贝

端到端推理路径包含输入获取、预处理、模型计算、后处理、输出返回多个环节。数据在环节间每发生一次拷贝(主机内 存 ↔ 显存 ↔ 用户态/内核态缓冲)都会引入延迟与带宽损耗,零拷贝数据流是推理性能优化的底层共性技术,数据中心 与端侧均适用。

  1. 数据流中的拷贝点 一次典型推理请求的完整数据流为:网络输入 → 内核缓冲区 → 用户态缓冲 → 预处理(CPU)→ 显存 → 模型计算 → 显 存 → 后处理 → 网络输出。每个环节的拷贝点: •网络收发:内核态与用户态缓冲之间的拷贝(可被零拷贝网络栈消除) •预处理:图像/音频数据从 CPU 内存拷贝到显存(可被 GPU 侧预处理或 CUDA IPC 消除) •模型输入输出:张量在显存内的布局变换(NHWC/NCHW)与跨设备拷贝 •视频帧直通:视频解码输出到显存再到推理引擎,避免经 CPU 中转
  2. 零拷贝的实现手段 •共享内存:生产端与消费端通过共享内存传递数据指针而非复制数据。进程内用 CUDA IPC ( cudaIpcOpenMemHandle )跨进程共享显存;同进程内直接用张量视图,零拷贝。 •内存映射 mmap:将文件(模型权重、输入数据)映射到进程地址空间,访问即读写,避免 read/write 系统调用复 制。模型权重常 mmap 加载,首次访问按需换页,降低加载延迟。 •设备直通 Direct I/O:数据绕过 CPU 内存直达显存。数据中心用 GPUDirect Storage/GPUDirect RDMA(GPU 与存 储/NIC 直连),端侧用 DMA 引擎将视频帧直接送入 NPU 内存。 •异步流水线:输入获取、预处理、推理、后处理四级异步流水线,各阶段用双缓冲(double buffering)重叠,当前帧 推理时下一帧已在预处理,消除串行等待。
  3. 端侧视频 Pipeline 的零拷贝示例 端侧(IPC/边缘网关)推理的典型负载是视频帧处理。完整零拷贝流水线: VPU/MPP 解码 → 输出帧在物理连续内存 → DMA 直送 NPU → 推理 → 结果回传 关键点:解码器(VPU/MPP/VAAPI)输出的帧需是物理连续、对齐的内存块,才能被 DMA/NPU 直接消费;若解码输出 与 NPU 输入格式不一致(如 NV12 vs RGB),需硬件格式转换(RGA/2D 引擎)而非 CPU 转换。零拷贝的设计目标是把 “解码 → 转换 → 推理 → 输出”全程保持在同一内存域(显存或 NPU 内存)内,CPU 只在控制面参与。
  4. 零拷贝的收益与代价 零拷贝能显著降低单帧处理延迟(视频场景常见 10-30% 提升)并减少 CPU 占用。代价是工程复杂度:共享内存的生命 周期管理、内存对齐约束、跨进程/设备同步。评测收益需在真实负载下测端到端延迟与 CPU 利用率,而非只测单算子。 对于高帧率视频或高 QPS 在线服务,零拷贝是达到吞吐目标的前提条件。

10.5.4 KV Cache 跨机传输

分离式架构中,Prefill 与 Decode 在不同节点,Prefill 产出的 KV Cache 必须跨越节点交给 Decode 使用,KV 传输因此成 为 PD 分离的工程关键。本节承接分离式架构的分析,落到 KV 传输的实现方案、优化手段与工程权衡。

  1. 为什么需要 KV 传输 PD 分离下 prefill 计算出的 KV Cache 驻留在 prefill 节点的 GPU 显存,decode 节点若要续算,必须拿到这批 KV。两条 路线: •远程 KV:把 KV Cache 传输到 decode 节点直接读取,代价是传输带宽与时延 •本地重算:decode 节点重新对 prompt 做 prefill,代价是重复的计算量 当传输时间小于重算时间时,远程 KV 更优。以 Llama-2-70B 4096 token 为例,KV Cache 约 1.3 GB,在 NDR 400G 上 约 26 ms,而 4096 token 的 prefill 计算通常需数百毫秒,因此远程 KV 在带宽充足的互联下净收益明确。传输设计的目 标是把“Prefill 完到 Decode 开始”之间的等待压到不可感知。
  2. 传输技术方案 三类主流实现各有适用场景。 Mooncake Store 分布式 KV 存储:Mooncake 把 KV Cache 放进独立的分布式存储池:Prefill 节点把 KV 写入基于 CPU DRAM/SSD 的池子(RDMA 传输),Decode 节点从池子读取。关键点: •TransferEngine 屏蔽异构互联(IB/RoCE/NVLink),提供异步读写接口 •KV 落池后与 prefill 节点解耦,Decode 可从任意节点读,天然支持弹性扩缩容 •分片与副本设计兼顾一致性与可用性,CPU 池容量远大于单 GPU 显存,前缀复用率可以做到很高 •适用:高并发、负载波动大、需要跨前缀复用的大规模集群 vLLM KV Connector / KV transfer:vLLM 社区以 kv-connector 规范统一 KV 传输接口,引擎通过连接器抽象读写远端 KV,与具体实现解耦,各连接器对比如表10-23所示: 表10-23 vLLM KV Connector 对比 连接器 传输介质 适用场景 LocalConnector 共享内存 / 同进程 单节点内 PD 分离 SharedStorageConnector 文件系统(mmap) 低成本验证、中小规模 RESTConnector HTTP / gRPC 与现有控制面集成的场景 RemoteConnector RDMA / GPU Direct 跨节点低延迟传输(Dynamo、Mooncake 等实现对接) 通过 kv-connector 配置(–kv-transfer-config)指定连接器与 KV 池,引擎运行时把 prefill 产出的 KV 块写入池, decode 侧按需拉取。适用:在 vLLM 生态内做 PD 分离,需要与调度器、前缀缓存打通。 PyNCCL 集合通信传 KV:PyNCCL 用 NCCL 集合通信直接在 GPU 之间搬运 KV 张量,没有存储层,延迟最低:
 # Source: KV transfer via NCCL p2p (simplified)
 def transfer_kv_to_decode(comm, kv_tensor, src_rank, dst_rank, stream):
     # single-shot GPU-to-GPU copy over NVLink / IB
     if rank == src_rank:
         nccl_send(kv_tensor, dst_rank, comm, stream)
     elif rank == dst_rank:
         nccl_recv(kv_tensor, src_rank, comm, stream)

传输直接发生在显存之间,无 CPU 中转,配合 GPU Direct RDMA 时软件开销极小;无存储层意味着接收端须就绪(同步 语义),弹性与容错弱。适用:节点内或紧耦合小集群的低延迟 KV 搬运,或作为 Store 底层的传输通道。 三者的本质区别是要不要中间存储:Mooncake 引入存储池换取弹性与复用率,PyNCCL 直接搬运换取最低延迟,KV Connector 是接口层,向上兼容二者。 3) 传输优化 KV 传输的优化围绕四个方向: •KV 量化后传输:传输 FP8 量化的 KV Cache(与 –kv-cache-dtype fp8 对齐),字节数减半;量化在 prefill 节点完成, decode 直接使用量化 KV 计算,避免传输后再反量化。FP8 KV Cache 对端到端质量影响通常在可忽略量级(随模型而 异) •并行传输:KV 按层或块切片并发传输,充分利用多链路带宽,避免单流独占 •流水线化:chunked prefill 每完成一个 chunk 立即传输,decode 侧从 chunk 1 开始续算,无需等待全部 prefill 完成 •传输与计算重叠:双缓冲设计,prefill 计算 chunk N+1 时传输 chunk N,把传输时间隐藏到计算时间中

 # Source: Mooncake-style pipelined KV write (simplified)
 def pipeline_prefill_and_store(store_client, chunks):
     for i, chunk in enumerate(chunks):
         kv = prefill_forward(chunk)            # compute on prefill GPU
         store_client.async_write(kv)           # RDMA write to KV pool
         # decode side starts consuming chunk i while chunk i+1 computes
  1. 工程权衡 KV 传输不是无条件收益,工程上需权衡三个维度: •传输带宽 vs 复用收益:KV 传输是一次性成本,复用是持续收益。同一前缀被 K 个请求复用时,传输成本被摊薄 K 倍;反之低频复用场景下传输成本占主导。决策以预期前缀复用率为判据,复用率低的流量不值得跨节点搬运。 •远程 KV vs 本地重算:以传输时间与重算时间的比值做阈值。带宽充足(节点内 NVSwitch、NDR 400G)时远程 KV 明 显占优;低带宽(25GbE)下 1.3 GB 传输需数百毫秒,接近重算时间,此时本地重算或同节点混合调度更合理。 •缓存池设计:KV 池的一致性(KV 块版本、失效时机)与淘汰策略(LRU、前缀感知淘汰)决定复用命中率;池容量横 跨 CPU DRAM 与 SSD 时,两者读写带宽差一个数量级,淘汰决策必须感知存储层级。 KV 传输的权衡决策如表10-24所示: 表10-24 KV 传输权衡维度 权衡维度 偏向远程 KV 偏向本地重算或混合 互联带宽 NVLink / NDR 400G 100GbE 以下 前缀复用率 高(多轮对话、共享 system prompt) 低(一次性长 prompt) 传输时延预算 TTFT 宽松,可流水化 TTFT 严格,传输不可隐藏 集群弹性 需要 Decode 独立扩缩容 节点固定,无跨节点依赖 KV 传输是 PD 分离能否落地的关键路径:方案选型决定延迟下限,量化与流水线决定吞吐上限,缓存池设计决定复用收 益,三者共同决定分离式架构的真实性价比。

10.6 推理网关与模型管理

推理网关负责请求接入与智能路由,模型管理覆盖版本、灰度与多模型共享。本节先讨论网关的路由算法与排队机制,再 展开模型版本管理、灰度发布与多模型 LoRA 共享 GPU 的工程实践。

10.6.1 推理网关与智能路由

推理网关是推理服务平台的“大脑”,负责请求的接入、鉴权、限流和智能路由。本节讨论推理网关的架构设计和路由算 法。

  1. 推理网关的核心功能 推理网关的总体架构如图10-15所示: Inference Backend 1 Authentication Rate Limit/Quota Request Routing Inference Backend 2 Response Aggregation Logging/Monitoring

API Gateway Inference Backend N Request/Response Cache Client Request 图10-15 推理网关架构 核心功能包括: •认证鉴权:API Key/JWT 验证,RBAC 权限控制 •速率限制:Token-based 或 Request-based 的速率控制 •请求路由:将请求分发到最合适的推理后端 •请求/响应转换:OpenAI API → 内部协议,响应流式输出 •可观测性:记录请求日志、指标、链路追踪 2) 路由算法 轮询(Round-Robin):最简单的策略,请求均匀分发到所有后端。

 # Conceptual code: actual implementation
 def round_robin_route(backends):
     idx = next(counter) % len(backends)
     return backends[idx]

最少连接(Least-Connections):将请求发送到当前处理请求最少的后端。 def least_connections_route(backends): return min(backends, key=lambda b: b.get_current_requests()) Power of Two Choices(P2C):随机选两个后端,将请求发送到其中负载更低的那一个。理论证明在大规模系统中接近 最优负载均衡。 import random def power_of_two_route(backends): a, b = random.sample(backends, 2) return a if a.load() < b.load() else b KV-Cache-Aware 路由:将请求路由到已缓存其前缀 KV Cache 的后端,最大化前缀缓存命中率。

 # Source: KV-Cache-Aware router (simplified)
 class KVCacheAwareRouter:
     def __init__(self):
         # Backend ▶ hash set of cached prefixes
         self.backend_prefixes: Dict[str, Set[str]] = defaultdict(set)
     def route(self, prompt: str) -> Backend:
         prefix_hash = compute_prefix_hash(prompt)
         # Find backends that have cached this prefix
         candidates = [

b for b, prefixes in self.backend_prefixes.items() if prefix_hash in prefixes ]

         if candidates:
             return min(candidates, key=lambda b: b.load())   # Load balancing preference
         # Cache miss, use P2C routing
         return power_of_two_route(self.all_backends)

Model-Aware 路由(LPM):Large Pool Manager 模式维护全局模型和 GPU 状态,实现模型级路由:

 # Source: LPM Routing Logic
 class LargePoolManager:
     def route(self, model_name: str, prompt: str) -> Backend:
         # 1. Find healthy backends serving this model
         backends = self.get_backends_for_model(model_name)
         # 2. Filter backends with sufficient KV Cache space
         prompt_len = len(self.tokenizer.encode(prompt))
         kv_required = self.estimate_kv_cache(prompt_len)
         backends = [b for b in backends if b.kv_free > kv_required]
         # 3. KV-Cache-Aware Routing
         backend = self.kv_aware_route(prompt, backends)
         return backend
  1. 异构 GPU 负载均衡 在实际部署中,推理集群可能包含不同的 GPU 型号(A100、H100、A40),路由需要考虑各后端的异构能力:
 # Source: Heterogeneous GPU Load
 class HeterogeneousLoadBalancer:
     def __init__(self, backends):
         self.backends = backends
         # Calculate capacity weight for each backend (based on GPU compute and memory)
         self.capacity_weights = {

b: self.compute_capacity_weight(b)

             for b in backends
         }
        def compute_capacity_weight(self, backend):
            gpu_type = backend.gpu_type
            num_gpus = backend.num_gpus
            # H100 weight 6.6 (BF16 dense ~989 TFLOPS), A100 weight 2.1 (BF16 dense ~312 TFLOPS), A40 weight 1.0 (~150 TFL

OPS)

           # L40S weight 2.4 (BF16 ~363 TFLOPS); adjust weights based on real decode throughput measurements
           type_weights = {"H100": 6.6, "A100": 2.1, "A40": 1.0, "L40S": 2.4}
           return type_weights.get(gpu_type, 1.0) * num_gpus
        def route(self, request):
            # Weighted least connections
            min_weighted_load = float('inf')
            best_backend = None
            for b in self.backends:
                weighted_load = b.current_requests / self.capacity_weights[b]
                if weighted_load < min_weighted_load:
                    min_weighted_load = weighted_load
                    best_backend = b
            return best_backend
  1. 请求排队与反压机制 当所有后端都满负载时,需要在网关层面排队和施加反压:
 # Source: Request Queue and Backpressure (simplified)
 from collections import deque
 import asyncio
 class RequestQueue:
     def __init__(self, max_queue_size=10000):
         self.queue = deque()
         self.max_queue_size = max_queue_size
        async def enqueue(self, request):
            if len(self.queue) >= self.max_queue_size:
                # Backpressure: return 429 Too Many Requests

raise HTTPException(status_code=429, detail="Rate limit exceeded")

           # Add to queue and register wait
           fut = asyncio.Future()
           self.queue.append((request, fut))
           # Optional timeout control

try: return await asyncio.wait_for(fut, timeout=30.0) except asyncio.TimeoutError: # Queue wait timeout raise HTTPException(status_code=504, detail="Request timeout in queue")

        def dispatch_next(self, backend):
            """When a backend has free capacity, dequeue the next request"""
            if self.queue:

request, fut = self.queue.popleft() fut.set_result(backend) 优先级队列:支持不同用户/SLA 等级的请求队列:

 class PriorityRequestQueue:
     def __init__(self):
         # Three priority queues
         self.high = deque()     # VIP users, real-time needs
         self.medium = deque() # Standard users
         self.low = deque()      # Batch tasks, background processing
     def push(self, request, priority):
         if priority == "high":
             self.high.append(request)
         elif priority == "medium":
             self.medium.append(request)
         else:
             self.low.append(request)
     def pop(self):
         # Strict priority scheduling (may starve low priority)
         for queue in [self.high, self.medium, self.low]:
             if queue:
                 return queue.popleft()
         return None
     def pop_weighted(self):
         # Weighted fair queue: high:medium:low = 6:3:1
         r = random.random()
         if r < 0.6 and self.high:
             return self.high.popleft()
         elif r < 0.9 and self.medium:
             return self.medium.popleft()
         elif self.low:
             return self.low.popleft()
         # fallback
         for queue in [self.high, self.medium, self.low]:
             if queue:
                 return queue.popleft()
  1. 网关实现方案对比 各网关实现方案的优缺点与适用场景如表10-25所示: 表10-25 网关实现方案对比 方案 优点 缺点 适用场景 LiteLLM Proxy 开箱即用,支持 100+ LLM 性能受限,自定义路由能力弱 中小规模 (< 100 QPS) Envoy + 自定义 Filter 高性能 (C++),丰富生态 复杂,需要 C++ 开发 大规模 (> 1000 QPS) Nginx/OpenResty + Lua 成熟稳定,运维友好 动态路由逻辑受限 中等规模,传统团队 自研网关(Python/Go) 完全可控,路由逻辑灵活 开发维护成本高 大规模,有自研能力 Kubernetes Gateway API K8s 原生,声明式配置 功能有限,不支持 KV-Cache-Aware K8s 部署的简单场景 推理感知网关:原生 Kubernetes Gateway API 功能有限、不支持 KV-Cache-Aware 路由,这一短板已由 2025 年推出的 Gateway API Inference Extension(也称 Inference Gateway,GIE)填补。GIE 在标准 Gateway API 之上引入自定义资 源:InferencePool(描述一组承载同一模型的后端 Pod)与 InferenceModel/InferenceObjective(描述模型名、优先 级与流量切分)。 其核心是一个可插拔的 Endpoint Picker(EPP,端点选择器)扩展点:网关在转发前先调用 EPP,由 EPP 基于后端实时 上报的指标(KV Cache 利用率、等待队列深度、已加载的 LoRA 适配器、前缀缓存命中情况)做出“推理感知”的负载 均衡决策,而非传统轮询或最少连接。这使得本节讨论的 KV-Cache-Aware 路由、LoRA 亲和路由与过载感知调度能以声 明式、厂商中立的方式落地,已被 vLLM production-stack、llm-d 及多家云厂商推理网关采用,成为 Kubernetes 上推 理路由的事实标准。因此在 K8s 场景下自研网关前应优先评估 GIE。 智能路由将请求精确分发到最优后端,与模型版本管理和灰度发布配合,形成完整的推理服务平台化能力。

10.6.2 模型版本与灰度发布

随着模型快速迭代和业务需求变更,推理平台需要支持多模型版本的共存、A/B 测试和安全的灰度发布。本节讨论模型版 本管理的工程实践。

  1. 模型版本管理挑战 与传统的软件版本管理不同,模型版本管理面临独特挑战: •版本粒度:模型权重(几十到几百 GB)无法像代码一样快速切换 •GPU 内存约束:多个模型版本共享 GPU 时,内存竞争严重 •质量评估延迟:模型质量需要通过业务指标在线评估,无法在部署前完全测试 •回滚成本:模型版本回滚需要重新加载全量权重(分钟级延迟)
  2. 模型注册中心 模型注册中心是模型版本管理的核心基础设施:
# Source: Model Registry Data
from dataclasses import dataclass
from datetime import datetime
from typing import Dict, Optional

@dataclass class ModelVersion: model_name: str # "llama-3-70b-instruct" version: str # "v1.2.3" or "2024-06-01-finetune" storage_path: str # "s3://models/llama3-70b/v1.2.3/" framework: str # "vllm" or "tgi" or "tensorrt-llm" precision: str # "bf16" or "fp8" or "int4" checksum: str # SHA256 of all weight files metadata: Dict # {"base_model": "llama-3-70b", "train_dataset": "..."} created_at: datetime status: str # "staging", "production", "archived" @dataclass class ModelAlias: # Model alias, supports "champion/challenger" pattern name: str # "llama-3-70b-instruct:production" points_to: str # version "v1.2.3" updated_at: datetime @dataclass class RolloutConfig: # Canary release config model_name: str versions: Dict[str, float] # {"v1.2.3": 0.9, "v1.3.0": 0.1} traffic_split: str # "weighted" or "user_id_hash" or "session_id_hash" 注册中心实现:

MLflow Model Registry

mlflow server --backend-store-uri postgresql://user:pass@host:5432/mlflow \

               --default-artifact-root s3://models-bucket/ \
               --host 0.0.0.0 --port 5000
 # Register model

mlflow models register-model --name "llama-3-70b-instruct"

Create version

mlflow models create-model-version \

     --name "llama-3-70b-instruct" \
     --source "s3://models-bucket/llama-3-70b-v2/" \
     --run-id abc123
 # Transition version to Production

mlflow models transition-model-version-stage \

     --name "llama-3-70b-instruct" \
     --version 3 \
     --stage "Production"
  1. 灰度发布策略 Canary 发布(金丝雀发布):逐步将流量从小比例扩大到大比例: Phase 1: v2: 1% (Duration 1h, monitor quality metrics) Phase 2: v2: 10% (Duration 4h) Phase 3: v2: 50% (Duration 12h) Phase 4: v2: 100% (Full switch)
 # Source: Canary Release Controller
 class CanaryController:
     def __init__(self, initial_weight=0.01, step_size=0.1):
         self.current_weight = initial_weight
         self.step_size = step_size
         self.automated = False # Automatic or manual progression
     def advance(self, metrics: Dict) -> bool:
         """Check if can advance to next canary stage"""
         checks = [

metrics["error_rate"] < 1.2 * metrics["baseline_error_rate"], metrics["p95_latency"] < 1.1 * metrics["baseline_p95_latency"], metrics["user_satisfaction"] > 0.95 * metrics["baseline_satisfaction"], ]

         if all(checks):
             self.current_weight = min(1.0, self.current_weight + self.step_size)
             return True
         return False
     def rollback(self):
         self.current_weight = 0.0
         # Trigger rollback to baseline version

蓝绿部署(Blue-Green Deployment):维护两套完整的推理服务(Blue=旧版本, Green=新版本),通过网关一键切换。 适合高可用性的严格上线场景。

Blue-green deployment flow

1. Deploy Green cluster

kubectl apply -f deployment-green.yaml # New Deployment independent of Blue

 # 2. Warm up Green cluster
 # ...
 # 3. Switch gateway route

kubectl patch configmap gateway-config
--patch '{"data":{"active_cluster":"green"}}'

4. Monitor Green health

kubectl scale deployment blue-deployment --replicas=0 影子流量(Traffic Mirroring):将真实流量同时发送到新版本模型,但新版本的输出仅用于监控和对比,不返回给用户:

 # Source: Traffic Mirroring
 async def handle_request_with_mirror(request):
     # Main path: return to user
     primary_response = await call_backend("production", request)
     # Shadow path: record only, do not block main path
     asyncio.create_task(mirror_and_compare(request, primary_response))
     return primary_response
 async def mirror_and_compare(request, primary_response):

try:

         shadow_response = await call_backend("shadow", request)
         # Compare outputs of two versions
         diff = compute_diff(primary_response, shadow_response)
         if diff.significant():
             log_metric("model_drift", diff)

except Exception as e: log_metric("shadow_error", 1) 4) A/B 测试框架 A/B 测试需要将用户均匀分配到不同的模型组,并收集业务指标:

 # Source: A/B Test
 import hashlib
 class ABTestRouter:
     def __init__(self, experiments: Dict[str, Dict]):
         # experiments: {
         #   "exp_001": {"model_a": 0.5, "model_b": 0.5},
         #   "exp_002": {"model_a": 0.9, "model_b": 0.1},
         # }
         self.experiments = experiments
     def assign(self, user_id: str, experiment_id: str) -> str:
         exp = self.experiments[experiment_id]
         # Consistent hashing, ensure same user always assigned to same group
         hash_val = int(hashlib.md5(f"{experiment_id}:{user_id}".encode()).hexdigest()[:8], 16)
         bucket = hash_val % 10000 # Ten-thousandths bucket
         cumulative = 0
         for model, weight in exp.items():
             cumulative += weight * 10000
             if bucket < cumulative:
                 return model
         return list(exp.keys())[-1] # fallback

A/B 测试的关键指标如表10-26所示: 表10-26 A/B 测试关键指标 指标类型 指标名称 说明 质量指标 人工评分 (win rate) 双盲对比的胜率 质量指标 自动评分 (LLM-as-Judge) 使用强裁判模型(如 GPT-4o / Claude)评估响应质量 业务指标 用户留存率 次日/7日留存 业务指标 对话完成率 用户主动结束 vs 中途退出 技术指标 平均响应长度 检测是否生成质量下降 技术指标 拒绝率 (Refusal Rate) 安全相关的拒答比例 5) LiteLLM Proxy 多模型路由 LiteLLM 提供了简洁的多模型管理和路由能力:

Source: LiteLLM proxy_config.

model_list:

  • model_name: gpt-4 litellm_params: model: azure/gpt-4 api_key: ${AZURE_API_KEY} api_base: ${AZURE_API_BASE}
  • model_name: llama-3-70b litellm_params: model: openai/llama-3-70b api_base: http://vllm-server:8000/v1 api_key: sk-none
  • model_name: llama-3-70b-canary # Canary version litellm_params: model: openai/llama-3-70b api_base: http://vllm-canary:8000/v1 api_key: sk-none router_settings: routing_strategy: "usage-based-routing" # Usage-based routing (correct LiteLLM enum) allowed_fails: 3 # Allowed failures num_retries: 2 # Retries fallbacks:
    • llama-3-70b: ["llama-3-8b"] # Fallback to 8B when 70B is unavailable 模型版本管理与灰度发布是推理服务平台化的关键能力。下一节将讨论多模型和多 LoRA 在共享 GPU 上的高效部署。

10.6.3 多模型 LoRA 共享 GPU

在推理服务平台中,为每个微调变体部署独立的 GPU 不仅成本高昂,而且 GPU 利用率极低。本节讨论在共享 GPU 上高 效服务多个模型和多个 LoRA 适配器的技术。

  1. 多模型共享 GPU 的方法 时间切片(Time-Slicing):GPU 时间片轮转执行不同模型的推理。实现简单但上下文切换开销大(需 reload 权重):

NVIDIA MPS Configuration

Start MPS control daemon

export CUDA_MPS_PIPE_DIRECTORY=/tmp/nvidia-mps export CUDA_MPS_LOG_DIRECTORY=/tmp/nvidia-log

 nvidia-cuda-mps-control -d
 # Multiple inference processes sharing the GPU
 CUDA_VISIBLE_DEVICES=0 python -m vllm.entrypoints.api_server --model model_a --port 8000 &
 CUDA_VISIBLE_DEVICES=0 python -m vllm.entrypoints.api_server --model model_b --port 8001 &

MIG(Multi-Instance GPU):NVIDIA A100/H100 的 MIG 将单个 GPU 物理划分为多个独立实例,每个实例拥有隔离的显 存和计算资源:

Enable MIG mode

sudo nvidia-smi -i 0 -mig 1

 # View available MIG configurations
 nvidia-smi mig -lgip
 # Create MIG instance
 # 1x 40GB + 3x 10GB

sudo nvidia-smi mig -cgi 9,14,14,14 -C

 # Each MIG instance runs inference
 CUDA_VISIBLE_DEVICES=MIG-GPU-xxx/0 python serve_model_a.py &
 CUDA_VISIBLE_DEVICES=MIG-GPU-xxx/1 python serve_model_b.py &

MIG 的局限性: •不支持 MIG 实例之间的动态显存调整 •MIG 实例不支持 NVLink(P2P 通信受限) •仅支持 A100 和 H100,A40/L40S 等不支持 •实例划分粒度有限(H100 最多 7 个实例) 基于 vLLM 的多模型服务:多模型服务通过独立 API Server 进程实现(vLLM LLM 对象会初始化全局 GPU 内存池,同一 进程中创建多个实例会因资源冲突而失败):

 # Start two independent vLLM API servers on one GPU
 CUDA_VISIBLE_DEVICES=0 python -m vllm.entrypoints.openai.api_server \
     --model meta-llama/Llama-3-8B-Instruct --gpu-memory-utilization 0.45 --port 8000 &
 CUDA_VISIBLE_DEVICES=0 python -m vllm.entrypoints.openai.api_server \
     --model mistralai/Mistral-7B-Instruct-v0.2 --gpu-memory-utilization 0.45 --port 8001 &
  1. LoRA 适配器共享服务 LoRA(Low-Rank Adaptation)通过在预训练权重旁添加低秩分解矩阵实现高效的模型微调。在推理服务中,一个基础 模型可以挂载多个 LoRA 适配器: GPU HBM Layout (80GB):

Note: base model in

┌──────────────────────────────────────────────┐ │ Base Model (Llama-3-70B FP8) ~70 GB │ ├──────────────────────────────────────────────┤ │ LoRA Adapter 1 (Code) ~100 MB │ │ LoRA Adapter 2 (Math) ~100 MB │ │ LoRA Adapter 3 (Chat) ~100 MB │ │ LoRA Adapter 4-5 (active) ~100 MB × 2│ ├──────────────────────────────────────────────┤ │ KV Cache Pool ~9.5 GB │ └──────────────────────────────────────────────┘

S-LoRA stores 100s of LoRA adapters in CPU memory; only hot ones stay in GPU HBM

S-LoRA(Sheng et al., 2024)是专门为大规模 LoRA 服务设计的系统: •统一分页(Unified Paging):将基础模型的 KV Cache 和 LoRA 权重统一用分页内存管理 •LoRA 批处理:同一批次中可以混合来自不同 LoRA 的请求,通过动态切换 LoRA 权重实现 •LoRA 权重缓存:高频使用的 LoRA 保留在 GPU,低频的换出到 CPU S-LoRA 性能:在 A100 上可同时服务 2000+ 个 LoRA,吞吐量相比基线提升 4-6x。 dLoRA 与 LoRA 服务演进(2024-2025):S-LoRA 和 Punica 解决了静态 LoRA 池的批处理效率,但从 2024 年下半年 起,LoRA 服务领域出现了两项关键演进。 dLoRA(Dynamic LoRA, 2024)的核心突破在于将 LoRA 权重视为可换入/换出的 GPU 内存页。与 S-LoRA 要求所有 LoRA 预加载到 GPU 不同,dLoRA 维护 CPU DRAM 中的全量 LoRA 池(可支持数万适配器),请求到达时按需加载 (PCIe 5.0 x16 约 64 GB/s,单个 LoRA 适配器 <2ms)。GPU 内存不足时将最不活跃的 LoRA 换出到 CPU,使 GPU 内存 中的 LoRA 容量与 HBM 大小解耦。 LoRA 服务技术对比(2025)如表10-27所示: 表10-27 LoRA 服务技术对比 方案 LoRA 容量 切换延迟 热/冷分离 发布时间 S-LoRA 约2000(GPU 内) <0.1ms 无 2024 Punica 约1000(GPU 内) <0.05ms 无 2024 LoRAX v1 动态加载 约100ms 有 2024 dLoRA / LoRAX v2 >10000(CPU+GPU 分层) <2ms 有 2024 Q4 vLLM multi-LoRA 约500(GPU 内) <0.1ms 有限 2025 选型建议:<500 LoRA 的热门池使用 vLLM 原生 multi-LoRA 或 S-LoRA 预加载;1000-10000 长尾 LoRA 应采用 dLoRA 风格的 GPU+CPU 分层方案。 Punica(多租户 LoRA 服务):通过 SGMV(Segmented Gather Matrix-Vector multiplication)CUDA 内核,在单次 kernel 中批处理来自不同 LoRA 适配器的请求,使一个 batch 内混合多个 LoRA 仍能并行完成,避免逐 LoRA 串行切换:

 # Source: Punica LoRA Service
 class PunicaLoRAServer:
     def __init__(self, base_model, lora_adapters):
         self.base_model = base_model
         self.lora_adapters = lora_adapters # Hundreds to thousands
         # Pre-compile CUDA Graph for each LoRA
         self.lora_graphs = {

name: cuda_graph_capture(adapter)

             for name, adapter in lora_adapters.items()
         }
    def generate_batch(self, requests):
        # Group requests by LoRA adapter
        groups = group_by_lora(requests)
        outputs = []
        for lora_name, batch in groups.items():
            # Switch to the correct LoRA
            self.base_model.activate_lora(self.lora_adapters[lora_name])
            # Execute efficiently using pre-compiled CUDA Graph
            output = self.lora_graphs[lora_name].replay(batch)
            outputs.extend(output)
        return outputs

LoRAX(动态 LoRA 加载):支持在服务运行中动态加载和卸载 LoRA 适配器,无需重启:

LoRAX start

docker run --gpus all -p 8000:8000 \

     -e MODEL_ID=meta-llama/Llama-3-70B-Instruct \
     -e MAX_LORA_RANK=64 \
     predibase/lorax:latest
 # Dynamic LoRA loading
 curl -X POST http://localhost:8000/adapters \
     -H "Content-Type: application/json" \
     -d '{

"adapter_id": "user/code-llama-lora", "adapter_source": "hub" }'

Inference with specific LoRA

curl http://localhost:8000/v1/chat/completions
-H "Content-Type: application/json"
-d '{ "model": "meta-llama/Llama-3-70B-Instruct", "adapter_id": "user/code-llama-lora", "messages": [{"role": "user", "content": "Write a Python function"}] }' 3) GPU 内存效率分析 纯模型加载(Llama-3-70B, BF16, TP=8 on H100×8):各部署方式的内存占用与可容纳模型数如表10-28所示: 表10-28 多模型部署内存效率 方案 模型权重 额外内存 总内存 容纳模型数 (8×80GB) 独立部署 70 GB/模型 KV Cache 可变 约75 GB/模型 1 分时共享 70 GB/模型 KV Cache 独立 约75 GB/模型 1-2 (需 swap) (MPS) MIG 70 GB/模型 (需 ≥70GB KV Cache 独立 - 1 (需 80GB 实例) 实例) LoRA 共享 70 GB 基座 +约10 GB per 100 LoRAs(按每适配器约 约80 GB + KV 1 基座 + N LoRAs 100 MB 计) Cache 经济收益(以下为数量级估算,实际节省取决于量化方案和负载特征): •独立部署 100 个微调模型(未量化、TP=8):100 × 8 = 800 GPUs •LoRA 共享(同一基座 + TP=8):约 9 GPUs(8 for TP + 1 for headroom) •极端下成本节省约 89x(800/9);若独立部署使用 FP8 量化(TP=4),则约为 44x(400/9) 4) 多 LoRA 调度的挑战 多 LoRA 服务面临的挑战与缓解方案如表10-29所示: 表10-29 多 LoRA 调度挑战 挑战 描述 缓解方案 LoRA 切换开销 GPU kernel 切换 LoRA 权重有开销 CUDA Graph 预编译;批量同 LoRA 请求 KV Cache 碎片化 不同 LoRA 的请求生命周期不同 统一分页管理;LoRA 维度标准化 负载不均 热门 LoRA vs 冷门 LoRA 热门 LoRA 的 KV Cache 常驻 GPU 显存碎片 数百 LoRA 的加载/卸载 内存池化 + 固定大小的 LoRA slot 多模型和 LoRA 共享大幅提升 GPU 利用率和成本效率。

10.7 推理 SLA 与可观测性

推理服务的质量承诺通过 SLA 体系量化,运维效果依赖全链路可观测性。本节先定义 SLI/SLO 与错误预算,说明限流、 优先级、过载保护与自动扩缩容等保障机制,再展开指标、链路追踪与告警等可观测性设计。

10.7.1 推理 SLA 保障体系

SLA(Service Level Agreement)是推理服务的质量承诺。本节讨论如何定义推理系统的 SLA 指标,设计 SLO 目标,并 通过限流、优先级和自动扩缩容实现 SLA 保障。

  1. 推理 SLI 定义 SLI(Service Level Indicator)是 SLA 的量化度量:

Source: Inference Service SLI

slis: latency: ttft_p50_ms: description: "TTFT median latency" target: "< 200ms (interactive), < 2s (batch)" ttft_p95_ms: description: "TTFT P95 latency" target: "< 500ms (interactive), < 5s (batch)" tpot_p50_ms: description: "TPOT median latency" target: "< 20ms" tpot_p95_ms: description: "TPOT P95 Latency" target: "< 50ms" availability: success_rate: description: "Successful request ratio (HTTP 2xx / total)" target: "> 99.9% (interactive), > 99.5% (batch)" error_rate: description: "Error request ratio (HTTP 5xx / total)" target: "< 0.1%" throughput: tokens_per_second: description: "System TPS (Output token)" requests_per_second: description: "System RPS" quality: refusal_rate: description: "Model refusal ratio (safety filter)" output_diversity: description: "Output diversity (avoid repetition)" 2) SLO 与错误预算 SLO(Service Level Objective)是 SLI 的具体数值目标。错误预算(Error Budget)是 SLO 允许的违反余量: Error Budget = 1 − SLO 例如,可用性 SLO = 99.9%,则月度错误预算 = 0.1% × 30 天 × 86400 秒 = 2592 秒 ≈ 43 分钟。 错误预算的使用策略: •开发环境可以更激进地消耗错误预算(快速迭代) •当错误预算消耗 > 80% 时,冻结新功能发布,专注稳定性

 # Source: Error Budget Monitoring
 class ErrorBudgetTracker:
     def __init__(self, slo: float, window_hours: int = 720):
         self.slo = slo # e.g., 0.999
         self.window_seconds = window_hours * 3600
         self.total_requests = 0
         self.error_requests = 0
     def record(self, success: bool):
         self.total_requests += 1
         if not success:
             self.error_requests += 1
     def remaining_budget(self) -> float:
         allowed_errors = (1 - self.slo) * self.total_requests
         return max(0, allowed_errors - self.error_requests)
     def budget_consumed_pct(self) -> float:
         allowed = (1 - self.slo) * self.total_requests
         return min(100, self.error_requests / max(1, allowed) * 100)
     def can_deploy(self) -> bool:
         """Whether error budget allows new deployment"""
         return self.budget_consumed_pct() < 50 # Allow when consumption < 50%
  1. 速率限制与节流 Token-Based 速率限制:相比传统的请求级别速率限制,LLM 推理更适合基于 token 的限流:
 # Source: Token-Based Rate
 from datetime import datetime, timedelta
 from collections import defaultdict
 class TokenRateLimiter:
     def __init__(self):
         # user_id ▶ [(timestamp, token_count), ...] sliding window
         self.windows = defaultdict(list)
     def check(self, user_id: str, requested_tokens: int,

limit_tokens_per_min: int = 10000) -> bool:

         now = datetime.now()
         window_start = now - timedelta(minutes=1)
         # Clean expired records
         self.windows[user_id] = [

(ts, count) for ts, count in self.windows[user_id] if ts > window_start ]

         # Check current usage
         current_tokens = sum(count for _, count in self.windows[user_id])
         if current_tokens + requested_tokens > limit_tokens_per_min:
             return False # Rate limited
         self.windows[user_id].append((now, requested_tokens))
         return True

多层级限流架构如图10-16所示: Request Entry Global Rate Limit System Total Capacity

                                User-Level Rate Limit
                               Per-User Token Budget
                               Model-Level Rate Limit
                                Per-Model Capacity
                               GPU-Level Backpressure

Backend Load All Passed? Yes No Execute Inference Return 429 Rate Limited 图10-16 多层级推理限流架构 4) 优先级队列与 QoS

# Source: Inference Priority Queue
import heapq
class PriorityScheduler:
    # Priority definitions
    PRIORITY_VIP = 0       # VIP users/Gold tier
    PRIORITY_REALTIME = 1 # Real-time interaction
    PRIORITY_STANDARD = 2 # Standard users
    PRIORITY_BATCH = 3     # Batch/background
    def __init__(self):
        self.queues = {
            self.PRIORITY_VIP: [],
            self.PRIORITY_REALTIME: [],
            self.PRIORITY_STANDARD: [],
            self.PRIORITY_BATCH: [],
        }
        self.quota_per_cycle = {
            self.PRIORITY_VIP: 0.20,      # 20% time slice
            self.PRIORITY_REALTIME: 0.40, # 40% time slice
            self.PRIORITY_STANDARD: 0.30, # 30% time slice
            self.PRIORITY_BATCH: 0.10,    # 10% time slice
        }
        self.cycle_counters = {p: 0.0 for p in self.queues}
    def get_next(self):
        """Weighted fair queue: ensure high priority gets more resources without starving low priority"""
        effective_weights = {

p: self.quota_per_cycle[p] - self.cycle_counters[p]

            for p in self.queues
        }
        selected = max(effective_weights, key=effective_weights.get)
        if self.queues[selected]:
            self.cycle_counters[selected] += 1
            return self.queues[selected].pop(0)
        # Selected queue empty, pick another
        for p in sorted(self.queues.keys()):
            if self.queues[p]:
                return self.queues[p].pop(0)
        return None
  1. 过载保护 推理系统的过载保护机制包括反压与熔断。 反压(Backpressure):
 # Source: Adaptive Backpressure
 class AdaptiveBackpressure:
     def __init__(self):
         self.queue_depth = 0
         self.gpu_utilization = 0.0
         self.kv_cache_usage = 0.0
     def should_accept(self) -> bool:
         """Determine whether to accept new requests"""
         checks = [
             self.queue_depth < 8000,          # Queue length limit
             self.gpu_utilization < 0.95,      # GPU not full
             self.kv_cache_usage < 0.90,       # KV Cache not full

]

         return all(checks)
     def adapt_sampling(self) -> Optional[float]:
         """Adaptive dropping strategy"""
         if self.queue_depth > 10000:
             return 0.1 # Only accept 10% of requests
         elif self.queue_depth > 8000:
             return 0.5 # Accept 50% of requests
         return None      # No dropping

熔断器(Circuit Breaker):

 # Source: Inference Circuit Breaker
 class InferenceCircuitBreaker:
     def __init__(self, failure_threshold=5, recovery_timeout=60):
         self.state = "CLOSED" # CLOSED ▶ OPEN ▶ HALF_OPEN ▶ CLOSED
         self.failure_count = 0
         self.failure_threshold = failure_threshold
         self.recovery_timeout = recovery_timeout
         self.last_failure_time = None
     def call(self, backend, request):
         if self.state == "OPEN":
             if time.time() - self.last_failure_time > self.recovery_timeout:
                 self.state = "HALF_OPEN"
             else:

raise CircuitBreakerOpenError() try:

             result = backend.execute(request)
             if self.state == "HALF_OPEN":
                 self.state = "CLOSED"
                 self.failure_count = 0
             return result

except Exception:

             self.failure_count += 1
             self.last_failure_time = time.time()
             if self.failure_count >= self.failure_threshold:
                 self.state = "OPEN"

raise 6) 自动扩缩容 推理服务的自动扩缩容策略需要考虑模型加载延迟(冷启动):

Source: Kubernetes HPA + KEDA

apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: vllm-autoscaler spec: scaleTargetRef: name: vllm-deployment minReplicaCount: 2 maxReplicaCount: 20 triggers: - type: prometheus metadata: serverAddress: http://prometheus:9090 metricName: queue_depth_per_replica query: | avg(vllm:num_requests_waiting) threshold: "50" # Scale up when average waiting requests exceed 50 - type: prometheus metadata: serverAddress: http://prometheus:9090 metricName: gpu_utilization query: avg(avg_over_time(DCGM_FI_DEV_GPU_UTIL[5m])) threshold: "85" # Scale up when GPU utilization exceeds 85% advanced: horizontalPodAutoscalerConfig: behavior: scaleDown: stabilizationWindowSeconds: 300 # Wait 5 minutes before scaling down scaleUp: stabilizationWindowSeconds: 60 # Takes effect after 1 minute of scale up 自动扩缩容是 SLA 保障的最后一道防线,配合全链路可观测性,形成完整的推理服务运营体系。

10.7.2 推理全链路可观测性

推理服务的可观测性通过 Metrics、Traces、Logs 三大支柱,提供从请求入口到 GPU 运算的端到端可视化。本节讨论推 理系统特有的可观测性设计。

  1. 可观测性三大支柱 推理可观测性的整体架构如图10-17所示: Inference Observability Metrics Traces Logs System Metrics: GPU/CPU/ Business Metrics: TTFT/TP Engine Metrics: KV Cache/ Request Trace: Gateway▶ Model Chain: Prefill▶Deco Request Log: Prompt/Resp System Log: Error/Warning Audit Log: Access/Operatio Memory OT/TPS Batch Engine▶GPU de▶Token Gen onse n 图10-17 推理可观测性架构
  2. 核心推理指标 vLLM 在 /metrics 端点暴露丰富的 Prometheus 指标:

Request-level metrics

vllm:request_success_total{model_name="llama-3-70b"} 125000 vllm:request_duration_seconds{quantile="0.5"} 2.3 vllm:request_duration_seconds{quantile="0.95"} 8.7

Token throughput

rate(vllm:prompt_tokens_total[1m]) # Input token throughput rate(vllm:generation_tokens_total[1m]) # Output token throughput

KV Cache

vllm:gpu_cache_usage_perc{gpu_id="0"} 0.78 # 78% usage vllm:cpu_cache_usage_perc 0.05 # CPU swap usage

Scheduler state

vllm:num_requests_running 32 # Running requests vllm:num_requests_waiting 18 # Waiting requests vllm:num_requests_swapped 5 # Preempted to CPU requests

Prefix cache

vllm:prefix_cache_hit_rate 0.42 # 42% hit rate

GPU resource utilization

vllm:gpu_utilization{gpu_id="0"} 0.85 # SM utilization vllm:gpu_memory_used_bytes{gpu_id="0"} 68.2e9 # Memory usage 自定义推理指标:

 # Source: Custom Inference Metrics
 from prometheus_client import Counter, Histogram, Gauge, start_http_server
 # Metric definitions
 ttft_seconds = Histogram(

'inference_ttft_seconds', 'Time to first token', buckets=[0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10] ) tpot_seconds = Histogram( 'inference_tpot_seconds', 'Time per output token', buckets=[0.005, 0.01, 0.02, 0.03, 0.05, 0.1, 0.2] ) requests_in_flight = Gauge('inference_requests_in_flight', 'Active requests') request_errors = Counter( 'inference_request_errors_total', 'Failed requests', ['error_type'] ) tokens_generated = Counter( 'inference_tokens_generated_total', 'Total tokens generated', ['model_name'] ) start_http_server(9090) # Expose Prometheus metrics endpoint 3) 分布式链路追踪 对于多步推理(如 RAG 检索→推理→后处理)、模型链式调用(如 LangChain agent),分布式追踪至关重要:

 # Source: OpenTelemetry Inference Tracing
 from opentelemetry import trace
 from opentelemetry.sdk.trace import TracerProvider
 from opentelemetry.sdk.trace.export import BatchSpanProcessor
 from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
 trace.set_tracer_provider(TracerProvider())
 otlp_exporter = OTLPSpanExporter(endpoint="http://jaeger:4317")
 trace.get_tracer_provider().add_span_processor(
     BatchSpanProcessor(otlp_exporter)

) tracer = trace.get_tracer(name) @tracer.start_as_current_span("llm_inference")

 def inference_with_trace(prompt: str):
     span = trace.get_current_span()
     span.set_attribute("prompt_length", len(prompt))
     span.set_attribute("model", "llama-3-70b")

with tracer.start_as_current_span("prefill"): kv_cache = prefill(prompt) span.set_attribute("prefill_ms", prefill_duration) with tracer.start_as_current_span("decode"): tokens = [] for i in range(max_tokens): with tracer.start_as_current_span(f"decode_step_{i}"):

                 token = decode_step(kv_cache)
                 tokens.append(token)
                 if token == eos_token:

break

     span.set_attribute("generated_tokens", len(tokens))
     span.set_attribute("total_ms", total_duration)
     return tokens

推理链路示例(Jaeger UI): Gateway (800ms) ├── Auth (5ms) ├── Rate Limit Check (2ms) ├── Router Decision (1ms) └── LLM Inference (790ms) ├── Prefill (120ms) ├── Decode Step 1 (25ms) ├── Decode Step 2 (22ms) ├── ... └── Decode Step 30 (18ms) 4) Grafana 仪表盘设计 推理服务核心仪表盘: { "dashboard": { "title": "Inference Service Overview - Llama-3-70B", "panels": [ { "title": "Request Throughput (RPS)", "targets": [ {"expr": "rate(vllm:request_success_total[1m])"} ], "thresholds": [{"value": 100, "color": "red"}] }, { "title": "TTFT Latency Distribution (P50/P95/P99)", "targets": [ {"expr": "histogram_quantile(0.50, rate(vllm:time_to_first_token_seconds_bucket[5m]))"}, {"expr": "histogram_quantile(0.95, rate(vllm:time_to_first_token_seconds_bucket[5m]))"}, {"expr": "histogram_quantile(0.99, rate(vllm:time_to_first_token_seconds_bucket[5m]))"} ] }, { "title": "TPOT Latency Distribution", "targets": [ {"expr": "histogram_quantile(0.50, vllm:time_per_output_token_seconds)"}, {"expr": "histogram_quantile(0.95, vllm:time_per_output_token_seconds)"} ] }, { "title": "GPU Utilization", "targets": [ {"expr": "avg(DCGM_FI_DEV_GPU_UTIL) by (gpu)"}, # GPU SM utilization from DCGM Exporter ] }, { "title": "GPU Memory and KV Cache Usage", "targets": [ {"expr": "vllm:gpu_memory_used_bytes / 80e9"}, {"expr": "vllm:gpu_cache_usage_perc"} ], "yaxis": {"max": 100, "format": "percentunit"} }, { "title": "Request Queue Status", "targets": [ {"expr": "vllm:num_requests_running"}, {"expr": "vllm:num_requests_waiting"}, {"expr": "vllm:num_requests_swapped"} ] }, { "title": "Prefix Cache Hit Rate", "targets": [ {"expr": "vllm:prefix_cache_hit_rate"} ], "thresholds": [ {"value": 0.50, "color": "green"}, {"value": 0.30, "color": "orange"}, {"value": 0.10, "color": "red"} ] }, { "title": "Error Rate", "targets": [ {"expr": "rate(inference_request_errors_total[1m]) / rate(vllm:request_success_total[1m])"} ], "thresholds": [{"value": 0.01, "color": "red"}] } ] } } 5) 告警规则设计

Source: Prometheus Alerting Rules

groups:

  • name: inference_sla rules:
    • alert: HighTTFTLatency expr: histogram_quantile(0.95, rate(vllm:time_to_first_token_seconds_bucket[5m])) > 1.0 for: 5m labels: severity: warning annotations: summary: "P95 TTFT exceeds 1s" description: "Current P95 TTFT: {{ $value }}s"
    • alert: HighTPOTLatency expr: histogram_quantile(0.95, rate(vllm:time_per_output_token_seconds_bucket[5m])) > 0.1 for: 5m labels: severity: warning annotations: summary: "P95 TPOT exceeds 100ms"
    • alert: LowGPUUtilization expr: avg(DCGM_FI_DEV_GPU_UTIL) < 50 # GPU SM utilization from DCGM (unit: %) for: 15m labels: severity: info annotations: summary: "GPU utilization below 50%, may be misconfigured"
    • alert: HighKVCacheUsage expr: vllm:gpu_cache_usage_perc > 0.95 for: 5m labels: severity: critical annotations: summary: "KV Cache usage exceeds 95%, OOM imminent"
    • alert: HighErrorRate expr: rate(inference_request_errors_total[5m]) / rate(vllm:request_success_total[5m]) > 0.01 for: 5m labels: severity: critical annotations: summary: "Inference error rate exceeds 1%"
    • alert: PodFrequentRestarts expr: rate(kube_pod_container_status_restarts_total[10m]) > 0.05 for: 10m labels: severity: warning annotations: summary: "Inference Pod frequently restarts"
    • alert: QueueDepthHigh expr: avg(vllm:num_requests_waiting) > 100 for: 10m labels: severity: warning annotations: summary: "Waiting queue depth exceeds 100"
  1. 可观测性技术栈 推荐的推理可观测性技术栈如表10-30所示: 表10-30 可观测性技术栈 组件 推荐工具 用途 指标收集 Prometheus + DCGM-Exporter GPU 指标 + 应用指标 组件 推荐工具 用途 指标可视化 Grafana 实时仪表盘 分布式追踪 Jaeger / Tempo 请求链路可视化 日志聚合 Loki / ELK 集中日志查询 告警 AlertManager + PagerDuty 分级告警通知 GPU 诊断 NVIDIA DCGM + nvidia-smi GPU 健康状态 Profiling PyTorch Profiler + TensorBoard 性能剖析 全链路可观测性是推理服务平台化运营的基础。

10.8 高可用推理平台实战

本节将推理优化的核心组件整合为一个完整的实战项目,构建一个支持 1000+ 模型的高可用推理平台。

10.8.1 平台架构总览

千模型推理平台的总体架构如图10-18所示: Control Plane Model Registry MLflow Ingress Layer Load Balancer Global Scheduler Monitoring & Alerting AWS ALB / Nginx LPM Prometheus + Grafana Inference Gateway LiteLLM / Custom Compute Plane - GPU Cluster Cold Pool - Cold Models Warm Pool - LoRA Models Hot Pool - Hot Models Serverless Instance Serverless Instance LoRAX Server 1 LoRAX Server 2 vLLM Server 1 vLLM Server 2 vLLM Server 3 On-demand loading On-demand loading Code LoRA × 200 Chat LoRA × 300 Llama-3-70B Llama-3-8B Mistral-7B Storage Layer Object Storage Distributed KV Store (Metadata DB Model Weights 3FS / Redis PostgreSQL) 图10-18 千模型推理平台总体架构

10.8.2 分层部署策略

按模型热度和成本将 1000+ 模型分层部署,各层的配置如表10-31所示: 表10-31 千模型平台分层部署策略 层级 模型数量 部署方式 GPU 配置 冷启动 成本/模型 Hot Pool 约20 (Top 2%) 常驻 GPU,vLLM 8×H100 per model 0 $$$ Warm Pool 约200 (Top 20%) LoRA 共享基座 8×H100 per 200 LoRA <1s (weight swap) $$ Cold Pool 约800 (Long-tail) Serverless,按需加载 共享 GPU 池 30-120s (model load) $ Hot Pool 部署(vLLM):

Source: K8s Deployment

apiVersion: apps/v1 kind: Deployment metadata: name: llama-3-70b-hot labels: pool: hot model: llama-3-70b-instruct spec: replicas: 2 # High availability dual replicas selector: matchLabels: model: llama-3-70b-instruct template: metadata: labels: pool: hot model: llama-3-70b-instruct spec: nodeSelector: gpu-type: h100-sxm5-80gb containers: - name: vllm image: vllm/vllm-openai:latest args:

           - "--model"
           - "s3://models/llama-3-70b-instruct/v2/"
           - "--tensor-parallel-size"
           - "8"
           - "--max-model-len"
           - "8192"
           - "--gpu-memory-utilization"
           - "0.92"
           - "--max-num-seqs"
           - "128"
           - "--enable-prefix-caching"
           - "--port"
           - "8000"

ports: - containerPort: 8000 resources: limits: nvidia.com/gpu: 8 env: - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: s3-credentials key: access-key - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: s3-credentials key: secret-key readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 120 periodSeconds: 10 livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 300 periodSeconds: 30 Warm Pool 部署(LoRAX):

Source: K8s Deployment

apiVersion: apps/v1 kind: Deployment metadata: name: lorax-code-pool labels: pool: warm spec: replicas: 3 template: spec: containers: - name: lorax image: predibase/lorax:latest args:

           - "--model-id"
           - "meta-llama/Llama-3-70B-Instruct"
           - "--max-lora-rank"
           - "64"
           - "--max-loaded-loras"
           - "200"
           - "--max-waiting-loras"
           - "500"

env: - name: LORA_ADAPTERS_SOURCE value: "s3://models/loras/" resources: limits: nvidia.com/gpu: 8 ports: - containerPort: 8000 Cold Pool 部署(Serverless): Source: KEDA ScaledJob for cold models apiVersion: keda.sh/v1alpha1 kind: ScaledJob metadata: name: cold-model-loader spec: jobTargetRef: template: spec: containers: - name: model-loader image: model-server:latest command: ["/bin/bash", "-c"] args: - | # Download model weights and start service aws s3 sync s3://models/${MODEL_NAME}/ /tmp/model/

              python -m vllm.entrypoints.api_server \
                --model /tmp/model/ \
                --max-model-len 4096 \
                --gpu-memory-utilization 0.85

env: - name: MODEL_NAME valueFrom: configMapKeyRef: name: cold-queue key: next_model resources: limits: nvidia.com/gpu: 1 # Auto scale down after 300 seconds idle ttlSecondsAfterFinished: 300 triggers: - type: redis metadata: address: redis:6379 listName: cold-model-queue listLength: "1"

10.8.3 LiteLLM 网关配置

Source: LiteLLM Proxy Global

general_settings: master_key: ${LITELLM_MASTER_KEY} database_url: "postgresql://litellm:password@postgres:5432/litellm" model_list:

Hot Pool Models

Warm Pool Models (via LoRA)

  • model_name: code-llama-lora litellm_params: model: openai/llama-3-70b api_base: http://lorax-code-pool.default.svc:8000/v1 api_key: sk-none extra_body: adapter_id: "code-llama-lora-v2" model_info: tier: warm max_tokens: 4096

Priority routing

fallbacks: ["llama-3-8b-backup"] router_settings: routing_strategy: "latency-based-routing" allowed_fails: 5 num_retries: 3 cooldown_time: 30 # Cooldown 30 seconds for failed backends enable_tag_filtering: true # Route by tag (pool=tier) litellm_settings: num_retries: 3 request_timeout: 600 set_verbose: false drop_params: true success_callback: ["prometheus"] # Export metrics to Prometheus failure_callback: ["prometheus"]

10.8.4 高可用设计

多可用区部署: AZ-1 (us-east-1a): Hot Pool: llama-3-70b (2 replicas) Warm Pool: lorax-pool (1 replica) AZ-2 (us-east-1b): Hot Pool: llama-3-70b (2 replicas) Warm Pool: lorax-pool (1 replica) AZ-3 (us-east-1c): Hot Pool: llama-3-70b (1 replica) # Minimum replicas Cold Pool: serverless nodes Leader Election for Global Scheduler:

Source: Global Scheduler HA

apiVersion: v1 kind: ConfigMap metadata: name: scheduler-leader-lock annotations: control-plane.alpha.kubernetes.io/leader: | {"holderIdentity":"scheduler-7b8f9c5d-abcde"} 跨区域流量管理:

10.8.5 监控与告警配置

 # Source: Region-Aware Routing
 class MultiRegionRouter:
     def route(self, request, user_region):
         # Prefer routing to same-region inference instances (low latency)
         primary = self.get_backends_in_region(user_region)
         if primary:
             backend = self.kv_aware_route(request.prompt, primary)
             if backend:
                 return backend
         # Same region unavailable, fallback to nearest region
         nearest = self.get_nearest_region(user_region)
         return self.least_loaded_in_region(nearest)

核心 SLO Dashboard: { "title": "Inference Platform SLO Overview", "rows": [ { "title": "Global SLO Compliance Rate", "panels": [ { "title": "Availability (Target: 99.9%)", "targets": [ {"expr": "sum(rate(vllm:request_success_total[1h])) / sum(rate(vllm:request_success_total[1h]) + rate(infe rence_request_errors_total[1h]))"} ] }, { "title": "TTFT P95 (Target: < 500ms)", "targets": [ {"expr": "histogram_quantile(0.95, sum(rate(vllm:time_to_first_token_seconds_bucket[5m])) by (le))"} ] } ] }, { "title": "Model Popularity Distribution", "panels": [ { "title": "Top 20 Hot Models", "targets": [ {"expr": "topk(20, sum(rate(vllm:request_success_total[1h])) by (model_name))"} ] } ] }, { "title": "Tiered Cost Distribution", "panels": [ { "title": "Cost Distribution by Tier", "targets": [ {"expr": "sum(gpu_cost_dollars_per_hour) by (tier)"} ] } ] } ]

10.8.6 成本优化策略

}

  1. Spot/Preemptible 实例混合:

Source: Multi-Instance-GPU NodePool (Karpenter)

apiVersion: karpenter.sh/v1 # Karpenter v1 GA (v1beta1 deprecated as of Karpenter 1.0) kind: NodePool metadata: name: inference-mixed-gpu spec: template: spec: requirements: - key: "nvidia.com/gpu.product" operator: In values: ["NVIDIA-H100-80GB-HBM3", "NVIDIA-A100-SXM4-80GB"] - key: "karpenter.sh/capacity-type" operator: In values: ["on-demand", "spot"] nodeClassRef: name: gpu-node-class limits: nvidia.com/gpu: 256 disruption: consolidationPolicy: WhenUnderutilized expireAfter: 720h # Spot to On-demand strategy 2) GPU 时间分片:对于低 QPS 模型,通过 GPU 分时复用降低空闲成本:

10.8.7 推理引擎选型策略

 # Source: GPU Time-Slice
 class TimeSliceScheduler:
     def __init__(self, num_slots=4, slot_duration_minutes=15):
         self.slots = [None] * num_slots # slot ▶ model_name
         self.slot_duration = slot_duration_minutes * 60
     def allocate_slot(self, model_name):
         for i, slot in enumerate(self.slots):
             if slot is None:
                 self.slots[i] = model_name
                 return i
         return None # no free slot
     async def run_scheduler(self):
         while True:
             for i in range(len(self.slots)):
                 model = self.slots[i]
                 if model:
                     # Allocate slot_duration seconds of exclusive GPU time to this model
                     activate_model(model, gpu_slot=i)
                     await asyncio.sleep(self.slot_duration)
             # One round complete, re-schedule

千模型推理平台的核心架构决策之一是推理引擎的选型。截至 2025 年中,主流开源推理引擎的差异化依据如表10-32所 示: 表10-32 主流推理引擎选型对比 引擎 核心优势 核心局限 推荐场景 vLLM PagedAttention 成熟度最高、社区最活跃、连续 V0 引擎内存碎片、前缀缓存开销 通用推理主力引擎 批处理稳定 SGLang RadixAttention 前缀缓存命中率高、结构化输出 社区规模小于 vLLM、部分模型适 系统提示共享场景、API 原生支持 配不完善 生成 TensorRT- NVIDIA 深度优化、H100/B200 MFU 最高(约 模型支持滞后、配置复杂、编译耗 NVIDIA 全栈环境、极致 LLM 75%) 时长 性能 引擎 核心优势 核心局限 推荐场景 TGI HuggingFace 生态集成好 性能不如 vLLM/SGLang、社区活 HuggingFace Hub 优先 跃度下降 LoRAX 动态 LoRA 加载/卸载、支持数千适配器 仅专注 LoRA 场景 Warm Pool 多 LoRA 层 在系统提示高度共享的场景下,SGLang 的 RadixAttention 前缀缓存命中率可达 90% 以上,官方基准(ShareGPT 负载) 中其吞吐量比 vLLM v0.5.x 高 15-30%,在长 system prompt 加短 user prompt 场景优势明显;SGLang v0.4+ 的 Overlap Scheduling(调度与计算重叠)与 FP8 量化推理进一步提升了高并发下的性能。 多后端策略:平台架构层(网关和调度器)应保持引擎无关性。通过统一的 OpenAI-compatible API 协议,可在不同负 载特征下动态路由到最优引擎,避免单一引擎绑定带来的技术债务。

10.8.8 部署清单

上线前检查清单: [ ]模型权重已上传到 S3,SHA256 校验完成 [ ]vLLM/LoRAX 部署 YAML 已通过 kubectl --dry-run 验证 [ ]LiteLLM 网 关配置已测试(包含所有模型的 fallback 路径) [ ]Prometheus 指标端点可访问 (http://vllm:8000/metrics) [ ]Grafana 仪表盘已导入并验证数据展示 [ ]告警规则已配置并通过 AlertManager 发送测试告警 [ ]HPA/KEDA 扩缩容策略已测试(模 拟高负载触发扩容) [ ]多 AZ 部署已通过单 AZ 故障演练验证 [ ]冷启动时间已基准测试(从请求到首次 token) [ ]端到端 延迟已满足 SLA(TTFT P95 < 500ms,TPOT P95 < 50ms) 持续运营阶段: Weekly:

   - Check error budget consumption rate
   - Review top 10 highest latency models
   - Evaluate capacity utilization (Hot/Warm/Cold ratio)

Monthly:

   - Archive model versions (versions unused for 30 days)
   - Cost analysis report (by tier, team, model)
   - SLO compliance rate review
   - Platform capacity planning (pre-scale GPU based on growth trend)

Quarterly:

   - Inference engine version upgrade (vLLM/TGI new versions)
   - GPU generation upgrade evaluation (H100 ▶ B200 migration plan)
   - Full fault drill (Chaos Engineering)

千模型推理平台是推理技术的集大成实践。

10.9 端侧与边缘推理

本节讨论端侧与边缘场景下的模型推理,覆盖约束维度、代表性框架、图切分与内存规划、端云协同以及自研 NPU 上的 优化路径。不涉及云端 GPU 集群的调度与容量管理。

10.9.1 端侧推理的约束与挑战

端侧推理(On-device Inference)指在手机、汽车、边缘服务器等靠近数据源的设备上直接运行模型,其约束维度与云 端推理差异显著,可归纳为算力、内存、功耗与延迟隐私四类。

  1. 算力 端侧芯片以 INT8/INT4 算力为主,FP16/BF16 算力通常仅为其一半到四分之一。旗舰手机 SoC 的 NPU INT8 算力约在 30-80 TOPS 量级,边缘服务器可达数百 TOPS,但相比数据中心 GPU 的 FP16 稠密算力(H100 约 989 TFLOPS)仍有数 量级差距。低精度因此成为端侧算力的第一约束,模型必须通过量化才能发挥硬件能力。
  2. 内存 设备内存带宽决定了自回归生成的单 token 延迟下限。生成阶段每个 token 需要读一遍全部权重,访存密集型特征使带 宽成为硬约束: Mweights Ttoken ≈ BDRAM 其中 M 为权重大小,B 为内存带宽。7B 模型 INT4 量化后约 3.5GB,在约 25GB/s 的 LPDDR5 带宽下单 token 延迟约 140ms;同模型在 H100 的 HBM3 带宽(约 3.35TB/s)下仅约 1ms。激活值与中间张量还受片上 SRAM 容 weights DRAM 量限制,过大的中间张量会被迫落回 DRAM。
  3. 功耗与散热 端侧设备功耗预算从手机的 3-15W 到边缘服务器的数百瓦不等,且缺乏数据中心级散热。高负载下 SoC 触发热节流 (thermal throttling)动态降频,推理引擎需感知功耗预算,在批大小、频率与精度之间折中。
  4. 延迟与隐私 端侧推理的核心动机是低延迟、数据不出设备与离线可用。本地计算省去网络往返,敏感数据驻留本机,弱网环境仍可提 供服务。代价是设备端模型能力有限,复杂任务依赖端云协同补足。 端侧与云端推理的差异汇总如表10-33所示: 表10-33 端侧与云端推理差异 维度 云端推理 端侧推理 算力 数百 TFLOPS FP16 数十 TOPS INT8 内存带宽 HBM 2-3.5 TB/s LPDDR 20-70 GB/s 功耗预算 每卡 300-700W 整机 3-15W 批处理 高并发连续批处理 单请求低时延 网络依赖 强 可离线 数据边界 数据出域 数据驻留本机 端侧推理的本质约束是把模型装进有限资源,后续小节围绕算子映射、内存规划与多端协同展开。

10.9.2 端侧推理框架

端侧推理框架解决模型在异构端侧硬件上的高效运行问题,代表性运行时包括 llama.cpp、MLC-LLM 与 ExecuTorch。

  1. llama.cpp llama.cpp 以 C/C++ 实现大模型推理,核心特点是统一的量化模型格式 GGUF(GPT-Generated Unified Format)与多 后端支持。GGUF 把权重、分词器、超参数与量化元数据封装进单个文件,便于分发与版本管理;格式分层清晰,量化类 型、张量元数据与权重数据分区存放,新量化格式可通过追加层扩展而不破坏旧文件。llama.cpp 提供 CPU 后端(x86 的 AVX2/AVX-512、ARM 的 NEON/SVE)与 GPU 后端(CUDA、Metal、Vulkan),并支持权重在 CPU 与 GPU 间分片。

Requires llama.cpp built with CPU and Metal backends

./llama-cli -m models/llama-3.2-1b-q4_0.gguf \

   --threads 8 \
   -n 256 \
   -p "Summarize the system architecture"
  1. MLC-LLM MLC-LLM 采用编译器化部署路线:模型经 TVM 编译栈生成针对目标硬件的原生代码,部署到 Vulkan、Metal、WebGPU 与 C++ 运行时。编译期完成算子融合与张量重排,运行期无动态图解释开销。WebGPU 后端使模型可运行于浏览器,以 零安装方式分发。
  2. ExecuTorch ExecuTorch 是 PyTorch 生态的端侧运行时,通过 torch.export 将模型导出为静态图,经量化与编译后部署到移动端和嵌 入式硬件。它提供可扩展的算子库接口,便于将自定义算子接入自研 NPU 后端。 如图10-19所示,端侧框架处于应用与硬件之间,通过后端抽象屏蔽 CPU、GPU 与 NPU 的差异,上层应用以统一接口调 用。 Application Layer Chat Assistant Speech Recognition Visual QA Framework Layer llama.cpp ONNX Runtime MLC-LLM ExecuTorch

Backend Layer CPU Kernels GPU Backend NPU Backend AVX/NEON/SVE Metal/Vulkan/WebGPU Hardware Layer CPU GPU NPU 图10-19 端侧推理软件栈 GGUF 内置 Q2_K、Q4_K、Q5_K、Q6_K、Q8_0 等量化类型,可按层敏感度选用不同精度:敏感层用较高精度,冗余层 用较低精度。量化类型与张量布局记录在文件元数据中,推理引擎据此选择对应内核,加载时无需额外配置。 框架选型取决于硬件与分发场景:llama.cpp 适合以 CPU 为主的桌面与服务器,MLC-LLM 适合需要极致性能的跨平台部 署,ExecuTorch 适合 PyTorch 生态与自研硬件。

10.9.3 模型图切分与内存规划

端侧内存规划的目标是在固定 DRAM 预算内同时容纳权重、激活值与 KV Cache,核心手段包括静态布局、图切分、权重 页换与 KV Cache 管理。

  1. 静态内存布局 端侧推理通常在编译期完成静态内存布局(static memory planning):为每个算子需要的临时张量分配固定偏移,并复 用生命周期不重叠的缓冲区,运行期零动态分配、零碎片。TVM 系编译器在编译期完成分配,运行时仅按偏移读写。
  2. 图切分与算子融合 计算图按层切分为若干段,逐段映射到 CPU、GPU 或 NPU,段间传输的数据量决定切分质量。算子融合(operator fusion)将相邻访存型算子(RMSNorm、SiLU、残差加等)并入矩阵乘内核,减少中间张量落 DRAM 的次数。融合边界 应选在片上缓存能容纳中间结果的位置。
  3. 权重驻留与页换 权重超过 DRAM 预算时,按层或按段驻留,通过页换(paging)按需换入。引擎维护权重页表,仅将当前计算段对应的 页读入 DRAM,计算完成后释放。与云端 vLLM 的 KV Cache 页换不同,端侧页换对象是权重与激活,粒度以段为单位。
  4. KV Cache 的端侧管理 单请求 KV Cache 的大小为: MKV = 2 × L × HKV × dhead × S × b

其中 L 为层数,H 为 KV 头数,d 为注意力头维度,S 为序列长度,b 为每元素字节数。端侧上下文通常为 4-16K, KV 仍达数十至数百 MB。管理策略围绕预分配与窗口裁剪:按 max_length 预分配连续内存避免碎片,长上下文启用窗 KV head 口注意力(sliding window attention)仅保留最近窗口的 KV。 5) 模型部分加载 对超出内存预算的超大模型,仅加载生成阶段实际触发的层,或配合稀疏激活跳过未激活的专家层。投机解码 (speculative decoding)的草稿模型与主干模型分驻设备,进一步压缩常驻内存。

 # Requires: a static planner pass in the compilation stack
 def plan_memory(blocks, dram_bytes):
     # Assign fixed offsets and reuse buffers whose lifetimes do not overlap

offsets, base, free = {}, 0, [] for name, size in blocks: if free and free[-1][1] >= size: off, _ = free.pop()

         else:
             off = base
             base += size

offsets[name] = off if base > dram_bytes: raise MemoryError("plan exceeds DRAM budget")

10.9.4 端云协同推理

 return offsets, base

端云协同推理(edge-cloud hybrid inference)在端侧小模型与云端大模型之间按请求特性路由,兼顾时延、成本与质 量。

  1. 意图与复杂度路由 路由层依据 prompt 长度、任务类型与历史成功率预估复杂度:翻译、摘要、格式化等简单任务由端侧完成,代码生成、 多跳推理等复杂任务上抛云端。路由阈值可离线基于验证集标注,也可用在线反馈自适应调整。如图10-20所示,路由结 果进入置信度校验环节,形成闭环。
  2. 本地初判与云端兜底 端侧小模型先生成草稿,若置信度(logit 熵或验证器打分)低于阈值,则请求云端大模型重生成。该模式可视为跨设备 投机解码:端侧草稿模型覆盖高频简单样本,云端验证模型保证质量兜底,两者在单次交互内配合完成。
  3. 隐私保护与配额管理 隐私敏感内容(医疗、个人数据)强制在端侧处理,或经脱敏后上送。云端访问受配额约束,按用户或租户计量 token 预算与 QPS,超限时降级为纯端侧模式,防止单个终端耗尽集群资源。
  4. 量化感知的端云一致性 端云若部署同一模型的不同量化版本,需保证输出分布一致。一致性依赖对齐的校准数据与量化算法:量化前使用相同校 准集、相同量化粒度,部署前度量端云 logits 的 KL 散度与 top-k 重合率,差异超阈值则触发云端重生成。 Request Complexity Estimation low On-device Small Model

high Confidence Check fail pass Return Device Result Cloud LLM Fallback Return Cloud Result 图10-20 端云协同推理流程

10.9.5 自研 NPU 上的推理优化

自研 NPU(Neural Processing Unit)以定制的脉动阵列或张量核执行矩阵乘,配合大容量片上 SRAM 实现低功耗高吞 吐,但指令集与软件栈封闭,通用框架难以直接运行。

  1. NPU 硬件特性 脉动阵列(systolic array)是二维乘累加阵列:权重预加载进阵列,激活数据流式灌入,单周期完成整块矩阵乘。张量 核(tensor core)形态类似,面向小矩阵密集乘加。片上 SRAM 通常为数十至数百 MB,是缓解带宽瓶颈的唯一手段,数 据进入 SRAM 后应尽量复用。指令集以 INT8/INT4 乘累加为原生单元,FP16 算力低或缺失。
  2. 算子映射与图切分 GEMM、RMSNorm、注意力等算子需逐算子映射到 NPU 指令,映射核心是循环分块(loop tiling):按 SRAM 容量把 GEMM 切为 tile,激活与权重 tile 在片上复用,分块尺寸须与 SRAM 容量和搬运带宽匹配。图切分时融合段边界应选在跨 单元通信代价最小的位置,通常落在注意力层前后。
  3. 量化策略 端侧 NPU 主流量化组合为 W4A8 与 W8A8:权重以 4-bit 驻留以减半访存,激活以 8-bit 计算以保证精度。权重量化采用 per-channel 或 group 粒度,配 GPTQ、AWQ 等算法。核函数示意:

Requires: Triton-style DSL compiled by an NPU backend

@triton.jit

 def w4a8_gemm(x_ptr, w_ptr, o_ptr, M, N, K, BLOCK: tl.constexpr):
     # Tile the GEMM loop so tiles stay resident in on-chip SRAM
     offs_m = tl.arange(0, BLOCK)
     offs_n = tl.arange(0, BLOCK)
     acc = tl.zeros((BLOCK, BLOCK), dtype=tl.float32)
     for k in tl.range(0, K, BLOCK):
         offs_k = k + tl.arange(0, BLOCK)
         a = tl.load(x_ptr + offs_m[:, None] * K + offs_k[None, :])
         w = tl.load(w_ptr + offs_k[:, None] * N + offs_n[None, :])
         acc += tl.dot(a, w, out_dtype=tl.float32)
     tl.store(o_ptr + offs_m[:, None] * N + offs_n[None, :], acc)
  1. KV Cache 管理与运行时调度 NPU 的 KV Cache 贴近多级缓存层次:权重驻留 SRAM 时 KV 驻留 DRAM,注意力阶段按头流式搬运进 SRAM 复用。运行 时调度把融合后的计算段排成流水线,用双缓冲(double buffering)重叠 DMA 搬运与计算,并按段粒度在 NPU 与 CPU 间动态分配任务,掩盖单一单元的等待时间。
  2. 编译栈适配 主流路径是以 Triton 类 DSL(Triton、TVM TIR)编写算子,再经后端适配层降级为 NPU 指令。适配层承担三项工作: 循环规范化与分块、SRAM 分配与数据搬移插入、指令选择(intrinsic selection)。DSL 侧保持硬件无关,NPU 差异收敛 在适配层,使上层模型代码可跨硬件复用。 端侧推理与 NPU 优化构成推理体系的下游链路,其性能上限由硬件特性与编译栈共同决定,与量化、编译等主题紧密相 关。