先下个结论:Liquid AI 开源的 Pipette,是一套针对端侧模型、量化、运行时与硬件做交叉评测的可复现基准套件。它既不是新的模型,也不是直接替代推理框架,而是把输入数据、测试脚本、参数配置和环境信息固定下来,让同一批模型和量化方案在不同硬件、不同运行时上产出可对比的结果。正在做端侧模型选型、量化方案对比、开发板评估、推理引擎替换的人,应该先理解它解决的问题:评测结果不统一,技术选型就全是拍脑袋。
这类工具最值得先看的不是功能列表,而是能不能在普通环境里稳定跑起来。下面按实际落地顺序拆开讲,内容包括四类变量怎么拆、评测矩阵怎么设计、从单条任务到批量跑通要经过哪些步骤、指标怎么读、结果对不上时怎么排查。
1. 先搞清楚端侧评测为什么难做
1.1 同一个模型,换台设备结果就变了
端侧模型和服务器端模型最大的区别,不是参数规模,而是运行环境极度碎片化。服务器场景里,模型部署在相对统一的 GPU 集群上,驱动、CUDA 版本、推理框架基本可控。端侧不同,可能是手机、平板、开发板、边缘盒子、树莓派,也可能是 RK3588 这类带 NPU 的 SoC。不同设备之间,CPU 指令集不同、内存带宽不同、GPU/NPU 算子支持不同,甚至同一块芯片在不同固件版本下的调度策略都不一样。
所以经常出现这样的现象:同一个量化模型,在自己的开发机上跑得很快,放到目标设备上就明显变慢;或者在 A 设备上精度正常,到了 B 设备上输出开始乱。很多人第一反应是模型问题或量化问题,但实际原因可能出在运行时没有走对算子,或者 NPU 只支持某一种算子布局,跑了一部分 CPU 回退。这个时候如果没有一套固定变量、能逐项对比的评测框架,很难定位是哪一层出了问题。
1.2 四类变量同时影响结果,必须拆开看
端侧模型评测真正难的地方,是变量太多。随便列一下就有四类:
- 模型本身。网络结构、参数量、输入输出格式、任务类型、词表走的是不同路线,甚至同一个模型的不同 checkpoint 也可能有差异。
- 量化方案。常见的有 INT8、INT4、FP16、FP8,还有 GGUF 量化里按不同 K 值拆出来的变体。量化粒度、校准集、是否混合量化、是否保留敏感层,都会影响最终结果。
- 运行时。llama.cpp、ONNX Runtime、MNN、NCNN、TFLite、自家引擎,在算子实现、内存布局、线程调度、图优化上差别很大。
- 硬件。CPU 微架构、内存大小和带宽、GPU/NPU 是否可用、功耗和散热策略、动态调频机制。
这四类变量不是独立存在的。同一个模型,配上不同量化格式,在不同运行时上的最优路径可能完全不同。比如某个运行时对 INT4 算子的优化很好,但另一个运行时可能对 INT8 更成熟。只看单点数据,根本得不出“哪个方案更好”的结论。Pipette 这类基准套件的定位,就是把四类变量放进同一个评测协议里,让你能固定一个维度,逐项比较其他维度。
1.3 手工对比的常见问题
手工做对比不是不行,但很容易掉进几个坑。第一,输入不一致。今天用一个 prompt,明天换一个,两轮结果没有可比性。第二,环境不一致。依赖版本升级了、线程数改过、系统更新过,结果飘了也不知道。第三,指标口径不一致。有人统计延迟只算推理时间,有人把加载模型时间也算进去,还有人用平均延迟但没说明方差。口径不一致,结论就没办法复用。
所以我一直建议,凡是涉及端侧部署选型,不要只记一个“跑分”。至少要记录环境、输入、参数、重复次数、日志路径。Pipette 这类可复现基准套件的核心价值,恰恰是把这个容易被忽略的事情标准化。
2. 可复现基准的核心不是跑分,而是固定变量
2.1 所谓可复现,到底要固定哪些东西
很多人听到“可复现”,以为就是把命令再跑一遍。实际没那么简单。要让结果可复现,需要固定的东西包括:
- 系统环境:操作系统版本、内核参数、驱动版本、运行时软件版本、编译选项。
- 输入数据:测试文本、图片、音频、视频,最好给出文件 hash,确认前后两次用的是同一份输入。
- 评测协议:加载模型的方式、预热次数、正式测试轮数、最大生成长度、批量大小、并发数、随机种子。
- 输出记录:原始日志、转换后的指标、运行时间、启动时间、峰值内存、错误信息。
这些信息缺一项,都可能让结果出现偏差。比如同一份 GGUF 模型,用不同版本的运行时加载,底层 kernel 可能变了,延迟和内存自然跟着变。如果只记录“跑了 100 次,平均延迟 15ms”,没人知道这个 15ms 是在什么条件下跑出来的,后期很难排查。
2.2 一个合理的评测矩阵
基准套件的核心能力,通常体现在评测矩阵上。端侧评测不建议一开始就全量组合,那样组合数量会爆炸。比如 3 个模型乘以 4 种量化格式乘以 3 个运行时乘以 2 台设备,就是 72 个组合,每个组合还跑多轮。如果单次推理就要几百毫秒,这轮评测会耗掉大量时间,而且日志会非常乱。
更合理的做法是分步:
- 先定目标:主要看延迟,还是看内存,还是看精度,还是全面看。
- 固定一个维度,其他维度取默认值,先跑一组基线。
- 再逐个打开其他维度,做增量对比。
- 每次只改一个变量,不要同时改两个。
比如先固定“模型 A + INT8 + 运行时 X + 设备 1”,跑出基线。然后保持其他不变,换成“运行时 Y”,对比两个运行时。再把“运行时 Y”固定住,换一种量化格式。这样每个结论都能追踪到具体变量。
2.3 从标题里能读出的定位
从项目标题来看,Pipette 把评测对象明确拆成了四块:端侧模型、量化、运行时、硬件。这个拆分本身就是一种设计思路,它暗示的不只是“测模型快不快”,而是“在某个硬件上,某份模型使用某种量化格式、经过某个运行时,整体表现如何”。
为什么要强调“同时评测”?因为端侧部署选型从来不是单点最优。一个模型在 A 设备上延迟低,不代表在 B 设备上同样低;INT8 在 CPU 上效果好,但切换到 NPU 可能反而被量化 kernel 限制。只有把四类因素放在同一个框架下,才能做交叉对比。
这一点对做实际项目的团队更重要。因为你的最终交付物不是“这个模型跑分很高”,而是“目标设备上能稳定运行,延迟和内存都达标”。没有交叉评测框架,团队很容易只盯着某一个指标,最后在集成阶段翻车。
3. 按照最小组合跑通第一次端侧评测
3.1 准备环境:系统、依赖和可对比基线
大多数端侧评测适合在 Linux 环境跑,因为很多推理运行时和交叉编译工具链对 Linux 支持最完整。Windows 和 macOS 也不是不行,但容易碰到路径分隔符、动态库加载、权限问题。准备环境时,建议先做以下几件事:
- 记录系统版本:
uname -a、cat /etc/os-release、CPU 型号、内存大小、磁盘剩余空间。 - 固定依赖版本:Python 版本、推理运行时的版本、量化工具版本、CUDA 或 ROCm 驱动版本(如果涉及 GPU/NPU)。
- 如果用到 Docker,把镜像 tag 固定下来,不要每次都用 latest。
- 给评测目录建好结构:
models/、data/、logs/、results/,路径里不要有中文和空格,避免工具解析出错。
准备环境的顺序不要反过来。先跑通环境,再跑模型;不要模型下载好了,才发现在目标设备上编译不过。
3.2 准备输入样例:宁可小,不要杂
端侧评测最容易翻车的就是输入样例准备得太大、太杂。一开始就用完整测试集,跑起来慢,出现问题时也分不清是哪个输入导致的。建议准备两套输入:
- 第一套是 smoke test,只放 3 到 5 个典型样本,用来验证整个流程能不能跑通。
- 第二套是正式评测集,覆盖目标任务的真实分布,但也不要一上来全量跑。
如果做文本生成任务,样例要覆盖短文本、中等长度文本、带特殊符号的文本。如果做多模态任务,还要检查图片尺寸、格式、通道数是否一致。同一份输入最好计算并保存 hash,后面排查“为什么两次结果不一样”时非常有用。
3.3 先跑单条 baseline,再扩展矩阵
我一般会先把“1 个模型 + 1 种量化格式 + 1 个运行时 + 1 台设备”跑通,作为 baseline。这一步的重心不是看数据,而是确认:
- 模型能正常加载。
- 输入能正常预处理。
- 推理能正常执行。
- 输出能正常保存。
- 日志里没有隐藏的报错或警告。
单条 baseline 跑通之后,不要急着一次性跑完整矩阵。先加一个变量,比如换一种量化格式,再跑一轮。跑完对比一次,确认数据有变化、日志正常。能连续控制两三个变量不出问题,再扩大组合范围。
3.4 把评测脚本固化下来
评测脚本不要写成一次性脚本,最好固化成可重复执行的文件。这一步的作用不是省事,而是保证“今天跑的结果”和“下周跑的结果”口径一致。
脚本里至少包含:
- 环境变量设置,比如线程数、OMP_NUM_THREADS、内存分配策略。
- 输入文件路径和输出目录路径。
- 量化格式和运行时参数。
- 测试轮数和随机种子。
- 日志输出和指标汇总。
至于工具本身,Pipette 这样的开源项目如果设计成了命令行工具,使用方式通常也会遵循类似模式:提供配置、输入、输出、设备和重复次数这些参数。具体命令以项目文档为准。我这里不推荐凭空写死命令,因为真实工具的参数名和用法可能不同。更重要的是理解“评测脚本必须能重复执行”这个原则,而不是背命令。
4. 读结果时,先分清延迟、吞吐、内存和精度
4.1 延迟指标:加载时间、预热后延迟、P50/P99
很多评测结果只给一个“平均延迟”,但这个数字非常容易被误解。端侧场景至少要拆成三个时间:
- 加载时间:从读取模型文件到模型准备就绪的时间。
- 首 token 延迟:从输入到第一个输出单元出现的时间,对交互式应用很关键。
- 稳态延迟:连续多次推理后的稳定延迟,反映持续运行时的真实水平。
还要区分统计口径。平均值容易被极端值拉高,中位数(P50)更适合描述“一般情况”,P99 适合判断“最差情况”。如果 P99 远大于中位数,说明偶尔会有卡顿,可能是内存分配、调度抖动或散热降频引起的。只看平均延迟,会掩盖这些偶发问题。
4.2 内存和资源占用:峰值不撒谎
端侧设备内存通常有限,模型权重、中间激活值、运行时 buffer、输入输出缓存都要占内存。评测时不能只看“模型权重有多大”,还要看峰值内存。同一个量化模型,在不同的运行时里,峰值内存可能差出几倍。有的运行时会一次性把权重全部加载到内存,有的会分块加载;有的会把中间结果缓存起来,有的选择重新计算。这些策略直接影响内存峰值,也会影响延迟。
记录内存时,建议同时记录:
- 进程启动前可用内存。
- 模型加载完成后已用内存增量。
- 推理过程中峰值内存。
- 连续运行多轮后内存是否持续增长。
如果内存持续增长,要警惕运行时泄漏或动态内存碎片化。这类问题在短时间单次测试里看不出来,但放到真实服务里就是事故。
4.3 精度对比:先看下游指标,再谈主观感受
模型量化后精度下降是正常现象,关键是要量化评估“下降了多少”。很多人喜欢把两段输出放在一起肉眼比较,然后说“差不多”或“差很多”。但肉眼判断容易受长度、措辞、格式影响,不够稳定。
更可靠的方式是定义任务指标。分类任务看准确率、F1;文本生成看 BLEU、ROUGE 或困惑度;多模态任务看匹配率、相似度分数。如果没有现成指标,至少要保持同一组输入、同一个温度参数、同一个随机种子,然后对比输出分布层面的平均偏差。
还要注意,量化前后的对比必须使用同一份校准数据或测试数据。如果校准集和测试集分布不一致,量化误差会被放大,结论也会失真。
4.4 稳定性:不要只用一次运行下结论
端侧硬件的温度、频率、后台进程都会影响运行结果。一次运行的数据基本没有说服力。建议至少跑 5 到 10 次,观察均值和方差。如果方差很大,先检查:
- 设备温度是否过高,触发了降频。
- 是否有后台进程抢占 CPU。
- 是否同一文件多次读取时缓存状态不同。
- 是否有随机调度导致线程绑定不稳定。
稳定性的意义不只是数字好看,它决定了这套方案能不能被其他人复现。如果只跑一次拿到一个漂亮的延迟,别人复现不出来,基准套件就失去意义了。
5. 结果对不上时,按输入、环境、参数、工具的顺序排查
5.1 输入问题:格式、hash、预处理不一致
评测结果异常时,最先要查的往往不是模型,而是输入。输入文件是不是被覆盖了?预处理脚本有没有统一?图片尺寸是否被自动缩放?文本是否走了不同的 tokenizer 版本?这些都是高频问题。
我建议每次评测前,先对比输入文件的 hash。hash 不一致,后面所有的对比都无效。还有一点容易被忽略:相同的文本在 tokenizer 不同版本下,token 序列可能不同,尤其当词表更新过之后。所以不仅要固定模型权重,也要固定 tokenizer 和预处理代码。
5.2 环境问题:版本、依赖、驱动、权限
版本变化是第二大坑。某个运行时升级一个小版本后,算子的 kernel 实现变了,延迟可能差 20%。所以评测前要记录运行时版本,并把依赖锁定。遇到结果对不上,先对比两次运行的版本信息,不要急着重新编译。
权限也值得留意。有些设备访问 NPU 或 GPU 需要额外权限,没有权限时会自动回退到 CPU。结果就是性能大幅下降,但不会直接报错。这个时候从日志里很难看出来,只有对比硬件加速器是否真正启用。
5.3 参数问题:线程数、batch、量化校准、随机种子
参数不一致是第三类常见问题。比如一个测试用了 4 线程,另一个测试用了默认线程数;一个 batch size 是 1,另一个是 8;一个量化版本用了动态校准,另一个用了静态校准。这些都会造成差异,但不会报错。
排查时,优先确认:
- 线程数、CPU 亲和性设置。
- batch size、并发数、超时时间。
- 随机种子和采样参数。
- 量化校准配置,比如校准集大小、是否 per-channel、是否跳过某些层。
- 输出目录或保存路径是否因为存在旧文件而覆盖到了不同结果。
5.4 工具边界:运行时算子差异和已知限制
最后才考虑工具本身的边界。有时同一个模型在 GPU 上精度正常,在 NPU 上输出偏差大,不一定是量化问题,可能是运行时对某些算子不支持,走了 CPU 回退;或者 NPU 端对某些算子使用了浮点近似实现。
这种情况可以从日志里搜“fallback”“unsupported”“partial”等提示。如果日志没有明确提示,可以对比不同运行时在同样输入上的输出差异。如果只有某一个运行时异常,问题基本不在模型,而在运行时和硬件的兼容性。
6. 把评测结论变成部署决策,不能只看一张表
6.1 明确目标约束:延迟上限、内存上限、功耗上限
评测做完之后,会得到一堆指标。这时候最怕的是只看“谁最快”或“谁精度最高”。真实部署要考虑目标约束:你的应用能不能接受 300ms 延迟?内存上限是 2GB 还是 512MB?设备有没有功耗限制?量化的精度损失能不能被业务容忍?
先把约束写下来,比如:
- 最长响应时间不超过 500ms。
- 峰值内存不超过 1GB。
- 连续运行 8 小时不崩溃、不持续涨内存。
- 精度指标相对原始模型下降不超过 2%。
然后拿评测结果去筛。不是拿所有指标一起比,而是先筛硬约束,再考虑软指标。能过第一轮的方案通常只有两三个,再做长稳测试和真实场景测试。
6.2 用分层筛选代替全量跑分
很多人一看到基准工具,就想着把所有组合都跑一遍。其实没必要。全量跑分只适合做技术调研,不适合做选型。选型应该分层:
- 第一层:从模型库中筛出任务最匹配、模型体积可接受的几个候选。
- 第二层:在目标设备上跑“1 个模型 + 候选量化格式 + 候选运行时”的小矩阵。
- 第三层:根据延迟、内存、精度的初步结果,选出前两个组合。
- 第四层:对前两个组合做长稳测试,包括连续请求、异常输入、内存观察、日志检查。
这样既不会漏掉关键组合,也不会把时间浪费在明显不可行的方案上。
6.3 把评测结果和业务集成分开看
评测结果是选型参考,不是交付保障。跑分高的组合,不代表接入业务后一定顺畅。真实业务要处理的一堆问题,评测脚本通常覆盖不到,比如:
- 输入数据格式多种多样,评测集只有标准样本。
- 并发请求如何排队、超时如何处理。
- 模型文件如何打包、升级、回滚。
- 日志如何采集,错误如何上报。
- 模型和设备兼容性在系统更新后是否会改变。
所以结论应该是:先用 Pipette 这样的基准套件缩小候选范围,再做真实业务集成验证。评测阶段的数据越客观,集成阶段需要排查的问题就越少。
7. 最后留几个细碎但很实用的建议
7.1 三个值得提前养成的习惯
第一,固定一个评测入口。无论是脚本、Makefile 还是容器,最终都要能一条命令跑完一个组合。入口不固定,每次手动敲命令,总会有人漏参数。第二,保留原始日志。不要只保留最终汇总的指标,最好把日志、配置、输入 hash 一起存档。遇到线上问题再回看,价值很大。第三,先跑小样本。多少次问题都是因为一上来就跑大数据集,导致慢了、卡了、崩了之后,根本不知道是哪一步出错。小样本跑通,再逐步放大。
7.2 什么情况下不必追求完整基准
如果你的项目只需要在某个固定设备上跑一个固定模型,也没有替代运行时、替代量化格式的诉求,那完整基准套件的意义会变小。这种情况下,你更需要的是“目标设备上的端到端验证”,也就是模型能不能稳定运行、延迟是否达标、内存是否安全、日志是否清晰。简单跑几轮没问题,就可以进入集成。
但只要你面临以下情况之一,就值得用一套可复现的基准框架:
- 需要在多台设备之间做对比。
- 需要在多种量化方案之间做取舍。
- 需要在多个运行时之间迁移。
- 需要评估模型升级或运行时升级造成的回归。
- 需要让团队里其他人也能复现评测结果。
端侧模型的生态会越来越复杂,模型版本、量化格式、运行时、硬件这几层之间的组合会越来越多。靠记忆和零散脚本维护评测结果,迟早会失效。Pipette 这类基准套件提供的是一个更稳的起点:先把变量固定住,再谈谁快谁慢、谁好谁差。真正落地时,最该盯住的不是单张跑分表,而是输入格式、资源占用、日志完整度和失败重试,这些才是长期可靠的关键。