同一块 GPU、同一份模型权重,llama.cpp 冷启动可压缩到约 30 秒量级,而 vLLM 往往需要数分钟;反过来,在并发请求下 vLLM 的持续吞吐可达 llama.cpp 的约 3 倍,首 token 延迟(TTFT)差距接近 5 倍。这种“启动快则吞吐弱、吞吐强则启动慢”的现象并非实现缺陷,而是两套引擎在内存管理、批处理调度与量化内核上的结构性取舍。本文以“启动速度—持续吞吐”二元权衡为分析主线,逐层拆解 KV Cache 分页、连续批处理、CUDA Graph、量化内核与冷启动链路,给出可复现的基准测试方法与按场景选型的决策矩阵,并讨论投机解码、分离式推理等前沿方向对这一权衡的重塑。
01 问题的提出:一次“换引擎即换体验”的现场复盘
先把场景摆出来。一台单卡工作站,一张 24GB 显存的消费级或入门级专业卡,加载一个 7B~14B 量级的开源权重。用 llama.cpp 的 llama-server 起服务,从进程拉起、权重映射、上下文初始化到端口可用,熟练配置下可以压到 30 秒上下;换成 vLLM,同样的权重、同样的卡,冷启动往往要几分钟,中间还伴随显存预分配、CUDA Graph 捕获、算子编译/预热等动作。可一旦服务真正跑起来、并发请求压上去,画风立刻反转:vLLM 的持续吞吐大约是 llama.cpp 的 3 倍,首 token 延迟(Time To First Token,TTFT)差距接近 5 倍。
于是就有了那个非常真实的抱怨:同一个模型、同一块 GPU,换个引擎就从“能用”变成“不想用”。这句话里其实藏着两个不同的“不想用”——启动慢的时候不想用,是因为迭代调试、弹性伸缩、故障恢复都被拖住了;吞吐低的时候不想用,是因为一旦有真实并发,排队时间迅速劣化,用户体验断崖式下跌。二者指向的并不是同一个工程目标。
笔者认为,把这个问题简化成“llama.cpp 和 vLLM 谁更好”是一次范畴错误。它们优化的目标函数根本不同:llama.cpp 的基因是“在尽可能广的硬件与尽可能轻的依赖上,把模型跑起来”,vLLM 的基因是“在数据中心级 GPU 上,把每一滴显存和每一个 SM 周期都榨成吞吐”。前者天然偏向冷启动与部署灵活性,后者天然偏向持续吞吐。选型的本质,是判断你的负载形态更靠近哪一端,而不是给两个引擎排座次。
“启动速度”和“持续吞吐”不是同一维度的指标,把它们放进同一张排行榜比较,就像用百米成绩去评价马拉松选手的耐力——数字都是真的,结论却会误导决策。
为了让后面的讨论有落点,这里先给出本文的分析主线:推理引擎的性能由“内存管理策略 × 批处理调度策略 × 内核与图优化程度”三者共同决定,而这三者恰恰是冷启动成本的主要来源。换句话说,吞吐优势与启动劣势在多数情况下是同一套设计的两面,而不是可以随意拆开的独立开关。理解了这条因果链,才能判断哪些差距可以被配置抹平、哪些只能靠架构演进消解。
02 背景与概念界定:两种引擎的设计哲学分野
2.1 先把几个指标定义清楚
讨论性能之前必须先统一口径,否则“3 倍”“5 倍”这类数字很容易被误读。本文采用如下定义:
- 冷启动时间(Cold Start):从进程创建到服务端能够接受并正确响应第一个请求的总耗时,包含权重加载、显存/内存分配、图捕获、预热等全部阶段。
- TTFT(Time To First Token):请求到达至首个输出 token 返回的时延,主要由预填充(prefill)阶段决定,与输入长度强相关。
- TPOT(Time Per Output Token):解码阶段每个输出 token 的平均间隔,反映单请求的生成速度。
- 持续吞吐(Sustained Throughput):在稳定并发负载下单位时间完成的总 token 数(输入+输出),通常以 tokens/s 计,是衡量单位算力经济性的核心指标。
这四个指标并非彼此独立。TTFT 与吞吐在批处理框架下存在直接张力:把更多请求塞进同一批次可以提升吞吐,但会拉长每个请求的排队与预填充等待,推高 TTFT。这个张力在后面的章节会反复出现。
2.2 llama.cpp:从“能在任何地方跑”出发
llama.cpp 最初以纯 C/C++ 实现、零重依赖为目标,核心卖点是在 CPU、Apple Silicon、消费级 GPU 乃至各类边缘设备上以量化形式运行 LLaMA 系模型。它的 ggml 后端支持多种量化格式(Q4_K_M、Q5_K_M、Q8_0 等),并逐步扩展出 CUDA、Metal、Vulkan、ROCm 等后端。其服务端组件 llama-server 提供 OpenAI 兼容接口,但调度层相对朴素:早期以单序列或简单并行槽位(slot)为主,后续版本引入了基于 slot 的连续批处理雏形,但整体仍以“低依赖、易部署、单机可用”为优先。
本文评述:llama.cpp 的工程价值不在于峰值性能,而在于它把“推理可用性”的下限抬得极高。一个没有 CUDA 工具链、没有 Python 环境、甚至没有 root 权限的机器,编译一个二进制加一个 gguf 文件就能跑。这种设计选择必然牺牲数据中心级的调度复杂度——而调度复杂度恰恰是吞吐的主要来源之一。
2.3 vLLM:以 PagedAttention 为原点的吞吐机器
vLLM 的核心贡献是 PagedAttention(Kwon 等,SOSP 2023),它把 KV Cache 按固定大小的块(block)管理,像操作系统虚拟内存分页一样按需分配、非连续存放,从而把 KV Cache 的显存浪费从传统的 60%~80% 压到个位数百分比。在此之上,vLLM 实现了连续批处理(continuous batching)、前缀缓存(prefix caching)、CUDA Graph 捕获、chunked prefill 等一整套面向吞吐的机制。这些机制共同构成了它对 llama.cpp 的吞吐优势,也共同构成了它冷启动更重的原因。
笔者认为,理解 vLLM 的关键不是记住它有哪些特性,而是理解它的默认假设:显存是稀缺且需要被精确调度的资源,请求是持续到达且长度不一的,GPU 的空转是最大的浪费。所有机制都是为这个假设服务的。一旦你的场景不满足这个假设——比如单用户、低并发、频繁重启——这些机制就从优势变成了负担。
| 维度 | llama.cpp | vLLM |
|---|---|---|
| 首要目标 | 可移植性与低依赖 | GPU 吞吐与显存效率 |
| KV Cache 管理 | 预分配连续缓冲 | 分页块管理(PagedAttention) |
| 批处理 | slot 并行,调度较简 | 连续批处理 + chunked prefill |
| 量化 | ggml 量化族,CPU/GPU 通吃 | 以 FP16/BF16 为主,AWQ/GPTQ/FP8 支持 |
| 启动路径 | mmap 权重 + 轻量初始化 | 显存预分配 + 图捕获 + 预热 |
03 原理拆解:吞吐差距的技术根源
要解释“约 3 倍吞吐、约 5 倍 TTFT”这个量级的差距,必须回到 GPU 的执行模型。解码阶段的自回归生成是典型的内存带宽受限(memory-bound)负载:每生成一个 token,都要把全部权重从显存读一遍。单请求时,GPU 的算力单元大量闲置,瓶颈在带宽;只有当多个请求被批处理、共享同一次权重读取时,算力利用率才会上升。这就是批处理提升吞吐的第一性原理。
3.1 KV Cache 的浪费率决定了你能塞进多少并发
传统实现中,KV Cache 按“最大序列长度 × 最大并发数”预分配连续显存。但真实请求长度分布高度长尾:多数请求很短,少数很长。预分配导致大量显存被闲置的“预留位”占据,可用并发数被严重压低。PagedAttention 的论文给出的实测是,既有系统在 KV Cache 上的显存浪费可达 60%~80%(Kwon et al., 2023)。vLLM 通过块表(block table)把逻辑连续的 KV 映射到物理非连续的块,浪费率降到个位数百分比,等效于可用批大小成倍提升。
本文评述:这个机制的价值常被简化为“省显存”,但更准确的表述是“把省下来的显存转化为并发度”。在内存带宽受限的解码阶段,并发度几乎线性地转化为吞吐——这正是 3 倍差距的主要来源之一。llama.cpp 的 slot 机制虽然也支持并行,但其 KV 缓冲的分配粒度更粗,长上下文场景下的浪费更明显,能稳定支撑的并发槽位数量受限。
3.2 连续批处理:让 GPU 不因“等一个请求”而空转
静态批处理要求一个批次内所有请求同时开始、同时结束,短请求必须等长请求,GPU 在批次尾部大量空转。连续批处理(continuous batching,又称 iteration-level scheduling)在每个解码步重新组装批次:已完成的请求立即退出,新到达的请求立即加入。Orca(Yu et al., OSDI 2022)系统性地提出了这一调度思想,vLLM 将其工程化并默认开启。
效果是直接的:在请求长度方差大的真实负载下,连续批处理能把 GPU 有效利用率从静态批处理的低位显著拉高。TTFT 的改善则来自另一个机制——chunked prefill,它把长输入的预填充切成小块,与解码请求交错执行,避免一个长 prefill 阻塞所有正在解码的请求。这解释了为什么 vLLM 的 TTFT 能比 llama.cpp 低近 5 倍:差距不只在单次 prefill 的速度,更在排队策略。
3.3 CUDA Graph 与前缀缓存:把重复开销摊薄
解码每一步的算子启动(kernel launch)开销在小模型、短序列时不可忽略。CUDA Graph 把一整步的计算图预先捕获为一张图,后续以单次图启动替代数十次 kernel launch,降低 CPU 侧调度开销与 GPU 空闲间隙。前缀缓存则让相同系统提示词的多个请求复用已计算的 KV,避免重复 prefill。二者都是“用启动期的准备换运行期的效率”的典型手段——注意这个句式,它会在第 4 章原样回响。
笔者认为,这三类机制可以统一为一个判断:vLLM 的吞吐优势,本质是把“请求间可共享的计算与内存”尽可能共享,把“请求间的等待”尽可能消除。共享需要索引结构(块表、前缀哈希),消除等待需要调度器状态机,这些结构都需要在启动时构建、在运行时维护,成本是真实存在的。
3.4 量化内核:llama.cpp 的隐性优势与显性代价
llama.cpp 的 ggml 量化内核(尤其是 K-quants 系列)在低比特下保持了很好的精度-速度平衡,且对 CPU 与统一内存架构(如 Apple Silicon)做了深度优化。在显存受限的单卡上,Q4 量化让更大的模型得以装入,这本身是吞吐之外的另一种“可用性”。但量化内核的多样性也意味着:不同量化格式走不同的反量化路径,部分格式在 GPU 上的矩阵乘效率不及 FP16/BF16 的原生 Tensor Core 路径。vLLM 主推 FP16/BF16 与 AWQ/GPTQ/FP8,其内核针对数据中心 GPU 的 Tensor Core 做了更集中的优化。
本文评述:这里存在一个常被忽略的公平性问题。拿 llama.cpp 的 Q4 量化对比 vLLM 的 FP16,吞吐差距里有一部分来自精度差异而非引擎差异;反过来,拿两者同为 FP16 对比,llama.cpp 的显存占用劣势又会放大。严谨的对比必须锁定精度与量化格式,这一点在第 5 章的方法论里会具体展开。
04 冷启动链路解剖:30 秒与数分钟之间发生了什么
现在把镜头切到启动阶段。冷启动不是单一动作,而是一条由多个阶段串成的链路,每个阶段的耗时来源不同,可优化空间也不同。
| 阶段 | llama.cpp 典型行为 | vLLM 典型行为 |
|---|---|---|
| 进程与运行时 | 单二进制,秒级 | Python 运行时 + 依赖导入,数十秒 |
| 权重加载 | mmap 映射 gguf,按需缺页 | 读 safetensors 并拷入显存 |
| 显存分配 | 按上下文与槽位预分配 | 按 gpu_memory_utilization 预占大块显存 |
| 图/内核准备 | 惰性,首次请求时编译/加载 | CUDA Graph 捕获 + 预热前向 |
| 就绪判定 | 端口监听即可 | 完成 profiling 与图捕获后 |
可以看到,vLLM 的启动慢并非“代码写得慢”,而是它把大量运行期优化提前到了启动期:显存预分配是为了避免运行中碎片化,CUDA Graph 捕获是为了消除每步的 kernel launch 开销,profiling 是为了确定安全的批大小上限。这些动作在服务生命周期内会被摊薄——服务跑得越久、负载越重,单位请求分摊的启动成本越低。反之,如果服务频繁重启、生命周期只有几分钟,这些成本就永远摊不薄。
本文评述:这引出一个非常实用的判断准则——启动成本是否值得,取决于服务生命周期内能承载多少请求。设冷启动成本为 C,稳态吞吐为 T,则启动开销的“摊销阈值”大致是服务需要处理约 C×T 个 token 才能回本。对一个每天重启数十次的调试环境,这个阈值永远达不到;对一个 7×24 的在线服务,它几分钟就跨过了。这条准则比任何排行榜都更贴近真实决策。
llama.cpp 的 mmap 加载是另一个值得单独说的点。gguf 文件通过内存映射按需缺页,意味着进程拉起时并不需要把全部权重读进内存,首请求触发缺页后再逐步加载。这让“端口可用”的时间大幅提前,但也意味着第一个请求可能比后续请求慢——冷启动快,冷首请求未必快。工程上如果只测“服务起来没有”,很容易高估 llama.cpp 的实际就绪速度。
05 可复现的基准方法:如何测出属于你自己的数字
“3 倍”“5 倍”这类数字如果不带条件,几乎没有决策价值。本节给出一套可复现的基准流程,目标是让读者在自己的硬件与负载上得到可信的对比数据,而不是照搬别人的结论。
5.1 控制变量清单
- 模型与精度:同一权重来源,锁定等效精度。若必须使用不同量化格式,需在报告中明确标注,并单独做一组同精度对照。
- 输入/输出长度分布:固定 prompt 长度与 max_tokens,或使用可复现的长度分布(如对数正态),避免用单一长度代表全部。
- 并发模型:明确是固定并发(closed-loop)还是固定到达率(open-loop)。两者测出的 TTFT 分布差异很大。
- 预热策略:区分冷启动首请求、预热后稳态、以及长时间运行后的稳态,三者分开报告。
- 硬件与驱动:GPU 型号、显存、驱动版本、CUDA 版本、是否启用 ECC、是否共享 GPU。
5.2 一个最小可用的压测脚本骨架
下面给出一个基于 Python 的 open-loop 压测骨架,核心是控制到达率并记录每个请求的 TTFT 与总耗时。实际使用时替换 endpoint 与 payload 即可。
import asyncio, time, json, aiohttp, statistics
ENDPOINT = "http://127.0.0.1:8000/v1/completions"
PROMPT = "Explain the difference between paged attention and contiguous KV cache."
RATE = 4 # 每秒到达请求数
DURATION = 60 # 压测时长(秒)
MAX_TOKENS = 256
async def one_request(session, idx, results):
payload = {
"model": "local",
"prompt": PROMPT,
"max_tokens": MAX_TOKENS,
"stream": True,
}
t0 = time.perf_counter()
ttft = None
async with session.post(ENDPOINT, json=payload) as resp:
async for raw in resp.content:
line = raw.decode("utf-8", "ignore").strip()
if not line.startswith("data:"):
continue
body = line[5:].strip()
if body == "[DONE]":
break
if ttft is None:
ttft = time.perf_counter() - t0
total = time.perf_counter() - t0
results.append((idx, ttft, total))
async def main():
results = []
async with aiohttp.ClientSession() as session:
tasks = []
for i in range(int(RATE * DURATION)):
tasks.append(asyncio.create_task(one_request(session, i, results)))
await asyncio.sleep(1.0 / RATE) # 固定到达率
await asyncio.gather(*tasks)
ttfts = [r[1] for r in results if r[1] is not None]
print("requests:", len(results))
print("TTFT p50/p95:",
round(statistics.median(ttfts), 3),
round(sorted(ttfts)[int(len(ttfts)*0.95)], 3))
print("throughput tok/s:",
round(len(results) * MAX_TOKENS / DURATION, 1))
asyncio.run(main())
这个脚本的关键设计点有三:固定到达率而非固定并发,能暴露排队效应;流式读取以精确捕捉首 token 时刻;分别统计 p50 与 p95,因为均值会掩盖长尾。实际工程中还应加入错误率统计与超时控制。
5.3 冷启动测量:别只测“端口起来”
冷启动测量建议分三个时间点记录:进程创建到端口监听(T1)、端口监听到首个请求返回首 token(T2)、首个请求完成(T3)。T1 反映框架初始化成本,T2 反映预热与首次计算成本,T3 反映冷首请求的端到端体验。很多“30 秒启动”的说法只覆盖 T1,而用户真正感知的是 T1+T2。建议在报告中同时给出三者,并注明是否清空了页缓存(echo 3 > /proc/sys/vm/drop_caches),否则 mmap 的冷启动优势会被高估。
06 关键数据与案例:公开基准与工程实测的交叉验证
公开文献与社区基准给出的量级,与本文开头的经验数字基本吻合,但条件差异很大,需要逐条辨析。
6.1 学术侧:PagedAttention 与连续批处理的原始数据
PagedAttention 论文报告,在相同延迟水平下 vLLM 相比 FasterTransformer 与 Orca 可实现 2~4 倍的吞吐提升,且 KV Cache 浪费率显著降低(Kwon et al., SOSP 2023)。Orca 论文则报告,连续批处理相比静态批处理在真实负载下吞吐提升可达数倍(Yu et al., OSDI 2022)。这两项工作共同奠定了“调度即吞吐”的认知基础。
本文评述:这些数字的参照系是同期的高性能推理系统,而非 llama.cpp。把论文里的倍数直接套到 llama.cpp 头上是不严谨的。llama.cpp 的定位本就不是吞吐竞赛,它的对照系是“能不能跑”。因此本文开头“约 3 倍”的说法,应当理解为特定硬件、特定模型、特定并发下的量级估计,而非普适常数。
6.2 工程侧:社区基准的常见结论与陷阱
社区中大量对比帖给出的结论高度一致:低并发(1~4)时两者差距不大,甚至 llama.cpp 因量化优势在显存受限时更实用;并发上升到 16、32、64 时,vLLM 的吞吐优势迅速拉开,TTFT 的长尾差距尤其明显。这与第 3 章的原理分析完全自洽——批处理收益随并发度增长,而 PagedAttention 的价值也随并发度显现。
但社区基准常见三个陷阱:其一,用不同量化格式对比,结论被精度差异污染;其二,只报平均 TTFT 不报 p95/p99,掩盖了长尾;其三,忽略预热,把冷首请求混入稳态统计。笔者认为,任何一份有决策价值的对比报告,都应至少包含“同精度、分并发、报分位数、标注预热状态”这四项信息,否则只能当作定性参考。
| 并发 | llama.cpp 相对吞吐 | vLLM 相对吞吐 | TTFT 差距趋势 |
|---|---|---|---|
| 1 | 1.0 | 0.9~1.1 | 接近 |
| 8 | 1.4 | 2.6 | vLLM 略优 |
| 32 | 1.8 | 5.0 | vLLM 明显优 |
| 64 | 2.0 | 6.2 | 差距扩大 |
这张表想传达的核心信息不是具体数字,而是曲线的形状:低并发区间两者几乎重合,高并发区间迅速分叉。这意味着选型的分水岭不在“哪个引擎绝对更快”,而在“你的负载落在曲线的哪一段”。如果你的服务峰值并发长期低于 4,为 vLLM 的吞吐优势付出的启动与运维复杂度可能并不划算;如果你的服务常态并发在 32 以上,llama.cpp 的吞吐天花板会成为硬约束。
07 对比与局限:谁在什么边界条件下更优
7.1 边界条件一:服务生命周期
这是最容易被忽略却最决定性的变量。生命周期短于启动摊销阈值的场景——CI 中的模型回归测试、按需拉起的一次性推理任务、Serverless 冷启动、开发调试——llama.cpp 的优势是结构性的,不是调参能追上的。生命周期长于阈值的场景——7×24 在线 API、批量离线生成、多租户平台——vLLM 的吞吐优势会迅速覆盖启动成本。
本文评述:Serverless 推理是这条边界最尖锐的试金石。在按请求计费、实例频繁伸缩的环境里,冷启动时间直接换算成金钱和超时率。这也是为什么近年出现了一批专门优化冷启动的推理系统(如面向 Serverless 的权重加载与快照技术),它们试图打破“吞吐强则启动慢”的绑定,第 9 章会展开。
7.2 边界条件二:硬件与显存预算
显存越紧张,llama.cpp 的量化优势越突出。一张 16GB 卡上跑 13B 模型,FP16 根本装不下,Q4 量化后不仅装得下还有余量做上下文。此时讨论 vLLM 的吞吐优势没有意义,因为它压根跑不起来这个配置。反过来,在 80GB 卡上跑 7B 模型,显存充裕,vLLM 可以把批大小拉到很高,吞吐优势充分释放。
另一个硬件维度是统一内存架构。在 Apple Silicon 上,llama.cpp 的 Metal 后端经过长期打磨,是事实上的首选;vLLM 对非 NVIDIA 平台的支持在持续完善,但成熟度与生态仍有差距。选型不能脱离硬件栈谈引擎。
7.3 边界条件三:功能完备度与运维复杂度
吞吐不是唯一维度。vLLM 在张量并行、流水线并行、多卡多机、前缀缓存、结构化输出、投机解码等功能上更完整,适合构建平台级服务;llama.cpp 在依赖极简、单二进制分发、跨平台一致性上更强,适合嵌入与边缘部署。运维层面,vLLM 的 Python 依赖栈更重,版本兼容与镜像体积是需要计入的成本;llama.cpp 的部署则轻得多。
笔者认为,把这两者对立起来本身就是一种资源浪费。在成熟的生产架构里,它们更常见的角色是分工而非替代:用 vLLM 承载高并发的在线主链路,用 llama.cpp 承担边缘节点、本地开发、离线小批量与降级备份。这种“双引擎”策略的复杂度,远低于在两个都不完美的选项里强行二选一。
| 场景 | 更倾向 | 关键理由 |
|---|---|---|
| 本地开发/调试 | llama.cpp | 频繁重启,启动成本主导 |
| 边缘/嵌入式 | llama.cpp | 低依赖、跨平台、量化 |
| 在线高并发 API | vLLM | 连续批处理 + 分页 KV |
| 批量离线生成 | vLLM | 吞吐即成本,启动可摊销 |
| Serverless 按需 | 视冷启动优化而定 | 需专门优化,见第 9 章 |
08 决策路径:把“选型”变成可执行的工程流程
把前面的分析收敛成一套可操作的流程。以下步骤按顺序执行,每一步都有明确的产出物,避免“凭感觉选型”。
步骤一:刻画负载画像
采集至少一周的真实请求日志,统计四个分布:输入长度分布、输出长度分布、并发到达率随时间的变化、请求的突发性(峰值/均值比)。如果还没有线上流量,用业务预估构造三档场景(保守/基准/激进)。产出物是一页负载画像文档,它是后续所有测算的输入。
步骤二:计算启动摊销阈值
用第 4 章的准则估算:服务平均生命周期内预计处理的 token 总量,与冷启动成本折算的等效 token 量对比。若前者远大于后者,启动成本可忽略,选型重心转向吞吐与功能;若两者接近或前者更小,启动成本必须进入决策函数。产出物是一个明确的“启动是否重要”的布尔判断。
步骤三:在目标硬件上跑对照基准
用第 5 章的方法,在真实硬件上对两个引擎做同精度、分并发(1/8/32/64)、报分位数的对照测试。特别关注 p95 TTFT 与峰值并发下的错误率。产出物是一张对照表,标注每组数据的条件。
步骤四:做单位成本测算
把吞吐换算成单位 token 成本,把启动成本按生命周期摊销进去,再加上运维人力与镜像/依赖维护成本。产出物是每百万 token 的综合成本对比。这一步常会推翻前面的直觉——某些场景下,vLLM 的高吞吐带来的成本优势,会被更重的运维负担部分抵消。
步骤五:设计降级与混合策略
无论主选哪个,都应设计降级路径:主链路用 vLLM 时,边缘与备份用 llama.cpp;主链路用 llama.cpp 时,预留切换到 vLLM 的接口与数据通道。产出物是一份包含主备切换、容量水位、回滚条件的运维手册。
选型不是一次性的技术判断,而是一个随负载演化的持续过程。今天合适的引擎,在并发翻倍后可能就不再合适。把基准流程固化进 CI,比一次性选对更重要。
09 结论与展望:权衡正在被什么力量改写
回到最初的问题:推理框架的选择该按“启动速度”还是“持续吞吐”来定?本文的答案是——按“启动成本能否在服务生命周期内被摊销”来定,再按摊销后的单位成本与功能需求做二次筛选。这不是一个二选一的问题,而是一个关于时间尺度的判断。短生命周期场景里,启动速度是硬约束;长生命周期场景里,持续吞吐是硬约束;介于两者之间的,需要实测数据说话。
更重要的是,这条权衡线本身正在被技术进步改写。三个方向值得持续关注:
其一,冷启动优化正在从“框架层”下沉到“系统层”。权重快照、内存快照恢复、按需分页加载、预编译内核缓存等技术,目标是把 vLLM 类系统的启动成本压到接近 llama.cpp 的量级。一旦这个方向成熟,“吞吐强则启动慢”的绑定会被显著削弱。
其二,分离式推理(disaggregated prefill/decode)正在重构资源模型。把预填充与解码拆到不同实例,各自按负载特征独立扩缩容,可以让 TTFT 与吞吐分别优化,而不是在同一实例上互相牵制。这一方向在近年的系统研究中被反复讨论,工程落地也在推进。
其三,投机解码与更激进的量化正在改变“每 token 成本”的基线。当单 token 的计算与带宽成本下降,批处理带来的相对收益结构也会变化,今天的倍数关系未必是明天的倍数关系。
本文评述:对工程团队而言,最务实的姿态不是押注某一个引擎会赢,而是把“可复现的基准能力”和“可切换的架构抽象”建起来。前者让你在每次版本升级、每次硬件更换后都能快速重新判断;后者让你在判断改变时不必推倒重来。引擎会迭代,负载会演化,唯有测量与抽象是长期资产。
最后回到那句抱怨——“换个引擎就从能用变成不想用”。它之所以刺耳,是因为它揭示了一个事实:推理系统的用户体验,从来不是由单一指标决定的。启动速度决定了你能不能快速开始,持续吞吐决定了你能不能一直用下去。成熟的工程判断,是同时看见这两件事,并且知道在你的场景里,哪一件的权重更高。
本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。文中涉及的模拟整合数据仅用于说明趋势,不代表任何具体硬件或版本的实测结果。
主 要 参 考 文 献
- Kwon, W., Li, Z., Zhuang, S., et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. Proceedings of the 29th ACM Symposium on Operating Systems Principles (SOSP), 2023.
- Yu, G.-I., Jeong, J. S., Kim, G.-W., et al. Orca: A Distributed Serving System for Transformer-Based Generative Models. 16th USENIX Symposium on Operating Systems Design and Implementation (OSDI), 2022.
- Agrawal, A., Kedia, N., Panwar, A., et al. Taming Throughput-Latency Tradeoff in LLM Inference with Sarathi-Serve. 18th USENIX Symposium on Operating Systems Design and Implementation (OSDI), 2024.
- Zhong, Y., Liu, S., Chen, J., et al. DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving. 18th USENIX Symposium on Operating Systems Design and Implementation (OSDI), 2024.
- Leviathan, Y., Kalman, M., Matias, Y. Fast Inference from Transformers via Speculative Decoding. Proceedings of the 40th International Conference on Machine Learning (ICML), 2023.
- Frantar, E., Ashkboos, S., Hoefler, T., Alistarh, D. GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers. International Conference on Learning Representations (ICLR), 2023.
- Lin, J., Tang, J., Tang, H., et al. AWQ: Activation-aware Weight Quantization for On-Device LLM Compression and Acceleration. Proceedings of Machine Learning and Systems (MLSys), 2024.
- ggml-org. llama.cpp: LLM inference in C/C++. GitHub repository, 2023–2025. https://github.com/ggml-org/llama.cpp
- vLLM Project. vLLM: Easy, Fast, and Cheap LLM Serving for Everyone. GitHub repository and documentation, 2023–2025. https://github.com/vllm-project/vllm
全文约 12600 字 | 参考文献 62 篇(主要 9 篇)

