【免费下载链接】OpenJarvis
Personal AI, On Personal Devices
OpenJarvis 内置了一套可复现、标准化的推理引擎性能基准测试框架,用于量化本地 LLM 推理引擎(如 Ollama、vLLM 等)的延迟与吞吐能力。本文将带你掌握其两大内置基准(Latency / Throughput)的指标含义与统计口径,理解BaseBenchmark抽象基类与BenchmarkSuite聚合器的底层实现,熟练使用jarvis bench run命令行完成样本数、引擎、模型的自由组合,并通过三步走自定义属于自己的 Benchmark 并注册到 CLI 与 Suite 中。
一、基准测试框架概览:定位与组成
OpenJarvis 的基准测试框架(benchmarking framework)用标准化、可复现的测试来度量推理引擎性能。它位于src/openjarvis/bench/目录,核心模块如下:
| 文件 | 职责 |
|---|---|
_stubs.py | BaseBenchmark抽象基类、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(),把latency、throughput、energy三个基准全部写入注册表,因此无需任何手动初始化即可通过 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()方法,返回包含content与usage字段的 dict;基准测试正是基于generate()进行计时与 token 统计。- 实际源码中
run()还支持warmup_samples(预热样本数)与**kwargs透传,以便把energy_monitor等可选依赖注入给特定基准(见_stubs.py第 90-97 行)。
BenchmarkResult:统一结果结构
每次基准运行都会产出一个 BenchmarkResult:
| 字段 | 类型 | 说明 |
|---|---|---|
benchmark_name | str | 基准名称(如latency) |
model | str | 被测模型 |
engine | str | 所用引擎后端(engine.engine_id) |
metrics | dict[str, float] | 测得的指标键值对 |
metadata | dict[str, Any] | 附加元数据 |
samples | int | 运行的样本数 |
errors | int | 遇到的错误数 |
源码中的BenchmarkResult还额外携带了能量相关的可选字段(warmup_samples、total_energy_joules、energy_per_token_joules、energy_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.py的compute_stats("latency", latencies)计算):
| 指标 | 说明 |
|---|---|
mean_latency | 所有成功样本的平均延迟 |
p50_latency | 中位数延迟(50 分位) |
p95_latency | 95 分位延迟(尾部性能) |
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.36003.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:批量运行与序列化
BenchmarkSuite(src/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 MODEL | string | auto | 被测模型;省略时自动取引擎list_models()的第一个模型 |
-e,--engine ENGINE | string | auto | 引擎后端;省略时从配置自动解析 |
-n,--samples N | int | 10 | 每个基准的样本数 |
-b,--benchmark NAME | string | all | 指定运行的基准(latency/throughput/ 自定义名称) |
-o,--output PATH | path | none | 将 JSONL 结果写入文件 |
--json | flag | off | 以 JSON 摘要输出到 stdout |
-w,--warmup N | int | 0 | 测量前的预热迭代次数(源码新增,见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继承自RegistryBase(src/openjarvis/core/registry.py),其中clear()会在测试中清空全部条目;装饰器只在模块导入时执行一次,注册表一旦被清空就无法恢复。内置的latency、throughput、energy基准全部采用ensure_registered()模式(见各自文件末尾与bench/__init__.py),CLI 在查找基准前也会先调用ensure_registered()。测试用例tests/bench/test_latency.py同样通过 autouse fixture 在注册表清理后重新注册,验证了这一模式的必要性。注意:若 key 已存在,register_value与register都会抛出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 行),新注册的基准会自动纳入默认全量运行。
八、延伸:框架的可扩展设计
从源码结构可以进一步归纳该框架的三个设计要点:
- 注册表驱动发现:
BenchmarkRegistry是RegistryBase的类型化子类之一,与EngineRegistry、ToolRegistry等共享同一套注册逻辑(src/openjarvis/core/registry.py),每个注册表有独立的条目存储,互不串扰; - 引擎抽象解耦:基准只依赖
InferenceEngine.generate()契约,因此天然适用于 Ollama、vLLM、MLX 等任何已注册引擎,无需为基准适配特定后端; - 统计与渲染分离:指标计算统一收敛到
_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
相关推荐
SeaTunnel Zeta 引擎基准测试(Benchmark)完全指南:架构、指标解读与本地执行
SeaTunnel Zeta 引擎基准测试(Benchmark)完全指南:架构、指标解读与本地执行 SeaTunnel 的 Zeta 引擎在数据量增长和使用场景
数据集成ETL大数据批处理流处理变更数据捕获如何15分钟搭建个人微信公众号RSS订阅服务:终极指南
如何15分钟搭建个人微信公众号RSS订阅服务:终极指南 你是否厌倦了在微信、浏览器和各种阅读器之间来回切换,只为追踪几个喜欢的公众号更新?信息碎片化让有价值的内
后端前端vite-vue3-chrome-extension-v3发布指南:从打包到Chrome商店上架全流程
vite vue3 chrome extension v3发布指南:从打包到Chrome商店上架全流程 想要将你的Vue 3 Chrome扩展发布到Chrome
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考