你可能听过一个直觉:C++ 比 Python 快很多倍,所以 llama.cpp(C++ 写的)应该比 vLLM(Python 写的)快很多倍。实测打脸——同一个模型同一张卡,vLLM 在不少场景下比 llama.cpp 还快。这就是 Caleb Writes Code 的这期 15 分钟视频 想解释的事,我把它的脉络重新整理一遍。
你下载下来的不是程序,是食谱
从 Hugging Face 拖一个模型回来,你拿到的不是一个能双击运行的可执行文件,而是一堆 artifact。一份完整的 Gemma 4 会包含:
model.safetensors(最大的一个,15 GB 左右):权重本体,里面其实是一个超大的 JSON 把所有权重码进去。config.json:模型架构定义。attention 头数、有几层、用哪种 attention 机制、词表大小,全在这一份。- 各种 tokenizer 文件、index 文件等等。
这些东西躺在 SSD 里,你需要一个推理引擎(inference engine)把它们按正确顺序搬进 RAM 和 GPU 显存。视频里打了个比方:你拿到的是配料和食谱,不同的厨师做出来的菜不一样。llama.cpp 是 C++ 厨师,vLLM 和 TGI 是 Python 厨师,TensorRT-LLM 和 TGI 是 Rust + C++ + Python 混合厨师。
推理流程实际是三段:Load(加载)→ Prefill + Decode(预填和解码)→ Serving(服务)。这期视频只讲第一段——光是加载就已经是一篇文章的密度了。
mmap:加载阶段的核心招式
把 15 GB 的权重从 SSD 搬进 RAM,最朴素的做法是「先复制一份到临时缓冲区,再复制到正式位置」。结果是 RAM 里会同时存在两份权重,临时那份等会被回收,但你已经把 RAM 占满了。
llama.cpp 这类引擎用一个叫 mmap(memory map,内存映射) 的技巧绕过这一步。思路简单到一句话能说完:
让操作系统替你管权重的内存位置——SSD 上存着真身,RAM 里挂个映射;推理引擎用到哪段就懒加载哪段,不用的时候由操作系统决定可不可以从 RAM 里 evict(淘汰)出去。
这个设计的好处不只是省 RAM。算一笔账:15 GB 模型放进 32 GB RAM 后,假设 5% 的权重被 evict 掉了(约 750 MB),下次需要时要从 SSD 重新读回来。PCIe 4 NVMe 的带宽大约 7 GB/s,750 MB 重新读回来的延迟大约 107 ms。这点延迟换来「不把整机内存吃光」的代价是划算的。
视频作者用 llama.cpp 加载一个量化过的中等模型,10 秒内就能吐出第一个 token。vLLM 也默认支持 mmap,但启动要好几分钟。慢的不是 mmap,是 vLLM 在启动时要做一大堆调度初始化和模型编译,为后面的并发 serving 攒能力。这是一个用启动时间换调度能力的取舍,等到 serving 阶段你就会理解为什么这么贵。
量化是工程,不只是压缩
模型权重默认是 BF16(16 位浮点),15 GB 那个 safetensors 文件就是这么大的根源。量化要做的事情大概类似于把 4K 画质压成 1080p,再尽量还原回 4K 不掉细节。问题是怎么分组、怎么取范围、怎么决定哪些权重值得多保留几个比特。
视频按由浅入深的顺序讲了五种思路。
1. 标准量化(RTN,round to nearest)
最朴素:把整个 tensor 或者按 channel/分组,把权重从 BF16 直接降到 INT8 或 INT4。
举个例子。INT4 只能存 16 个值(-8 到 7),那 0.9124、1.31、6.34、3.32、5.4 这一组怎么塞进 INT4?找出最大值 6.34,假设范围是 ±6.34,把所有值按这个尺度归一化再四舍五入。这叫对称量化。也可以用最小值 0.91 和最大值 6.34 形成一个非对称范围,这叫非对称量化。
GGML 格式里的 Q4_0 对应对称(只存一个 scale),Q4_1 对应非对称(存 scale + bias 两个值)。
2. K-quants:分层 + 混合精度
RTN 的问题是组内统一拉同一个尺度,组里有个别 outlier 会把其他值的精度全压垮。K-quants(GGML 的 Q4_KS、Q4_KM)的招式是两层:32 个权重作为一个小组,8 个小组(256 个权重)作为一个大组,给每个层级各算一个 scale。局部 outlier 就不会污染全局。
混合精度是另一个加分项。模型不同部位对量化敏感度不一样:embedding、attention 头、feedforward 网络、归一化层,每一层的”承压能力”不同。Q4_KS 把大多数层量化到 4 bit,只让 normalization 留高精度;Q4_KM 进一步把 output projection 和 FFN gate 拉到 6 bit。Hugging Face 上同一个模型有一长串 GGUF 变体,每个变体对应”哪些层省、哪些层不省”的不同取舍。
3. AWQ:先找重要的权重再量化
AWQ(Activation-aware Weight Quantization)换了个角度:先用一个校准数据集跑一遍模型,看哪些权重对应的激活值幅度最大——这些叫 salient weights(显著权重)。量化时先单独把这些权重 scale 一下,让它们在 INT4 里少损失精度。
K-quants 是按”位置”猜哪些层敏感,AWQ 是按”实际激活”测哪些权重重要。后者更准。
4. EXL2:同样先找显著权重,但策略不同
EXL2 也找显著权重,但走了另一条路:不去 scale 它们,给它们更高的存储精度。重要的权重存 4-6 bit,不重要的存 2-3 bit。判定敏感度用的是 Hessian 矩阵(损失对权重的二阶偏导,高中学过的求二阶导那个概念)。
视频里给的 Llama 2 13B 对比里,EXL2 在 tokens/秒上是最快的,perplexity(困惑度,模型对文本的不确定度,越低越好)也最低,压缩率和别的方法接近。
5. FP8、NVFP4:把硬件拉进来
前面四种都是软件层面的量化。FP8 和 NVFP4 不一样——它们要求 GPU 张量核心原生支持低精度计算,不再走”INT4 存储、计算时还要反量化回 FP16″的弯路。
代价是绑硬件:FP8 要 NVIDIA Hopper 架构(H100 这一代),NVFP4 要 Blackwell(B100/B200 那一代)。买不起的人没资格用。
反直觉:为什么 GGUF 还是最流行
EXL2 跑得更快、精度更高,但 Hugging Face 上各家发的本地模型还是 GGUF 占多数。原因不在技术,在硬件预算。
视频里点了一句:大部分本地跑模型的人显存不够。消费级显卡发烧友撑死 32 GB,资深玩家上多卡能凑 60-70 GB,但模型动辄 30B 起步——所以你需要的是「能在 RAM 和 GPU 显存之间灵活 offload」的格式。llama.cpp + GGUF 在这方面做得最完整,CPU 和 GPU 之间的 bunk bed(上下铺)切换是它的强项。EXL2 速度好看,但更依赖 GPU 全量驻留。
我的补充:和 wiki 里两条旧笔记接上
我在自己的工作笔记里翻了一下,关于 LLM 部署的痛点过去出现过两次。
一次是 Hermes Agent 白皮书里讲 Ollama 部署调参:OLLAMA_NUM_PARALLEL=4、OLLAMA_MAX_LOADED_MODELS=1。当时只记了”为了不让聊天模型和嵌入模型在 GPU 上抢资源”,没写为什么是这两个值。看完这期视频明白了——这些参数控制的就是 batching 大小(影响 prefill 阶段的吞吐)和模型驻留策略(影响 mmap evict 的频率)。Caleb 说的”vLLM 启动慢是为了换并发”,和 Ollama 调这两个参数是同一件事的两个侧面。
另一次是”本周 GitHub 热门项目”里提到 Claude Code 路由器把请求转发到 llama.cpp 等本地后端。当时只是把 llama.cpp 当一个能跑的”本地模型容器”。现在回看,选 llama.cpp 还是选 vLLM 实际上是在选内存模型 + 量化策略 + 调度算法的组合包:llama.cpp = mmap 友好 + GGUF + 低并发,vLLM = 强调度 + 多种量化 + 高并发。
如果你只在自己的笔记本里跑 7B 玩玩,llama.cpp 几乎是默认选择。如果你要给一个有真实并发的服务做 backend,vLLM 那几分钟启动是必要的成本。
一个值得记住的点
视频后半段没明说但反复暗示了一个判断:inference engine 的瓶颈很少在编程语言,几乎都在内存怎么搬、权重怎么压、请求怎么调度上面。这三层都是工程取舍题,没有”哪个最好”的标准答案。
下次有人在朋友圈说”Python 太慢了,我们换 Rust 重写推理”,可以问他一句:你瓶颈在 Python 解释器,还是在 PCIe 总线和 KV cache 上?前者是伪问题,后者才是真问题。

IT资源栈
评论前必须登录!
立即登录 注册