跳到正文
vLLM Blog· vLLM Team·· 27 天前精选AI 评分60

vLLM 为 GLM 5.3 引入 Hybrid HiSparse 混合稀疏卸载

GLM 5.3 Optimizations, Part 1: Hybrid HiSparse Offloading in vLLM

AI 导读

vLLM 团队发布 Hybrid HiSparse,在单台 8× H200 节点上让 GLM 5.3 支持完整的 100 万上下文长度,此前该硬件无法做到,并在各上下文长度下取得更高并发。

推荐理由

vLLM 团队公开了 GLM 5.3 在单节点上的稀疏卸载方案与复现命令,可据此评估长上下文并发部署的取舍。

正文 · AI 翻译

TL;DR: vLLM 的使命是让推理服务更快、更便宜。在这个由两部分组成的系列中,我们介绍了为实现这一目标而针对 GLM 5.3 引入的新优化:在第 1 部分中,我们展示了 Hybrid HiSparse 如何协助在单个 8× H200 节点上进行聚合部署,而该节点对于这种规模的模型来说内存紧张。Hybrid HiSparse 使得能够在完整的 100 万上下文长度下运行 GLM 5.3——这在此硬件上以前是不可能的——并在不同上下文长度下实现了显著更高的并发度。

在需要时利用稀疏性

Agentic 工作负载的特点是大量并发请求,每个请求都有不断增长的长期上下文。由于 GPU 块池是固定的,KV 缓存最终会耗尽空间,无法为并发请求分配新块。

到目前为止,解决这个问题主要有两种选择,各有其权衡:

  • 抢占选择一个请求,丢弃其 KV 缓存,稍后重新预填充。该请求在每次驱逐时都要再次支付完整的 TTFT。
  • 卸载将块移到主机内存,但密集注意力要求每个 token 都驻留在 GPU 上,因此并发请求的数量仍然受限于 GPU 内存。

对于稀疏 MLA KV 缓存,索引器选择 top-K token 并仅关注这些 token。HiSparse 利用这一行为,将所有 KV 缓存——除了这些选定的 token——卸载到 CPU,从而为每个请求所需的 GPU 内存设定了有效的上限。索引器 KV 保持驻留在 GPU 上,并且仍随上下文长度增长,但总体上要小得多,而且 GLM 5.3 的 IndexShare 意味着每四个稀疏 MLA 层只有一个索引器层。

我们引入了 Hybrid HiSparse,它还会在 GPU 上有足够容量时继续将 KV 缓存保留在 GPU 上。只有当 KV 缓存面临压力时,我们才会应用上述 HiSparse 卸载机制。热缓冲区页面按 token 索引;因此,一个页面可以容纳来自许多不同 CPU 块的 token,从而在广泛的上下文范围内实现缩减。这样,Hybrid HiSparse 仅在系统面临 KV 缓存压力(即更高并发度)时才支付 CPU-GPU 内存传输的成本。

One pool, two growing requests: preempt vs offload
一个池,两个不断增长的请求:抢占 vs 卸载
抢占:B 的槽位被释放,其 KV 消失。传统卸载:B 的 KV 在主机上存活,我们不需要重新预填充,但 B 仍然无法运行,直到所有内容再次适合 GPU,因此 A 独自解码。混合稀疏卸载:每个请求就地释放其最冷的页面,相同的槽位被重新租用为新的尾部和热页面,两者都继续解码。

只有 Hybrid HiSparse 能让两个请求都继续解码。热页面与 KV 页面从同一个块池中租用,更重要的是,它们位于同一个 KV 缓存张量中,因此对稀疏 MLA 内核来说它们看起来像普通页面。混合稀疏的独特之处在于,一些 token 可以存在于热缓冲区中,而一些 token 仍然可以存在于 GPU 驻留页面中,从而减少了 CPU 重新加载的量。

工作原理

Three KV residency states over one shared GPU block pool
一个共享 GPU 块池上的三种 KV 驻留状态
每个面板中都圈出了相同的六个 top-K token;只有它们的驻留状态发生变化。实线箭头:未命中,将一行复制到热页面。虚线箭头:热命中,无需复制即可重用。

驻留状态按页面跟踪,因此请求会随着压力升降在三种状态之间移动:

  • 完全驻留:所有稀疏 MLA KV 都保留在 GPU 上,同时已完成的前缀页会主动物化到主机内存中。
  • 混合驻留:请求的尾部留在 GPU 上,较旧的页只存在于 CPU 内存中,而索引器想从这些页中获取的行则位于热缓冲区中。块表同时保存真实块和空占位符,且尾部永远不会被驱逐。一个融合内核解析 top-K:驻留 token 就地读取,热 token 被读取并刷新其 LRU 条目,未命中则从固定主机内存复制单行到 LRU 槽位。解码路径上没有任何环节等待 CPU 决策,因此它保持可被 CUDA 图捕获。
  • 无驻留:一个复用仅存在于 CPU 内存中的前缀的新请求,会以占位符和一个热页开始。行在索引器选择它们时到达,因此我们只为模型实际关注的部分付出代价,而不是整个历史。

这三种状态都能工作,因为热缓冲区不是单独分配。热缓冲区页是普通的 KV 缓存块,通过 vLLM 的 Hybrid Memory Allocator 从与驻留页相同的池中租用,在请求首次需要时获取,不需要时归还。无论是位于驻留页还是热缓冲区中的行,解析器都会传递 HMA 行 ID,HMA 以单一步幅收集它们。一个请求释放的块可以成为另一个请求的热缓冲区容量。

HiSparse 在压力到来之前就做好准备。当一个可缓存的前缀页完成时,HiSparse 会排队将其复制到 CPU 内存,同时继续从 GPU 提供该页。如果 GPU 缓存之后被填满,该页可以释放其 GPU 槽位而无需再次复制。即使压力先到达较新的页,其 GPU 槽位也会在复制排队后立即可复用,而 CPU 副本在传输完成后即可用于前缀复用。

hisparse-glm 分支通过在前向传播后一次启动中复制所有稀疏 MLA 层,使这条路径保持轻量。复制在模型的 GPU 流上排序,从而保持同步简单且安全。

与 vLLM 其余部分组合

Hybrid HiSparse 是共享 HMA 池上的一种驻留策略,也是 vLLM 其他 KV 机制旁边的一个连接器,因此堆栈的其余部分照常工作。其他缓存组仍使用正常的前缀缓存、传输和卸载,尤其是索引器 KV 不受 HiSparse 影响:标准的 OffloadingConnector 可以以普通块粒度存储独立卸载它。来自 P/D 分离的导入可以在前缀无法驻留时落到主机侧,而推测解码通过每步可重放的解析器计划工作,这些计划共享请求的热状态。

热缓冲区默认每个请求为 2 倍 top-K 行,这确保了高命中率,同时保持缓冲区大小较小。由于 MLA KV 在各 TP rank 上相同,固定主机池按 DP 副本分配,并在其本地 TP rank 之间共享。TP rank 0 写入共享副本,每个 rank 都可以读取它,CUDA 事件保持流顺序。

数据

我们在 8× H200 上使用 OpenHands 多轮智能体工作负载对 GLM 5.3 进行了基准测试(来源):13 轮对话,首轮 74,160 个 token,后续轮次 753 个 token,输出固定为 220 个 token。两个 TP8 部署均使用了 MTP3、FP8 KV 缓存、142K 准入限制、max_num_batched_tokens=32768、max_num_seqs=256 和 gpu_memory_utilization=0.92。卸载基线使用了 512 GiB 卸载池;Hybrid HiSparse 将相同的主机预算拆分为 384 GiB 的 HiSparse 池和 128 GiB 的卸载。

GLM 5.3 interactivity-throughput Pareto and measured concurrent running requests for Hybrid HiSparse and KV offloading
GLM 5.3 交互性-吞吐量帕累托图,以及 Hybrid HiSparse 和 KV 卸载的实测并发运行请求数
上:交互性-吞吐量扫描。交互性为 1000 除以平均 TPOT;逻辑总 token 吞吐量包含前缀缓存的提示 token,并除以八块 GPU。下:每个基准测试点采集的平均非零 vllm:num_requests_running 样本数。Hybrid HiSparse:e8ef1e07bd。卸载基线:80cb71c9ff。

我们计划在 vLLM v0.30 中广泛提供 Hybrid HiSparse。与此同时,用于获得这些结果的确切启动命令和基准测试客户端设置见下方的复现附录。

只在需要的地方卸载

Hybrid HiSparse 只在需要的地方进行卸载。KV 从 GPU 上开始,只要还有空间就留在那里,然后在池空间不足时逐页放弃驻留。热缓冲区和驻留页共享池和 tensor,因此处于压力下的请求会以部分驻留状态继续解码,而不是等待槽位释放或付出代价重新预填充自身。

估算你的配置能获得的收益

下面的计算器使用相同的可用 HBM 来估算普通 GPU 驻留 KV 和混合稀疏卸载容量。调整工作负载、GPU、并行度、热缓冲区和主机池,以近似一个部署。调整这些值可以感受并发数可能的提升。

该计算器显示保持 CPU 内存不限制 GPU 侧索引器和热缓冲区所能维持的并发数所需的最小 HiSparse 主机池。原生索引器卸载被建模为单独的总 CPU 池:它扩展了前缀缓存,但活跃索引器历史仍消耗 HBM,因此仍属于运行请求限制的一部分。该图比较了在假设 HiSparse 主机容量不受限的情况下,不同序列长度下的总 HiSparse 和普通 GPU 驻留并发数。热缓冲区为每个请求增加固定的 GPU 开销,因此在短上下文下普通驻留可以容纳更多请求;在更长上下文下,限制稀疏 MLA 驻留使 HiSparse 能够维持更多并发请求。增加热缓冲区会以部分容量换取更大的热缓存覆盖范围。

注意:这些是规划估算,并非保证的服务限制:运行时工作区、请求长度偏斜和调度行为可能会降低实际达到的并发数。

注意:MTP 可能进一步限制并发数,因为其热缓冲区必须一次性容纳所有验证 token。在发布时,这意味着每个热缓冲区的大小需设为 (num_speculative_tokens + 2) × top-K。随着我们努力缩小缓冲区,这一点可能会改变。目前下面的计算器未考虑这一点,因为我们计划放宽此约束。

全屏打开并发计算器

第 2 部分

这是关于使用 vLLM 服务 GLM 5.3 系列文章的第一篇。混合 HiSparse 在 P/D 部署的解码侧最为重要,因为那里的上下文最长,KV 压力也最高。在第 2 部分中,我们将在大规模部署中把这些部分整合起来,结合新的和现有的优化:预填充上下文并行(PCP)、解码上下文并行(DCP)、自适应验证以及混合 HiSparse。

致谢

vLLM 的混合 HiSparse 实现由 Matthew Bonanni(Red Hat)、Lucas Wilkinson(Red Hat)和 Fares Obeid(Prime Intellect)开发。该设计是在与 Chao Lei(蚂蚁集团)和 Nicolò Lucchesi(Mistral)的密切合作中形成的。Simon Veitner(Red Hat)为本博客的性能评估和开发做出了贡献。我们感谢 HiSparse 的作者们开发了本工作中所采用的稀疏卸载概念。

附录:复现我们的结果

上述结果使用 vLLM e8ef1e07bd。我们计划在 vLLM v0.30 中广泛提供混合 HiSparse;在此之前,请构建上述固定版本的检出。使用以下命令在一个 8× H200 节点上启动混合 HiSparse 配置:

vllm serve zai-org/GLM-5.3 \
  --served-model-name glm-agentx \
  --trust-remote-code \
  --host 0.0.0.0 \
  --port 8000 \
  --tensor-parallel-size 8 \
  --kv-cache-dtype fp8 \
  --gpu-memory-utilization 0.92 \
  --max-model-len 142000 \
  --max-num-batched-tokens 32768 \
  --max-num-seqs 256 \
  --enable-prefix-caching \
  --attention-config '{"hisparse_config":{"host_pool_gib":384}}' \
  --kv-transfer-config '{"kv_connector":"OffloadingConnector","kv_role":"kv_both","kv_connector_extra_config":{"spec_name":"TieringOffloadingSpec","cpu_bytes_to_use":137438953472}}' \
  --speculative-config '{"method":"mtp","num_speculative_tokens":3}' \
  --enable-auto-tool-choice \
  --tool-call-parser glm47 \
  --reasoning-parser glm45

host_pool_gib 按每个 DP 副本计算,并向上取整到整个主机块。128 GiB 的卸载池存储 HiSparse 不管理的缓存组,包括索引器 KV。要在不使用 MTP 的情况下复现 HiSparse,请省略 --speculative-config。对于图中所示的无 HiSparse 的 MTP3 基线,保留 --speculative-config,省略 --attention-config,并将 cpu_bytes_to_use 改为 549755813888(512 GiB)。对于无 MTP 基线,同时省略 HiSparse 和 --speculative-config。HiSparse 目前仅针对 NVIDIA GPU 实现。

复现填充后的 OpenHands 扫描

基准测试客户端所需的一切都随本博客一起提供,因此该配方是自包含的:build_openhands_padded_dataset.py、install_evalscope_deps.sh 和 evalscope-all-nodeps.txt。将这三者下载到同一个目录中。EvalScope 固定为 acd09b44384d53174768bb1063f675420f76fae9。以下内容构建确定性的 128 轮对话数据集,然后在每个点使用全新对话运行 c1/c8/c16/c24/c32:

python3.12 -m venv client-venv
source client-venv/bin/activate
bash install_evalscope_deps.sh
pip install 'modelscope[datasets]==1.34.0' 'lxml==6.0.2'
pip install 'evalscope[perf] @ git+https://github.com/modelscope/evalscope.git@acd09b44384d53174768bb1063f675420f76fae9'
 
python build_openhands_padded_dataset.py \
  --model zai-org/GLM-5.3 \
  --pad-source openscience \
  --first-turn-length 74160 \
  --subsequent-turn-length 753 \
  --num-turns 13 \
  --number 128 \
  --output-path openhand-zai-org-GLM-5.3.json
 
evalscope perf \
  --model glm-agentx \
  --url http://127.0.0.1:8000/v1/chat/completions \
  --api openai \
  --dataset swe_smith \
  --dataset-path openhand-zai-org-GLM-5.3.json \
  --dataset-offset 52 \
  --max-tokens 220 \
  --multi-turn \
  --number 4 16 32 48 64 \
  --parallel 1 8 16 24 32 \
  --extra-args '{"ignore_eos":true}' \
  --name tp8-hisparse384-native128 \
  --outputs-dir results \
  --no-timestamp

对于该图,交互性为 1000 / mean_TPOT_ms;每 GPU 的逻辑总 token 吞吐量为 EvalScope 的总 token 吞吐量除以八。我们在每个点每 30 秒抓取一次 /metrics。请求占用率为非零 vllm:num_requests_running 样本的均值,MTP 接受长度为 1 + Δ(vllm:spec_decode_num_accepted_tokens_total) / Δ(vllm:spec_decode_num_drafts_total)。

来源:vLLM Blog · vllm.ai