news 2026/9/24 22:31:10

OpenJarvis 推理引擎基准测试框架完全指南:Latency / Throughput 指标、BenchmarkSuite 与自定义 Benchmark

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenJarvis 推理引擎基准测试框架完全指南:Latency / Throughput 指标、BenchmarkSuite 与自定义 Benchmark

【免费下载链接】OpenJarvis

Personal AI, On Personal Devices

项目地址:https://gitcode.com/gh_mirrors/op/OpenJarvis
点击查看免费下载

OpenJarvis 内置了一套可复现、标准化的推理引擎性能基准测试框架,用于量化本地 LLM 推理引擎(如 Ollama、vLLM 等)的延迟与吞吐能力。本文将带你掌握其两大内置基准(Latency / Throughput)的指标含义与统计口径,理解BaseBenchmark抽象基类与BenchmarkSuite聚合器的底层实现,熟练使用jarvis bench run命令行完成样本数、引擎、模型的自由组合,并通过三步走自定义属于自己的 Benchmark 并注册到 CLI 与 Suite 中。


一、基准测试框架概览:定位与组成

OpenJarvis 的基准测试框架(benchmarking framework)用标准化、可复现的测试来度量推理引擎性能。它位于src/openjarvis/bench/目录,核心模块如下:

文件职责
_stubs.pyBaseBenchmark抽象基类、BenchmarkResult结果数据类、BenchmarkSuite批量运行器
_stats.py共享统计工具compute_stats,输出 mean/p50/p95/min/max/std
latency.py内置延迟基准LatencyBenchmark
throughput.py内置吞吐基准ThroughputBenchmark
energy.py能量效率基准EnergyBenchmark(按 token 统计 J/tok、功率 W 等)
__init__.py通过ensure_registered()在导入时自动注册全部基准

框架自带两个核心基准:

基准Registry 键度量内容
Latency(延迟)latency单次调用推理延迟(mean、p50、p95、min、max)
Throughput(吞吐)throughput每秒生成的 token 数

src/openjarvis/bench/__init__.py的实现可以看出,import openjarvis.bench时会触发ensure_registered(),把latencythroughputenergy三个基准全部写入注册表,因此无需任何手动初始化即可通过 CLI 使用。


二、BaseBenchmark 抽象基类:所有基准的统一契约

所有基准都必须继承 BaseBenchmark 抽象基类。它定义了两个抽象属性与一个抽象方法:

from abc import ABC, abstractmethod from openjarvis.bench._stubs import BenchmarkResult from openjarvis.engine._stubs import InferenceEngine class BaseBenchmark(ABC): @property @abstractmethod def name(self) -> str: """Short identifier for this benchmark.""" @property @abstractmethod def description(self) -> str: """Human-readable description of what this benchmark measures.""" @abstractmethod def run( self, engine: InferenceEngine, model: str, *, num_samples: int = 10, ) -> BenchmarkResult: """Execute the benchmark and return results."""

几点说明:

  • engine参数类型是InferenceEngine抽象基类(定义于src/openjarvis/engine/_stubs.py),它要求每个引擎实现同步generate()方法,返回包含contentusage字段的 dict;基准测试正是基于generate()进行计时与 token 统计。
  • 实际源码中run()还支持warmup_samples(预热样本数)与**kwargs透传,以便把energy_monitor等可选依赖注入给特定基准(见_stubs.py第 90-97 行)。

BenchmarkResult:统一结果结构

每次基准运行都会产出一个 BenchmarkResult:

字段类型说明
benchmark_namestr基准名称(如latency
modelstr被测模型
enginestr所用引擎后端(engine.engine_id
metricsdict[str, float]测得的指标键值对
metadatadict[str, Any]附加元数据
samplesint运行的样本数
errorsint遇到的错误数

源码中的BenchmarkResult还额外携带了能量相关的可选字段(warmup_samplestotal_energy_joulesenergy_per_token_joulesenergy_method等),由能量基准填充,普通基准保持默认值即可。


三、内置基准详解

3.1 Latency Benchmark:逐调用延迟

延迟基准使用短固定提示词测量单次调用延迟,每次样本都向引擎发送一个简单 prompt 并记录墙钟时间。其实现位于src/openjarvis/bench/latency.py

  • 提示词轮换:内置三句固定提示词"Hello""What is 2+2?""Explain gravity in one sentence"_CANNED_PROMPTS),按样本序号取模轮换,保证多次运行的输入分布一致;
  • 预热支持:在正式测量前可执行warmup_samples次预热请求,避免冷启动(如模型加载、显存分配)污染数据;
  • 异常处理:每次请求失败仅计数到errors,不会中断整个基准。

产出指标(经_stats.pycompute_stats("latency", latencies)计算):

指标说明
mean_latency所有成功样本的平均延迟
p50_latency中位数延迟(50 分位)
p95_latency95 分位延迟(尾部性能)
min_latency最快单次调用
max_latency最慢单次调用
std_latency延迟标准差(源码新增,衡量稳定性)

示例输出

latency (10 samples, 0 errors) mean_latency: 0.2345 p50_latency: 0.2100 p95_latency: 0.3800 min_latency: 0.1500 max_latency: 0.4200

从源码看,CLI 的 Rich 表格渲染器(bench_cmd.py中的_render_stats_table)会把mean_/p50_/p95_/min_/max_/std_前缀的指标自动聚合成 "Avg / Median / Min / Max / Std / P95" 多列表格,便于横向对比。

3.2 Throughput Benchmark:每秒 token 吞吐

吞吐基准向引擎发送一条较长的固定提示词"Write a short paragraph about artificial intelligence."_PROMPT),同时记录耗时与生成的完成 token 数。实现位于src/openjarvis/bench/throughput.py

  • 每次样本通过result.get("usage", {})读取completion_tokens
  • 单样本吞吐 =completion_tokens / elapsed
  • 与延迟基准一样,也支持预热,并会调用engine_info()把引擎的自描述信息(如 SDK 版本、上下文大小、宿主芯片)写入metadata["engine_info"]

产出指标

指标说明
tokens_per_second总完成 token 数 ÷ 总耗时(同时输出 mean/p50/p95/min/max/std 统计族)
total_tokens全部样本的完成 token 总数
total_time_seconds全部样本的墙钟总耗时
latency_seconds每样本延迟统计族(源码新增)

示例输出

throughput (10 samples, 0 errors) tokens_per_second: 45.6789 total_tokens: 1250.0000 total_time_seconds: 27.3600

3.3 统计口径说明

compute_stats()src/openjarvis/bench/_stats.py)是全部基准共享的统计核心:分位数采用线性插值法计算,即对排序后的样本,位置k = (len-1) * p,在两相邻值之间线性插值,这比简单的最近邻分位更精确;同时输出std_标准差以量化抖动。p50直接使用statistics.median


四、结果解读指南

延迟指标

  • mean_latency:平均响应时间,适合做总体性能对比;
  • p50_latency(中位数):典型响应时间,受离群值影响小于均值;
  • p95_latency:95% 请求的最坏响应时间,直接关系用户体验——若过高,部分用户会感知明显卡顿;
  • min/max_latency:最佳与最差单次调用,两者差距过大说明性能不一致。

健康度提示:一个健康的配置通常满足p95 / p50 < 2。若 p95 远高于中位数,建议排查引擎是否出现资源争抢(contention)、热降频(thermal throttling)或内存压力(memory pressure)。

吞吐指标

  • tokens_per_second:核心吞吐指标,越高越好。常见量级参考:
    • 纯 CPU:5–20 tokens/秒
    • 消费级 GPU(RTX 3060–4090):30–100 tokens/秒
    • 数据中心 GPU(A100、H100):100–500+ tokens/秒
  • total_tokens / total_time:吞吐计算背后的原始数据,可用于校验引擎确实产出了有效输出(而非返回空响应)。

以上量级为文档给出的典型参考区间,实际表现取决于模型参数量、量化方式、批处理大小与系统负载,请以本机实测为准。


五、BenchmarkSuite:批量运行与序列化

BenchmarkSuitesrc/openjarvis/bench/_stubs.py第 101-153 行)负责运行一组基准并提供聚合、序列化工具:

from openjarvis.bench._stubs import BenchmarkSuite from openjarvis.bench.latency import LatencyBenchmark from openjarvis.bench.throughput import ThroughputBenchmark suite = BenchmarkSuite([LatencyBenchmark(), ThroughputBenchmark()]) # 运行全部基准 results = suite.run_all(engine, model, num_samples=20) # 序列化为 JSONL(每行一个 JSON 对象) jsonl = suite.to_jsonl(results) # 获取摘要 dict summary = suite.summary(results)

方法一览

方法返回类型说明
run_all(engine, model, num_samples=10)list[BenchmarkResult]顺序运行全部基准
to_jsonl(results)str序列化为 JSONL 格式
summary(results)dict[str, Any]生成摘要字典

JSONL 输出格式

每行一个 JSON 对象,便于按行追加到日志或统计系统:

{"benchmark_name": "latency", "model": "qwen3:8b", "engine": "ollama", "metrics": {"mean_latency": 0.234, "p50_latency": 0.21, "p95_latency": 0.38, "min_latency": 0.15, "max_latency": 0.42}, "metadata": {}, "samples": 10, "errors": 0} {"benchmark_name": "throughput", "model": "qwen3:8b", "engine": "ollama", "metrics": {"tokens_per_second": 45.67, "total_tokens": 1250.0, "total_time_seconds": 27.36}, "metadata": {}, "samples": 10, "errors": 0}

Summary 输出格式

{ "benchmark_count": 2, "benchmarks": [ { "name": "latency", "model": "qwen3:8b", "engine": "ollama", "metrics": {"mean_latency": 0.234, ...}, "samples": 10, "errors": 0 }, { "name": "throughput", "model": "qwen3:8b", "engine": "ollama", "metrics": {"tokens_per_second": 45.67, ...}, "samples": 10, "errors": 0 } ] }

六、CLI 用法:jarvis bench run

CLI 入口实现位于src/openjarvis/cli/bench_cmd.py。执行流程为:加载配置 → 注册全部基准 → 解析引擎与模型 → 组装BenchmarkSuite→ 运行并渲染/输出结果。

# 使用默认设置(10 个样本)运行全部基准 jarvis bench run # 增加样本数以提升统计精度 jarvis bench run -n 50 # 只运行延迟基准 jarvis bench run -b latency # 只运行吞吐基准,20 个样本 jarvis bench run -b throughput -n 20 # 指定模型与引擎 jarvis bench run -m qwen3:8b -e ollama # 以 JSON 摘要形式输出到 stdout jarvis bench run --json # 将 JSONL 结果写入文件 jarvis bench run -o results.jsonl # 组合使用 jarvis bench run -b latency -n 100 -m qwen3:8b --json -o latency.jsonl

选项参考

选项类型默认值说明
-m,--model MODELstringauto被测模型;省略时自动取引擎list_models()的第一个模型
-e,--engine ENGINEstringauto引擎后端;省略时从配置自动解析
-n,--samples Nint10每个基准的样本数
-b,--benchmark NAMEstringall指定运行的基准(latency/throughput/ 自定义名称)
-o,--output PATHpathnone将 JSONL 结果写入文件
--jsonflagoff以 JSON 摘要输出到 stdout
-w,--warmup Nint0测量前的预热迭代次数(源码新增,见bench_cmd.py第 183-190 行)

输出优先级:同时指定-o--json时,文件写入与 stdout 摘要都会产生;仅指定-o时不打印 Rich 表格,仅指定--json时输出摘要 JSON;两者都未指定时渲染为 Rich 统计表格。

bench_cmd.py源码看,jarvis bench run还支持通过--setup-energy自动运行scripts/setup-energy-monitor.sh初始化能量监控,并在telemetry.gpu_metrics开启或运行 energy 基准时自动挂载能量监视器;能量相关选项与jarvis bench skills(PinchBench 技能评估)命令不在本文档基准主题范围内,此处仅作提示。


七、添加自定义 Benchmark

自定义基准只需三步:继承BaseBenchmark实现逻辑,注册到BenchmarkRegistry,然后通过 CLI 或BenchmarkSuite使用。

7.1 实现基准类

以下示例实现一个"输入长度越长、延迟越高"的context_length基准:

import time from openjarvis.bench._stubs import BaseBenchmark, BenchmarkResult from openjarvis.core.registry import BenchmarkRegistry from openjarvis.core.types import Message, Role from openjarvis.engine._stubs import InferenceEngine class ContextLengthBenchmark(BaseBenchmark): """Measures how latency scales with input length.""" @property def name(self) -> str: return "context_length" @property def description(self) -> str: return "Measures latency scaling with increasing input length" def run( self, engine: InferenceEngine, model: str, *, num_samples: int = 10, ) -> BenchmarkResult: latencies = {} errors = 0 for length in [100, 500, 1000, 2000]: prompt = "x " * length messages = [Message(role=Role.USER, content=prompt)] t0 = time.time() try: engine.generate(messages, model=model) latencies[f"latency_{length}_tokens"] = time.time() - t0 except Exception: errors += 1 return BenchmarkResult( benchmark_name=self.name, model=model, engine=engine.engine_id, metrics=latencies, samples=len(latencies), errors=errors, )

7.2 注册基准

推荐使用ensure_registered()模式,它在测试清空注册表后依然能存活:

def ensure_registered() -> None: """Register the benchmark if not already present.""" if not BenchmarkRegistry.contains("context_length"): BenchmarkRegistry.register_value("context_length", ContextLengthBenchmark)

也可以在类定义时直接使用装饰器:

@BenchmarkRegistry.register("context_length") class ContextLengthBenchmark(BaseBenchmark): ...

为什么优先用ensure_registered()BenchmarkRegistry继承自RegistryBasesrc/openjarvis/core/registry.py),其中clear()会在测试中清空全部条目;装饰器只在模块导入时执行一次,注册表一旦被清空就无法恢复。内置的latencythroughputenergy基准全部采用ensure_registered()模式(见各自文件末尾与bench/__init__.py),CLI 在查找基准前也会先调用ensure_registered()。测试用例tests/bench/test_latency.py同样通过 autouse fixture 在注册表清理后重新注册,验证了这一模式的必要性。注意:若 key 已存在,register_valueregister都会抛出ValueError,因此"先contains检查再注册"是幂等安全的写法。

7.3 使用你的基准

注册完成后即可通过 CLI 直接调用:

jarvis bench run -b context_length

也可以通过BenchmarkSuite或注册表按需实例化:

from openjarvis.core.registry import BenchmarkRegistry bench_cls = BenchmarkRegistry.get("context_length") bench = bench_cls() result = bench.run(engine, model, num_samples=5)

由于jarvis bench run在未指定-b时会遍历注册表运行全部基准(见bench_cmd.py第 246 行),新注册的基准会自动纳入默认全量运行。


八、延伸:框架的可扩展设计

从源码结构可以进一步归纳该框架的三个设计要点:

  1. 注册表驱动发现BenchmarkRegistryRegistryBase的类型化子类之一,与EngineRegistryToolRegistry等共享同一套注册逻辑(src/openjarvis/core/registry.py),每个注册表有独立的条目存储,互不串扰;
  2. 引擎抽象解耦:基准只依赖InferenceEngine.generate()契约,因此天然适用于 Ollama、vLLM、MLX 等任何已注册引擎,无需为基准适配特定后端;
  3. 统计与渲染分离:指标计算统一收敛到_stats.py,CLI 侧通过识别mean_/p50_/p95_/min_/max_/std_前缀自动分组渲染成统计表格,新增基准只要沿用compute_stats()命名约定即可获得一致的终端展示。

九、总结

OpenJarvis 的基准测试框架以BaseBenchmark为统一契约、BenchmarkRegistry为发现机制、BenchmarkSuite为聚合入口,配合jarvis bench runCLI,能够在不同引擎、模型与样本量之间建立可复现的性能对照。理解p95/p50比例与 token 吞吐量级,可以帮助你判断本地部署是否遇到资源争抢或热降频;而ensure_registered()+ 注册表的扩展范式,则让自定义基准可以无缝接入 CLI、Suite 与测试体系,成为一套既能开箱即用、又能深度定制的推理性能测量工具链。

</|DSML|parameter> </|DSML|invoke> </|DSML|tool_calls>

【免费下载链接】OpenJarvis

Personal AI, On Personal Devices

项目地址:https://gitcode.com/gh_mirrors/op/OpenJarvis
点击查看免费下载

相关推荐

上一篇:3个简单步骤掌握Python通达信数据读取:mootdx金融分析终极指南
下一篇:CKEditor 5 CKBox 集成全指南:文件管理、图片上传与图片编辑一体化接入

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 22:30:38

YOLOv11智慧工地实战(二):工程车行为分析与作业区域识别算法详解及系统整合部署全流程开发指南

🎪 摸鱼匠:个人主页 🎒 个人专栏:《YOLOv11实战专栏》 🥇 没有好的理念,只有脚踏实地! 文章目录 四、工程车行为分析与作业区域识别 4.1 工程车行为分析基础 4.1.1 运动状态分类 4.1.2 作业区域识别 4.1.3 工作模式分析 4.2 运动状态识别算法 4.3 作业区域识别算法…

作者头像 李华
网站建设 2026/9/24 22:29:14

训练速度慢不一定是显卡的锅:GPU租用平台选型与迁移实战

“训练速度慢”这四个字&#xff0c;几乎是每个搞深度学习的人都会撞上的墙。模型迭代到第三版&#xff0c;loss曲线死活下不去&#xff1b;明明加了数据增强&#xff0c;一个epoch却从20分钟变成40分钟&#xff1b;转头看任务管理器&#xff0c;GPU占用率在60%和90%之间反复横…

作者头像 李华
网站建设 2026/9/24 22:29:11

Java常用类核心要点:包装类、BigDecimal精度与随机数实战

1. 包装类到底解决什么问题——先聊设计思路1.1 基本类型不是对象&#xff0c;集合又只收对象Java有两套类型体系&#xff1a;一套是基本类型&#xff08;int、double、boolean这些&#xff09;&#xff0c;另一套是引用类型&#xff08;String、数组、各种类对象&#xff09;。…

作者头像 李华
网站建设 2026/9/24 22:29:00

情绪Alpha:如何把市场情绪变成可交易的量化因子

量化圈子里聊“因子”&#xff0c;聊到后来基本就两类&#xff1a;一类是量价&#xff0c;一类是基本面。但最近几年&#xff0c;大家开始高频地提一个词叫“情绪 Alpha”。第一次看到这个标题的时候&#xff0c;我第一反应是&#xff1a;这不就是量化里那句老话“别人贪婪我恐…

作者头像 李华
网站建设 2026/9/24 22:28:59

浏览器端3D姿态检测实战:BlazePose与TensorFlow.js实现

把姿态检测从2D升级到3D&#xff0c;这件事本身听起来不算新鲜&#xff0c;但真正落地到浏览器里、还要实时跑、并且能拿到底层人体模型的3D参数&#xff0c;那就是另一回事了。我最近把一个运动分析项目从Python端整体迁到了浏览器端&#xff0c;用的就是MediaPipe的BlazePose…

作者头像 李华
网站建设 2026/9/24 22:28:41

纯前端实现实时3D人体姿态估计:MediaPipe BlazePose与TensorFlow.js实战

在浏览器里做人体姿态估计&#xff0c;前几年基本还停留在调用远端API或者后端跑Python推理的阶段。今天这篇换个路子&#xff0c;咱们纯前端、纯JavaScript&#xff0c;直接调用MediaPipe的BlazePose模型&#xff0c;配合GHUM人体参数模型拿到的3D关键点&#xff0c;再用Tenso…

作者头像 李华