自建推理用什么框架:七个维度加一套自测方案
有人在群里问「自建推理到底选哪个框架」,底下立刻贴出三张截图,三张图的排名各不相同,其中两张还互相矛盾。这不是谁在骗人,是这三个人测的东西根本不是一回事:一个用 7B 级别的模型测短问答,一个拿长文档摘要压 64 并发,还有一个在消费级单卡上测单请求延迟。这三种负载对框架的要求分别落在完全不同的地方,得出相反的排名再正常不过。
所以我一直劝人别把这类跑分数字往自己的方案里抄。跑分数字不可迁移,选型维度和测试方法可以迁移。 这篇就只讲后面两样:先给一套七个维度的判断框架,再给一套你能在自己那张卡上跑出来的自测流程。跑完之后你手上会有一份属于你自己负载的结论,那个结论比任何截图都硬。
一、为什么别人的跑分对你没用
一次推理压测的结果,至少被下面这些变量同时决定:
| 变量 | 为什么会翻转结论 |
|---|---|
| 模型规模与结构 | 权重占多少显存、注意力实现方式不同,剩给 KV cache 的空间就不同 |
| 显卡型号与显存容量 | 显存决定并发天花板,卡间互联带宽决定多卡方案的代价 |
| 量化格式 | 不同框架对不同量化方案的支持成熟度差别很大 |
| 并发数 | 低并发看的是单请求延迟,高并发看的是批处理效率,两者常常此消彼长 |
| 输入输出长度分布 | 长输入压 prefill,长输出压 decode,两个阶段的瓶颈完全不同 |
| 是否流式 | 首 token 延迟在流式场景里是体感主指标,在批处理场景里几乎无所谓 |
| 框架版本 | 推理框架迭代极快,半年前的对比图可能已经完全失效 |
任何一张对比图,只要没有把这七项全部交代清楚,它就只是「某个人在某台机器上某一天的一个观测」。而这七项里,你能和别人对上的通常连一半都不到。
更要命的是版本这一项。这个领域的迭代节奏是以周计的,一篇看起来很专业的对比文章,写作那天的结论可能是真的,你读到它的时候框架已经改过好几轮调度实现了。所以任何看到的性能结论,都要先看它的时间戳,再看它有没有交代上面这张表。
二、七个选型维度
下面每个维度我都写清楚「为什么重要」和「你怎么自己判断」。判断方法都是查文档就能做的,不需要先把环境搭起来。
1. 服务化程度:它的设计目标里包不包括「被运维」
这是我第一个看的维度,因为它决定的不是性能,是你半夜会不会被叫起来。
一个只想着「把模型跑起来」的项目,和一个想着「被放进生产环境长期运行」的项目,在文档里长得完全不一样。后者会有两样东西:一套面向运维的参数体系,和一组基础设施端点。
以 vLLM 为例。它的官方文档里给出了一组直接对着显存和并发的参数:gpu_memory_utilization(提高它可以给 KV cache 更多空间)、max_num_seqs(一批里的并发请求数上限)、max_num_batched_tokens(单批次最大 token 数,影响 prefill 与 decode 的平衡)、max_model_len(限制单个序列的最大 token 数)、tensor_parallel_size(把模型权重切分到多张 GPU)、pipeline_parallel_size(把模型层分布到多张 GPU)、kv_cache_memory(直接指定 KV cache 大小,跳过显存 profiling 阶段)。
这组参数的存在本身就是一个信号:它承认「显存不够」是运行时的常态问题,并且给了你调的旋钮。
端点这边同理。vLLM 的官方 API 清单里,除了 /v1/completions、/v1/chat/completions、/v1/embeddings 这类业务端点,还单独有一组基础设施端点:/version、/health、/load、/v1/models、/metrics(Prometheus 格式)。/health 意味着你的负载均衡器和容器编排能直接做存活探测,/metrics 意味着监控可以直接抓,/v1/models 能用来验证服务实际暴露的模型名是不是你想要的。
判断方法:翻你候选框架的文档,找有没有健康检查端点、有没有指标导出、有没有一组显式的资源控制参数。找不到不代表框架不好,但意味着这部分工作要你自己补。其他框架的参数名和端点各不相同,以其官方文档为准——我这里只用 vLLM 举例,是因为它的这份清单我核对过。
2. 兼容性:你的调用代码要不要改
这个维度的价值特别实在:如果框架提供 OpenAI 兼容接口,你现有的 SDK、网关配置、重试逻辑基本不用动,把 base_url 一改就能切;如果不兼容,你要么改所有调用点,要么自己写一层适配。
更关键的是它决定了你的退路。兼容 OpenAI 接口的自建服务,可以直接挂在你现有的多模型网关后面,和云端 API 混编成同一个模型池,随时切回去。不兼容的话,你的自建方案就是一条单行道,出问题时没有平滑降级的路径。vLLM 这条路的兼容接口长什么样,可以看 vLLM 自建推理入门 和 vllm serve 起服务的参数与端点。
判断方法:文档里有没有明确写「OpenAI-compatible」,端点路径是不是 /v1/ 开头的那套。注意兼容度是有层次的:能对上 chat completions 不等于能对上 embeddings、function calling、结构化输出。把你实际用到的那几个能力逐个对一遍。
3. 模型支持面:你要跑的那个模型,那种权重格式
这个维度最容易在最后一刻把方案打回原形。你选好了框架,装好了环境,才发现你要跑的那个模型架构还没被支持,或者支持了但你手上那份量化权重的格式它读不了。
判断方法:去框架的支持模型列表里搜你的模型名(不是模型系列名,是具体那一个)。然后单独确认量化格式——不同框架支持的量化方案交集并不大,而且「支持」还分「能加载」和「有优化过的算子」两档。要是你打算用社区量化的第三方权重,还得确认那份权重的元数据格式对不对。
我见过不少人在这一步返工,共同点是选型阶段只看了框架的通用能力,没有拿具体的模型加具体的权重文件去核。
4. 多卡与分布式:单卡装不下的时候怎么办
单卡跑得动的时候这条不重要,一旦装不下就是生死线。主要看两件事:能不能做张量并行(把权重按维度切到多张卡上,各卡协同算一层),能不能做流水并行(把模型的层分到不同卡上,逐段传递)。
这两种切法的代价不一样。张量并行每一层都要跨卡通信,对卡间互联带宽敏感,同步开销是它的固定成本;流水并行的通信量小得多,但请求要依次穿过各个阶段,会给单请求延迟加负担。vLLM 把这两条分别放在 tensor_parallel_size 和 pipeline_parallel_size 两个参数上,官方在讲 OOM 缓解顺序时也是先建议提张量并行再建议提流水并行,代价分别标注为「同步开销」和「延迟」。
判断方法:确认框架支持哪种并行、你的机器拓扑(同机多卡还是跨机)是否被支持。跨机是另一个难度等级,不要默认支持。
5. 显存控制能力:能不能精细管住 KV cache 和并发
自建推理的绝大多数线上事故,追到底都是显存问题。模型权重是静态的,真正会涨的是 KV cache——它随并发数和序列长度一起长。一个框架如果只让你设「用几张卡」,不让你设并发上限和批次 token 上限,那你的服务就是一个随机在高峰期崩掉的服务。
好的信号是:能限制并发序列数,能限制单批 token 数,能限制单序列最大长度,能控制显存占用比例。这几样合起来才能让你在「吃满显存换吞吐」和「留出余量换稳定」之间做选择。
判断方法:在文档里找显存相关章节,看它有没有专门讨论 OOM 怎么办。有专门章节的框架,说明维护者知道这是高频问题。
6. 可观测性:指标能不能接进你现有的监控
自建最大的隐性成本是「出问题时看不见」。用云端 API 时,慢了就是慢了,你只能重试;自建时你必须能回答「慢在哪」——是排队排长了,是 KV cache 满了在抢占,还是 GPU 利用率本来就低。
能导出 Prometheus 格式指标的框架,可以直接进你现有的 Grafana,不用另起一套。加上健康检查端点,编排层的滚动更新和自动重启就能正常工作。这两样加起来,是「自建不等于没有可观测性」的实际依据。
判断方法:找指标导出端点和它导出的指标口径。具体有哪些指标名,以你选定框架的官方文档为准,别照抄别人博客里的指标名——这类名字改起来毫不留情。
7. 社区与更新节奏:模型迭代很快,框架跟不跟得上
新模型发布后多久能被支持,是个非常实际的问题。你现在选定的框架,半年后可能因为跟不上你要用的新架构而被迫更换,而更换的成本包含全部调参经验的作废。
判断方法:看仓库最近的提交频率、issue 的响应情况、新模型架构的支持是不是很快跟进。还有一条容易被忽略:文档的更新是不是跟得上代码。文档滞后的项目,你每次升级都要读源码,这个成本会持续摊在整个生命周期里。
三、核心:一套你自己跑得出来的自测方案
上面七条能帮你把候选压到两三个。剩下的分不出高下,就自己测。测试的目标不是产出一张排名图,是产出你自己那条负载曲线的拐点在哪。
第 0 步:固定所有变量
自测最容易失败的地方就是变量没锁住,测完了不知道差异来自哪。开测前把这几样钉死:
- 同一个模型、同一份权重文件(包括量化格式,别一个测 FP16 一个测量化)
- 同一张卡、同一台机器,测试期间不跑别的负载
- 同一批真实请求样本(下一节专门讲这个)
- 同一个客户端压测工具和并发模型
- 记下每个框架的版本号,以及你改过的每一个启动参数
第 1 步:请求样本必须来自真实流量
这一步是多数自测结论失真的根源,我单独拎出来讲。
绝大多数人压测时用的是合成请求:造一段固定长度的输入,让模型生成固定长度的输出,循环发。这么测出来的数字很漂亮,也很没用——因为真实流量的上下文长度是一个分布,不是一个数。
批处理调度的效率高度依赖长度分布。当一批请求里长度接近时,批次填得满,调度器很轻松;当同一批里既有几百 token 的短问答又有几万 token 的长文档时,短请求会被长请求拖住,KV cache 被少数长序列占掉大头,实际能并发的请求数远低于你按平均长度算出来的数字。用统一长度的合成请求测出来的容量,在真实混合负载下往往达不到。
正确做法:从你的线上日志里采样。如果还没上线,就用你的实际使用场景手工构造一批,但要刻意覆盖长度分布的两端。至少准备这些:
- 输入长度的分布(记 p50、p90、p99,不要只记平均值)
- 输出长度的分布(同上)
- 长请求在总请求中的占比
- 请求到达的时间分布(是均匀的还是有突发)
样本量不用大,几百条覆盖住分布形状就够。关键是长度比例要对。
第 2 步:单请求跑通,确认正确性
先别压。用单请求把整条链路走通,确认这几件事:
- 服务能起来,能加载你那份权重
- 返回内容正确,不是乱码不是截断
- 特殊能力可用(流式、工具调用、结构化输出,凡是你要用的都试一遍)
- 停止条件正常,长输出不会跑飞
- 模型名对得上(对提供模型列表端点的框架,用它验证)
这一步不通过,后面测什么都没意义。我见过有人压了两天才发现聊天模板配错了,输出一直不对。
第 3 步:阶梯加压,找拐点
从并发 1 开始,按 1 → 2 → 4 → 8 → 16 → 32 这样翻倍加,每一档跑够时间(让统计稳定,至少几分钟),记录下面这组数:
| 记录项 | 说明 |
|---|---|
| 并发数 | 客户端同时在飞的请求数 |
| 吞吐 | 单位时间完成的输出 token 数 |
| 首 token 延迟 p50 / p95 | 流式场景的体感主指标 |
| 端到端延迟 p50 / p95 | 整条请求的完成时间 |
| 显存占用峰值 | 整个档位期间的最高点,不是均值 |
| 错误数与错误类型 | 超时、拒绝、OOM 分开记 |
| GPU 利用率 | 判断是算力瓶颈还是调度瓶颈 |
你要找的是拐点:吞吐不再随并发提升、而 p95 延迟开始陡增的那个位置。拐点之前是有效并发区,拐点之后加并发只是把请求堆在队列里,用户体验会崩,吞吐还不涨。
这个拐点就是你的容量规划依据,比任何外部数字都准。
第 4 步:在拐点附近稳定跑一段
短时压测跑得好,不等于能长期跑。在拐点稍偏保守的位置(比如拐点并发的七到八成)连续跑几十分钟到几小时,看有没有劣化:
- 延迟是不是随时间缓慢上升
- 显存占用是不是单调爬升(爬升就是有东西没释放)
- 错误率是不是在某个时刻突然出现
- 长请求进来时,短请求的延迟被拖到什么程度
这一步最能筛掉「峰值好看但长期不稳」的方案。也是最多人省掉的一步。
第 5 步:故意压到 OOM,标出边界
最后主动把并发或者上下文长度往上推,推到服务报错为止,记下边界在哪。你需要知道这条线的位置,才知道线上限流该设在多少。不知道边界在哪的服务,等于把边界交给用户去发现。
记录表模板
每个候选框架、每个参数组合都填一行,最后一起看:
| 框架 | 版本 | 关键参数 | 并发 | 吞吐 | 首token p95 | 端到端 p95 | 显存峰值 | 错误率 | 备注 |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 正确性基线 | ||||||||
| 4 | |||||||||
| 8 | |||||||||
| 16 | |||||||||
| 32 | 是否已过拐点 | ||||||||
| 拐点×0.8 | 稳定性跑,时长记这里 | ||||||||
| 边界 | OOM 触发条件 |
「备注」一栏很重要,把每次改了什么、观察到什么异常都写进去。三天后你会忘。
四、OOM 调参:每个框架都要过这一关
不管你最后选谁,显存不够这件事都会遇到。调参的思路各框架相通,具体参数各不相同。
vLLM 官方给出的缓解顺序是明确的四步,我把每步的副作用一并写出来:
| 顺序 | 动作 | 机制 | 代价 |
|---|---|---|---|
| 1 | 提高 gpu_memory_utilization | 给 KV cache 更多空间 | 留给系统和碎片的余量变小,逼近上限时更容易在长请求下翻车 |
| 2 | 降低 max_num_seqs 或 max_num_batched_tokens | 减少同时在批里的请求数或单批 token 数,需要的 KV cache 就少了 | 并发能力直接下降,高峰期排队变长 |
| 3 | 提高 tensor_parallel_size | 权重切到多张卡,每张卡留给 KV cache 的空间变多 | 官方标注的代价是同步开销 |
| 4 | 提高 pipeline_parallel_size | 模型层分布到多张卡,降低每卡权重显存 | 官方标注的代价是延迟 |
另外 max_model_len 可以限制单序列最大 token 数,从源头掐掉超长请求把 KV cache 吃干的可能;kv_cache_memory 可以直接指定 KV cache 大小、跳过显存 profiling 阶段,在你已经测出稳定值之后用它锁死更省事。
注意这个顺序的内在逻辑:先在单卡内部腾空间(1、2),单卡实在腾不出来才动多卡(3、4)。因为多卡方案引入的是新的通信成本和新的故障面,能不上就不上。
其他框架有各自的参数体系,调法以其官方文档为准——这里给的是思路顺序,不是可以照抄的参数名。至于每张卡具体能装下多大的模型、需要多少显存,这个必须你自己按参数量、权重精度、KV cache 和余量算,再用实际部署验证,没有可以直接抄的数字。
五、别只看性能
七个维度里我把服务化程度放在第一位,性能相关的反而排在后面,这不是随便排的。在生产环境里,下面这几件事的权重比性能高:
升级维护的难度。 推理框架版本更新很频繁,你要评估的是「升一次要付出什么」:配置文件格式会不会破坏兼容、参数会不会被改名或废弃、旧权重还能不能加载、有没有可回滚的路径。一个每次升级都要重新调一遍参的框架,长期成本远高于它省下来的那点延迟。
故障排查的难度。 半夜服务变慢,你需要在几分钟内定位到原因。有指标、有结构化日志、有清晰错误信息的框架,和一个只在崩溃时吐一行 CUDA 报错的框架,处理同一个事故的时间差可能是十倍。
出问题时能不能找到人问。 这是最被低估的一条。你遇到的问题,别人有没有遇到过、issue 里有没有讨论、有没有中文资料,直接决定了你是花二十分钟解决还是花两天。一个使用者众多的方案,光是「搜索错误信息能搜到结果」这一点就值回票价。
部署形态是否匹配你的场景。 单机开发调试、内网小规模服务、面向公网的高并发服务,对框架的要求完全不同。开发机上顺手的方案,未必适合当线上服务;针对线上服务优化的方案,装在开发机上可能又太重。关于把开发用方案推上生产时会遇到的具体问题,可以参考 Ollama 上生产要补哪些课。
六、一条务实的路线
我给大多数团队的建议是同一条:
第一步,用最主流、文档最全的方案先跑通,把基线测出来。 主流方案的价值不在于它某项指标最好,而在于你遇到的每个问题基本都有人遇到过。跑通之后,你手上就有了第一份属于自己负载的真实数据。
第二步,只有当你有明确的、量化的瓶颈时,才去评估换。 「p95 延迟在目标并发下超标 40%,各种参数都调过了」是明确的瓶颈;「听说 X 更快」不是。
第三步,换的时候只换一个变量。 保持模型、样本、压测方法不变,只换框架,这样对比才有意义。
为什么不建议一上来就追最优?三个原因。一是你还不知道瓶颈在哪,没跑过就选优化方向,多半会优化错地方——很多人以为瓶颈在推理框架,实测发现在数据预处理或者网络往返上。二是最优是随负载变的,你的负载在产品迭代中还会变,现在为一个即将过时的负载做的极致优化,很快就作废。三是调参经验有沉没成本,你在一个框架上积累的显存和并发调参直觉,换框架时基本要重来一遍,这个隐性成本通常比省下的机器成本高。
先跑起来、先测出来、先知道边界在哪,比一开始就选对更重要。真到了需要换的那天,你手上那份自测数据会让换的决策变得非常简单。
自查清单
开始选型前,把这七条过一遍:
- 我要跑的具体模型和具体权重格式,在候选框架的支持列表里逐个确认过了吗?(不是模型系列名,是具体那一个)
- 候选框架有没有健康检查端点和指标导出?没有的话,我打算怎么补这部分监控?
- 它提供的接口能不能让我的调用代码不用改,以及出问题时能不能平滑切回云端 API?
- 我的压测样本是从真实流量采样的吗?输入输出长度的分布形状对不对,还是用的统一长度合成请求?
- 压测时模型、权重、显卡、样本、客户端这五项都固定住了吗?每个框架的版本号记了吗?
- 我知道拐点并发在哪吗?在拐点附近连续跑过一段时间、确认过没有延迟爬升和显存泄漏吗?
- 我故意压到过 OOM 吗?知道边界在哪、线上限流该设多少吗?
- 除了性能,升级成本、排障难度、社区活跃度这三项我评估过吗?
本文提到的所有参数名与端点均出自 vLLM 官方文档,以官方文档当次内容为准;其他框架的参数、端点与配置方式各不相同,请以各自官方文档为准。本文不提供任何框架的性能数字与排名——那正是需要你自己测出来的东西。