SGLang v0.5.18 双卡升级实战:当推理框架的每一次更新都值得较真

SGLang v0.5.18 双卡升级实战:当推理框架的每一次更新都值得较真

本地部署 AI 的乐趣,一半在模型,一半在框架。这篇文章记录了一次完整的升级实战:把双卡 3090 上的 SGLang 从 v0.5.17 升到 v0.5.18,在 torch 2.13 大版本换代下保住 27B 激进量化模型的 156K 上下文,用 7 万 token 的压力测试验证稳定性,以及探索 HiCache、int8 缓存、换高精度模型三个”看上去很美”的方案后,为什么最终都放弃了。

一、为什么要追这个版本

推理框架的更新,对普通用户来说往往只是 release notes 里一行行看不懂的术语。但对把 24GB 卡用到极限的人来说,每一次版本更新都可能意味着:更快的 prefill、更大的上下文、更稳的显存管理——或者,全新的坑。
这次从 v0.5.17 升到 v0.5.18,吸引我的是这样一串关键词:
torch 2.13.0 + triton 3.7.1 + FlashInfer 0.6.17——三大底层依赖同步换代,kernel 全面重写;GDN 内核系列优化——针对 Qwen3.8 这类混合 Gated DeltaNet 架构的专项改进;Qwen3.5 MTP + HiCache 启动修复——看起来和我们的模型直接相关;startup-weight-load-mode overlap——启动提速 2.38 倍的新参数;language_model_only——跳过视觉塔加载的省显存参数。
升级的另一个现实原因:我打给上游的补丁(PR #36137)是基于 v0.5.17 的,框架一更新,补丁就可能失效。与其等出了问题再补,不如主动验证新版本下的兼容性。

二、先认识我们测试的主角

这次升级的两张卡上跑的,是 born2bewild/Qwen3.8-27B-W4A16-AutoRound-fast——一个把”省显存”做到极致的激进量化模型。
Qwen3.8-27B 本身就是 27B 级别的高能效比选手,而 born2bewild 这个量化版更狠:不仅主干用业界公认最无损的 W4A16 AutoRound 量化,连通常被”供起来”的 lm_head(INT4)和 embedding(INT8)也一起压了。整包只有 15.81GB,相比官方量化版省 3.6GB——这些省下的显存,全都变成了 KV 缓存,也就是上下文。
代价是:标准推理框架跑不起来。SGLang 不认识被量化的 embedding 和 lm_head,需要三个补丁(其中两个是给框架补能力,一个是修框架原生 bug),已提交 PR #36137。
所以这次升级有一个额外的看点:v0.5.18 会不会已经吸收了我们的补丁逻辑?

三、v0.5.18 升级实录:三个关键结论

结论一:补丁没有被”白嫖”,官方也没有吸收

升级前我做了个对比:v0.5.18 的 qwen3_5.py 里出现了 _bind_packed_weight_loaders 等看起来很像我们补丁的函数,一度以为官方吸收了我们 PR。
仔细核对后真相是:那些函数在 v0.5.17 官方里本来就有,我们补丁的核心内容(CompressedTensorsEmbeddingMethod、ParallelLMHead 量化处理)在 v0.5.18 官方代码里依然不存在
所以补丁处理方案是:compressed_tensors.py:官方 v0.5.17 = v0.5.18(md5 完全一致),原样沿用qwen3_5.py:官方 v0.5.18 有改动(新增了 GDN ratio-8、DCP kv_tp 等),基于 v0.5.18 官方版重打——实际只需要加回那一行 quant_config=quant_config(embedding 层漏传量化配置的 bug 修复)。
这是个值得记录的教训:看到”看起来像自己贡献”的代码,先确认时间线,别急着自我感动。我们的 PR #36137 至今仍 open,价值独立存在。

结论二:--startup-weight-load-mode overlap 是个陷阱

release notes 里写着这个参数能让启动提速 2.38 倍(84.8s→35.6s),我高高兴兴加进配置,结果启动直接报错:

ValueError: --startup-weight-load-mode=overlap is not supported: quantization is not supported

官方限制:量化模型不支持该参数。release notes 的启动加速数据是针对非量化模型(Qwen3-32B bf16)的。我们的模型是 compressed-tensors INT4,直接撞墙。
教训:新参数先看官方源码确认支持范围,别只看 release notes 的亮点数据

结论三:底层换代带来显存重新洗牌

升级后最意外的变化是显存占用结构变了。v0.5.17 时 GPU1 空闲约 1.5GB,v0.5.18 空闲只剩 152MB——prefill CUDA graph 捕获从 ~0.9GB 涨到 1.17GB(torch 2.13 的 graph 机制变化)。
这个差异在空闲时看不出来,一跑大输入就现原形:

torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 48.00 MiB.
GPU has 23.56 GiB total, only 39.38 MiB free.

处理 5 万 token 输入时,GDN prefill kernel 需要临时分配 48MB,但显存只剩 39MB——scheduler 崩溃,容器自动重启。

四、OOM 排查:从”受害者”到”元凶”

这个 OOM 很有意思,因为它不是传统意义的”KV 池满了”,而是 prefill 临时 workspace 挤占了最后一点余量
排查链是这样的:
一、先怀疑 KV 池:KV 池不是满的(178K 池只用了部分),排除
二、降低 context:从 172032 降到 162032,结果空闲显存确实增加了(152MB→1.7GB),但一请求又 OOM
三、定位真相:KV 池按显存预算(mem-fraction)分配,不随 context 缩小。context 降低释放的是其他空间,而请求时 KV 池被填满 + GDN prefill 临时分配叠加,还是打满
关键解法是 --max-total-tokens

--max-total-tokens 显式限制 KV 池大小,让它精确匹配 context,把多余显存释放给 prefill 临时分配
最终配置:GPU0:context 133120(130×1024)+ --max-total-tokens 133120;GPU1:context 159744(156×1024)+ --max-total-tokens 159744 + mem-fraction 0.93→0.92。
这像什么?像一个”显存预算书”——告诉框架:KV 池就这么多,剩下的都留给计算临时空间

五、压力测试:7 万 token 的考验

修复后做了系统性的压力测试,从 12K 一路顶到 72K:

输入规模 GPU0 (130K ctx) GPU1 (156K ctx) 用时
12K tokens ~5s
22K tokens ~19s
36K tokens ~25s
72K tokens ~44s

prefill 吞吐稳定在 1300-1600 token/s;显存余量稳定(GPU0 ~0.9GB / GPU1 ~0.5GB);全程无 OOM、无崩溃。

测试中还踩了个有意思的坑:命令行参数有长度限制(ARG_MAX)。用 curl -d ‘{“…50K token…”}’ 直接传会报 “参数列表过长”,一开始误以为是框架故障。正确姿势是把请求体写入文件,用 curl -d @file.json 传。这提醒我们:大上下文测试时,请求构造方式本身也可能是”假故障”来源。

六、三个”看上去很美”的方案,为什么都放弃了

1. HiCache(三级缓存,12GB 主机内存)

HiCache 是 SGLang 把 KV 缓存从 GPU 扩展到主机内存再扩展到分布式存储的三级体系。12GB 预算(官方推荐的 –hicache-ratio 2 量级)听起来很诱人。

但算完账就清醒了:GDN mamba 状态每 token 要 36.6MB,而 attention KV 只要 32KB——差了 1125 倍。12GB 主机内存按设备池字节比例分配后:attention 部分:10.56GB → 346K tokens L2 缓存(很有价值);mamba 部分:0.62GB → 仅 17 个 token 的状态(等于没有)。

Qwen3.8-27B 有 48 层线性 attention,任何前缀复用都需要完整的 mamba 状态链——L2 存不下 17 个 token 的状态,命中时还是要重算。这是架构性问题,调大内存也解决不了。

2. int8 mamba checkpoint(2 倍缓存密度)

这个参数把 radix 缓存的 mamba 状态压缩成 int8,同样显存能存 2 倍前缀状态。听起来是针对我们瓶颈的完美方案。

但实际上:int8 checkpoint pool 是额外叠加的(0.73GB),不替代 active bf16 pool。active 池(运行中请求的实时状态)仍用 bf16,int8 只用于缓存副本。24GB 卡的 headroom 本就只有 ~2.7GB,再加 0.73GB 直接压垮 CUDA graph 捕获(avail 从 2.72GB 掉到 1.91GB,graph 捕获 OOM)。

而代价是真实的:mem-fraction 每降 0.03,KV 池损失 23,568 tokens(约 16% 上下文)。

3. 换回高精度 RedHatAI 模型

这是最自然的想法:既然要压榨显存,为什么不用精度更高(lm_head 保留 BF16)的官方量化版?

算一下就知道不可行:RedHatAI 权重 ~19GB,比 born2bewild 多 4GB。24GB 卡上,这 4GB 直接挤占 KV 池预算,上下文只能到 ~53K——远达不到 156K/130K 的生产需求。

born2bewild 的激进量化(lm_head INT4 + embed INT8)正是为了在 24GB 上实现 150K+ 上下文的设计选择。在 24GB 单卡上,”模型精度”和”长上下文”是一对不可兼得的矛盾。

这三个方案都指向同一个洞察:24GB 卡的每一 MB 都是稀缺资源,任何”额外”机制(缓存、压缩池、更大模型)都要先问自己:它占用的是什么,换回来的值不值。

七、升级后的收获与稳定状态

折腾一圈,最终的双卡状态:

GPU 引擎 模型 context KV池 显存余量
GPU0 (:11434) SGLang v0.5.18 born2bewild 27B 133,120 133,120 ~0.9GB
GPU1 (:15433) SGLang v0.5.18 born2bewild 27B 159,744 159,744 ~0.5GB

实际收益:

一、框架底层换代:torch 2.13 + triton 3.7.1 + FlashInfer 0.6.17,kernel 全面升级,长期稳定性值得

二、补丁适配:与 v0.5.18 官方版对齐重打,消除了对旧版本的依赖

三、大输入稳定性:72K 输入实测通过,超出此前 20K 的验证水平 3 倍

四、显存预算透明化:–max-total-tokens 精确匹配,把”隐性抢占”变成”显式预算”

踩坑清单(下次升级直接避雷):

一、量化模型不支持 startup-weight-load-mode overlap

二、v0.5.18 的 CUDA graph 预留比 v0.5.17 大,显存余量需重新评估

三、–max-total-tokens 是解决”KV 池抢 prefill 空间”的利器

四、大请求 body 必须用 -d @file,ARG_MAX 限制是假故障源

五、判断”官方是否吸收了自己的 PR”,先看代码时间线

八、一些掏心窝的话

在 24GB 单卡上跑 27B 模型,本质上是在做”显存经济学”——每一 MB 都要算清投入产出。这次升级最大的收获,不是 v0.5.18 带来了什么新功能,而是把”显存预算”这件事做得更彻底了:KV 池精确匹配、余量显式预留、临时分配有空间。

回头再看这次放弃的三个方案(HiCache / int8 / RedHatAI),它们不是”不好”,而是不适合 24GB 这张牌桌。HiCache 对纯 dense 模型是神技,int8 对非 GDN 架构很香,RedHatAI 在大显存机器上更优——但在 24GB + GDN 混合架构的约束下,born2bewild + 精确预算就是最优解。

本地大模型部署迷人的地方就在这:没有银弹,只有算账。把每一笔账都算清楚,就是在榨干硬件的同时,保住模型的能力上限。

本文所有数据来自双 RTX 3090(24GB)实机部署实测。升级完整记录见部署文档(SGLang v0.5.18 升级 + born2bewild 部署记录,2026-08-27)。模型:huggingface.co/born2bewild/Qwen3.8-27B-W4A16-AutoRound-fast。补丁 PR:github.com/sgl-project/sglang/pull/36137。


皖ICP备2025105865号-2|皖公网安备34010402704739号