如果你最近在折腾大模型本地部署,大概率已经见过 SGLang 这个名字。我自己是从 vLLM 转过来的,最早打动我的不是它标榜的吞吐量数字,而是它对“缓存”这件事的认真程度。这篇想聊的 HiCache,就是 SGLang 缓存体系里值得单独拿出来讲清楚的一块。它解决的问题非常朴素:同样的前缀输入,为什么要一遍又一遍地重新计算?尤其当你拿 SGLang 离线部署 Qwen3-8B、跑私有知识库问答这类场景时,有没有把这个机制吃透,直接决定你在无 NVLink 的普通 PC 上能不能流畅用起来,以及对比 vLLM、Ollama、LM Studio 时到底该选谁。这篇文章不打算全面介绍 SGLang 的所有功能,只围绕 HiCache 展开,并把“无 NVLink 影响多大”这个问题一并算清楚。
1. 先搞清楚 HiCache 在解决什么问题
大模型生成每个 token 时,都要读取历史 token 的 Key 和 Value,这套 KV Cache 是 Transformer 推理加速的基石。没有它,模型每出来一个字,前面所有字都要重新算一遍,别说多轮对话,单是长文本阅读都跑不动。缓存这件事单看不算新概念,问题在于它在实际部署中很容易被用得很粗糙。
1.1 长上下文里的“重复劳动”有多夸张
假设你有一个 8K token 的 system prompt——比如一段企业知识库的规则说明,或者一份长时间跨度的数据报告。用户每问一个问题,模型都要先把这 8K 前缀完整过一遍,生成中间状态的 KV Cache。如果服务端没有任何跨请求的缓存复用,那 10 个用户同时问 10 个不同问题,这 8K 前缀就被白算 10 遍。
我实测过一个不算极端的场景:单张 RTX 4090 上跑 8B 模型,一次 8K 长度的 prefill 大约要 3 到 5 秒。如果并发 50 个携带同一长前缀的请求,光重复计算就能把整张卡的吞吐直接吃没。更烦的是,多轮对话里前面几轮的内容在每一轮都会被重新当作前缀处理,对话越长,浪费越大。H iCache 这种机制要解决的核心痛点就是这个——只要是出现过的前缀,就不许再重算。
1.2 HiCache 和传统 KV Cache 的定位差异
传统 KV Cache 是请求内部的缓存,生命周期跟单个请求绑定,请求结束就释放。它解决的是“一个请求内部不重复计算”的问题。HiCache 则跨出了一大步:它把缓存放进了跨请求、跨层级的结构里。请求 A 算过某个前缀之后,请求 B 如果带同样的前缀,可以直接从缓存里把对应的 KV block 接上,整个 prefill 阶段直接跳过去,只计算两者不同的那部分后缀。
用生活类比可能更好理解。普通 KV Cache 像是你在图书馆随手翻开一本书,看完放回去,下次再找还得重新翻目录;HiCache 则是你手里捏着一张个人化书签,还顺便把上次翻到的那几页复印件揣在兜里。读同一本书的人越多、读的次数越频繁,书签的价值就越大。
1.3 什么人应该认真读这篇文章
如果你是下面三类人之一,HiCache 相关信息值得读完并自己验证一遍:
- 在跑本地大模型服务,希望提高并发、降低首 token 延迟,而不是无脑堆显卡;
- 正在比较 SGLang、vLLM、Ollama、LM Studio 这些框架,想搞清楚“引擎不同到底差在哪”;
- 手上是几张没有 NVLink 的消费级显卡(尤其多张 4090),想知道多卡分布式推理到底能不能碰。
我后面所有论述都会落到这三类需求上,不空谈理论。
2. 分层缓存机制到底改了哪几层东西
HiCache 这个名字我理解成 Hierarchical Cache,也就是分层缓存。它在 SGLang 原有的 RadixAttention 基础上继续演进,把缓存放进了一个多级结构,而不是把所有希望都押在昂贵的 GPU 显存上。
2.1 先用 RadixAttention 打个底
SGLang 最早吸引我的一点,是它的前缀缓存算法不搞简单哈希,而是用基数树(Radix Tree)来组织共享前缀。基数树的好处是能高效处理“部分共享”的情况:两个请求的前 100 个 token 相同但后面不同,它们在树里共用一个父节点路径,各自长出分支;后续计算时,相同的 100 token 对应的 KV Cache 只要有一份就够了。
SGLang 的这套机制叫 RadixAttention,它在调度层做了很多精妙设计,比如 LRU 淘汰策略、缓存块的重组等等。以前我误以为前缀缓存就是把整段 prompt 做一次精确匹配,实践下来发现根本不够用——真实场景里请求永远是既有共享又有差异,基数树这种结构天生就是干这个的。
2.2 HiCache 的三级缓存:显存、内存、磁盘
HiCache 这层演进,粗看就是借鉴了计算机体系结构里经典的 L1/L2/主存/硬盘分层思路。它把 KV Cache 的存储层级拆分成了三层:
- GPU HBM 作为 L1,存放最热点的前缀,访问速度最快但容量最小;
- CPU DRAM 作为 L2,放一批还算常用的缓存块,显存放不下就溢到这里;
- 磁盘(NVMe/SATA SSD)作为 L3,容纳最冷门的大量历史缓存块,反正便宜、容量大。
关键点在于,它不是一个笨拙的“冷了就淘汰”逻辑,而是“冷了就先降级存放”。当某个前缀在 L1 显存里没命中,但能在 L2 或 L3 中找到时,引擎会把对应的缓存块异步换回显存,然后再做增量计算,而不是整段重新 prefill。这个设计思路说白了就是:宁可多花一次换入换出的 I/O,也绝不返工重算一整段几百上千的 token。
2.3 命中率怎么影响真实吞吐
我拿自己的机器做过一组对比试验,环境是单张 RTX 4090 24G、Qwen3-8B、静态显存占比设为 0.85、最大上下文 32K、并发 32。所有结果只代表我个人环境,但趋势很有参考意义。
第一组场景是纯粹的短请求,每个 prompt 之间没有任何共享前缀。此时缓存几乎帮不上忙,实测吞吐大约在 1300 tokens/s 上下,这基本就是原始算力上限。
第二组场景是所有请求共享一段 4K 的 system prompt。开了默认的 RadixAttention 之后,TTFT(首 token 延迟)肉眼可见地降低,整体吞吐爬到了 1600 tokens/s 左右。
第三组场景最接近生产环境:所有请求共享一段 12K 的文档前缀,同时并发更高。只靠 GPU 显存缓存时,显存很快被文档的 KV Cache 占满,并发一高就开始抖动。启用了分层缓存之后,一部分冷数据下沉到内存和磁盘,GPU 显存被释放出来,实际吞吐从 1400 出头拉到了接近 2100,而且 32K 长上下文的请求不再容易中途 OOM。
命中率每提高 10 个百分点,收益都不是线性的。因为缓存命中的不仅是一段计算量,还连带减少了显存占用,给更大的并发 batch 腾出了空间。推理吞吐量和显存使用率往往是联动的,HiCache 的高明之处就在于它同时优化了这两个变量。
2.4 值得关注的启动参数
SGLang 的缓存机制在不同版本里的实现细节和参数名一直在变,我下面列的是我当时用的版本,具体以你手上的版本文档为准。但有几个方向是通用的:
- 默认 Radix Cache 是开启的,不需要额外设置;如果你发现日志里有一堆重新 prefill,先怀疑是不是有人显式加了禁用参数;
- 给 CPU 内存或磁盘分配缓存空间时,需要关注启动命令里的内存分配参数,不要图省事全部丢给 GPU,尤其在长上下文场景;
- 日志和 metrics 接口里可以观察到缓存命中率,这是调优的第一手依据。
参数的精确值不是重点,重点是你要清楚:SGLang 不是只能把缓存放显存,它能往内存和磁盘溢写,而 HiCache 往前走的这一步,把溢写变成了一种更聪明的分层策略。
3. 没有 NVLink 的机器上折腾 SGLang,影响到底多大
这个问题在相关讨论里被反复提起,非常真实。因为很多人在本地玩模型用的是两张 RTX 4090,而 4090 这代消费卡直接被砍掉了 NVLink 支持,两张卡之间只有 PCIe 通道可走。在这个问题上我踩过很多次坑,直接把结论和账目都摆出来。
3.1 先算一笔通信账
NVLink 和 PCIe 的带宽差距不是一倍两倍,是数量级的差距。A100 的 NVLink 单向带宽大约 600GB/s,H100 能到 900GB/s;而 PCIe 4.0 x16 两卡之间实测有效带宽也就 20 到 30GB/s,PCIe 5.0 x16 大概能到 50 到 60GB/s。也就是说,走 PCIe 通信比走 NVLink 慢了至少 20 到 30 倍。
SGLang 采用张量并行(TP)时,模型权重被切到多张卡上,每一层 Transformer 的前向计算里,Attention 和 MLP 都要做跨卡同步,通常是 all-reduce 或 all-gather。我做了一个很粗的估算:假设 8B 模型 hidden size 4096、层数 32,BF16 精度,单请求 1024 tokens,TP=2 时,每层做过一次跨卡同步大约要传 1024×4096×2 字节等于 8MB 的数据。一个请求前向过 32 层,每次至少两三次同步,累计下来跨卡通信量在 0.5GB 以上。PCIe 带宽按 25GB/s 算,光通信就要 20 到 50 毫秒;而在 NVLink 上,这个量级在 1 到 2 毫秒内就能完成。
3.2 哪些环节会被无 NVLink 放大
不是所有操作都会被通信瓶颈拖垮,但下面几个场景确实非常难受:
- 长输入的 prefill 阶段。通信量随 sequence length 线性增长,上下文越长,PCIe 短板越刺眼;
- 小 batch 请求。通信延迟没法被大量计算掩盖,就像高速收费站,车少的时候每辆车过闸的固定时间才是关键;
- decode 阶段虽然每次只生成一个 token,但每生成一个 token 都要跨卡同步一次,虽然单次数据量不大,累积起来也拖慢生成速度;
- 缓存恢复。TP 模式下,某一张卡要读取的缓存块可能恰好分布在另一张卡的显存里,无 NVLink 时这个跨卡读取同样走 PCIe。
3.3 我实测下来的感受
我自己有两套配置:单张 4090,以及两张 4090 走 PCIe 互联。在两个环境跑同一个 Qwen3-8B,结论非常明确:单卡能放下模型和 KV Cache 的,绝对不要用双卡 TP。单卡下无 NVLink 的影响为零,因为根本没有跨卡通信这回事。
模型确实大到单卡放不下时,我的第一选择不是 TP,而是数据并行(DP)。两张卡各跑一个完整的服务实例,外面用负载均衡把请求分开发到两张卡上,两张卡之间完全不通信。吞吐量几乎线性增长,唯一丢失的是单请求级别的显存容量上限,但这在大多数 8B、14B 甚至 32B 量化模型场景下完全可接受。
必须上 TP 的场景,我会尽量把 batch 做大。batch 越大,单次通信里摊掉的计算越多,PCIe 短板的影响越小。小并发、长上下文的组合对无 NVLink 的 TP 最不友好,尽量避免。
3.4 给无 NVLink 用户的操作建议
总结成四条,可以直接抄:
- 模型单卡能放,就单卡跑,别创造机会让通信成为瓶颈;
- 非要多卡扩展吞吐,优先数据并行,而不是张量并行;
- 只有模型超单卡显存才考虑 TP,此时把 batch 和并发做上去,别拿短请求打长文本;
- 长上下文场景善用分层缓存,把一部分 KV Cache 溢写到内存和磁盘,比硬件互联升级便宜得多。
我后来把主力方案定为“单卡模型 + 分层缓存 + 数据并行扩展”,这套组合在无 NVLink 环境里非常稳。
4. 不吹不黑:SGLang、vLLM、Ollama、LM Studio 怎么选
这个话题的讨论度一直很高,尤其很多刚入门的用户在四个选项面前容易蒙圈。我的观点是:它们不是同类东西,硬比梯度意义不大。关键是理解各自定位然后按场景选。
4.1 四个工具各自的性格
Ollama 像傻瓜相机,装好就能拍,内置量化模型库,一条命令拉模型一条命令启动,特别适合第一次接触本地大模型的人。它的缺点是灵活性差,高级推理参数能动的少,缓存机制也比较基础,生产环境几乎没人拿它当服务后端。
LM Studio 是带了一个漂亮屏幕的傻瓜相机,图形界面做得非常讨喜,适合桌面用户一边试模型一边看参数,下载管理、模型切换都顺手。它本质上也不是高性能服务引擎,不适合高并发调用。
vLLM 是专业单反,生态成熟,OpenAI 兼容接口做得好,量化支持广,在工业界有大量验证。它的前缀缓存主要走 PageAttention 路线,虽然也能复用共享前缀,但在非常规前缀结构和长上下文场景下,调度灵活性和缓存命中率和 SGLang 相比还是有差距。
SGLang 是带了电脑控制的专业机,学习曲线更陡,配置更细,但换来的是更激进的调度策略、RadixAttention 这类缓存结构,以及在 HiCache 分层缓存上的演进。它现在也支持多模态,结构化输出约束做得尤其好。
4.2 一张表看懂差异
| 维度 | SGLang | vLLM | Ollama | LM Studio |
|---|---|---|---|---|
| 定位 | 高性能推理服务 | 高性能推理服务 | 本地快速使用 | 桌面图形化使用 |
| 缓存机制 | RadixAttention + 分层缓存演进 | PageAttention,前缀复用有限 | 基础 KV Cache | 基础 KV Cache |
| OpenAI 兼容 API | 支持 | 支持 | 支持 | 支持 |
| 结构化输出约束 | 强 | 一般 | 弱 | 弱 |
| 多模态支持 | 原生支持 | 部分支持 | 部分支持 | 部分支持 |
| 部署复杂度 | 较高 | 中 | 低 | 低 |
| 典型场景 | 生产服务、共享长前缀、高并发 | 生产服务、生态成熟 | 个人初体验 | 桌面试模型 |
4.3 我给不同人群的结论
第一次玩本地模型,先用 Ollama 或 LM Studio,别一上来就折腾 SGLang,没必要。等你有明确性能需求,比如同时服务几十个人、跑长文档知识库、要求 JSON 结构化输出,再认真评估 SGLang 和 vLLM。
两者怎么选?我的倾向是:新项目、长上下文密集、前缀共享明显的场景,SGLang 值得优先验证,HiCache 这类分层缓存带来的实际收益很容易测出来;老项目已经在 vLLM 上稳定跑着,也没必要为了追新而迁移,线上稳定比什么都重要。
5. 离线部署 Qwen3-8B 的完整实操记录
接下来分享一份可以直接照着做的流程。机器环境是 Ubuntu 22.04、单张 RTX 4090、CUDA 12.x,目的是完全离线跑通 Qwen3-8B,并验证缓存命中带来的速度差异。
5.1 环境准备与安装
我建议用 conda 单独建一个环境,避免污染系统 Python。SGLang 对 PyTorch 版本有要求,最好是匹配官方验证过的组合,省得后面出现莫名其妙的兼容问题。
conda create -n sglang python=3.10 -y conda activate sglang pip install --upgrade pip pip install "sglang[all]"安装完成后可以执行python -c "import sglang; print(sglang.__version__)"确认。如果安装很慢,多换几次源,不要死磕一个节点。
5.2 先离线把模型文件备好
离线部署最关键的一步:先把模型完整下载到本地。千万别在有网环境里直接指向远程仓库路径,然后期望着到离线环境里还能加载,大概率会卡在联网检查上。
我用的是 ModelScope 的下载命令,速度对国内网络很友好,一条命令拉到本地目录:
pip install modelscope modelscope download --model Qwen/Qwen3-8B --local_dir /data/models/Qwen3-8B下载完检查目录里应该有config.json、model.safetensors分片文件、tokenizer.json、tokenizer_config.json这些关键文件。Qwen3-8B 的 BF16 权重大约 16GB,单张 24GB 显存的 4090 完全能装下,扣除权重后还剩约 7GB 给 KV Cache 使用。
5.3 启动 server 的完整命令
离线环境下启动服务,关键是指定本地模型路径,并明确上下文长度和显存占用比例。我常用的命令长这样:
python -m sglang.launch_server \ --model-path /data/models/Qwen3-8B \ --host 0.0.0.0 \ --port 30000 \ --mem-fraction-static 0.85 \ --context-length 32768 \ --tp-size 1几个参数分别说明一下:
--mem-fraction-static:给权重和静态图预留的显存比例,剩下的是 KV Cache 增量空间。0.85 对单卡部署比较稳,太高容易在长上下文时 OOM;--context-length:最大上下文长度,Qwen3-8B 基础版是 32K 附近,超长需求要配合推理扩展手段处理;--tp-size:张量并行卡数,单卡就写 1,这直接规避了上一章说的无 NVLink 通信问题。
启动成功的标志是日志里出现类似“Server is ready”且没有显存分配失败的报错。
5.4 客户端调用与缓存验证
SGLang 提供 OpenAI 兼容接口,用起来非常顺。离线环境里直接用本地地址调用:
from openai import OpenAI client = OpenAI(base_url="http://localhost:30000/v1", api_key="EMPTY") resp = client.chat.completions.create( model="Qwen3-8B", messages=[{"role": "user", "content": "请用一句话介绍你自己"}], ) print(resp.choices[0].message.content)这里我想额外强调一个很有用的缓存验证方法。先准备一段比较长的 system prompt,比如一份 2000 字的知识库规则说明,然后连续发起两次完全相同的请求。第一次因为冷启动,TTFT 会有明显耗时;第二次如果命中了前缀缓存,TTFT 会大幅下降,这个时间差就是缓存复用的直接证据。
更系统一点的做法是访问http://localhost:30000/metrics,在 Prometheus 格式的输出里找缓存相关的指标,观察命中次数和不命中次数。我习惯先做一轮基线请求,再修改 system prompt 里一个字符对比指标变化,能直观看出前缀共享的边界。
5.5 我踩过的几个坑
离线部署总有几个坑是文档里不会写的,我逐个说:
第一,显存分配比例设置贪心。我一开始把--mem-fraction-static调到 0.95,启动确实很快,但一跑到长上下文直接 OOM。后来回到 0.85,损失一点峰值吞吐,换来整个服务的稳定性。KV Cache 是动态分配的,必须给增量留足空间。
第二,本地模型目录缺文件。有一次我只拷贝了权重文件,忘掉tokenizer_config.json,启动后模型能加载,但聊天模板完全错乱,生成了一堆奇怪格式的回复。文件完整性检查别省。
第三,版本错配问题。SGLang 和 transformers 的版本如果没对齐,可能出现一个请求发过去,服务端报一堆和缓存复用相关的内部错误,看起来像是缓存问题,其实是版本不兼容。遇到诡异问题先查版本,再查缓存。
第四,Qwen3 的 chat template 在部分旧版本里需要显式指定。如果回复风格不对或打印了原始模板占位符,大概率是模板没配对,启动参数里加对应模板名就行。
写在最后
这套方案跑通之后,我最大的感受是:缓存机制用得好,可能比多买一张显卡还值。以前总觉得没 NVLink 就玩不了大模型多卡,后来发现靠数据并行加分层缓存,消费级显卡也能把 Qwen3-8B 这类模型服务得挺体面。如果你手头正好也是无 NVLink 的机器,先别急着换硬件,照着上面的思路把缓存和并行策略调一调,说不定性能提升比预期大得多。