EdgeJev
在本地设备上跑 Jev 式的类型化决策。ONNX + INT8,运行时不需要 torch。
Jev 不生成文本:给一份 state 和几个带类型的问题,一次前向答完,返回能直接 if 的值加一个校准概率。
EdgeJev 把各家开源复现统一成「转换 → 量化 → 部署」一条路径。
4 vCPU Xeon 上 laya-multilingual(322M)单题 15.6 ms,模型 324 MB。
安装
还没发到 PyPI,从 GitHub 装:
# uv(推荐,自己管虚拟环境)
uv tool install "git+https://github.com/yzfly/edgejev"
# 或装进项目的虚拟环境
uv venv && uv pip install "git+https://github.com/yzfly/edgejev"
# pipx
pipx install "git+https://github.com/yzfly/edgejev"
# 原生 venv(Debian/Ubuntu 直接 pip 会报 externally-managed-environment)
python3 -m venv .venv && .venv/bin/pip install "git+https://github.com/yzfly/edgejev"
可选依赖按需加,uv pip install "edgejev[build] @ git+https://github.com/yzfly/edgejev":
| extra | 用途 |
|---|---|
build |
edgejev build 转 ONNX,需要 torch / transformers / laya |
vlm |
playjev 后端,需要 torch / torchvision / transformers / pillow |
train |
edgejev.train 训练模块 |
不加 extra 时运行时只有 onnxruntime + tokenizers + numpy。
用法
edgejev build --backend laya --out ./jev-int8
from edgejev import Agent
ag = Agent("./jev-int8")
r = ag.system_one("我的信用卡被扣了两次款,麻烦退一笔。", {
"dept": {"type": "choice", "instructions": "该转给哪个组?",
"criteria": {"billing": "支付、扣款、发票、退款",
"technical": "程序缺陷、报错",
"sales": "售前咨询、定价"}},
"urgent": {"type": "noul", "instructions": "这条消息表达了紧急或时间压力"},
"anger": {"type": "score", "instructions": "客户的不满程度",
"criteria": ["平静陈述", "有情绪但讲道理", "非常愤怒"]},
})
r["answers"]["dept"]["choice"] # 'billing'
r["answers"]["dept"]["probabilities"] # {'billing': 0.92, 'technical': 0.08, 'sales': 0.00}
r["answers"]["anger"]["confidence"] # 0.41
官方协议端点:
edgejev serve --model ./jev-int8 --port 8009
export TYPESAFE_BASE_URL=http://127.0.0.1:8009
export TYPESAFE_API_KEY=local
命令
| 命令 | 作用 |
|---|---|
edgejev build |
checkpoint → ONNX → 量化 → 多形状自检 |
edgejev serve |
POST /v1/systemone |
edgejev eval |
AG News / dair-ai emotion 上跑指标 |
edgejev bench |
测延迟 |
edgejev info |
provider 与已注册后端 |
后端
| 后端 | 骨干 | 读出方式 | 运行时 | 状态 |
|---|---|---|---|---|
laya |
mmBERT-base 322M | 每题一行,读 [MASK] 标记位 |
ONNX | ✅ |
playjev |
Qwen3.5-0.8B VLM | 画面输入,读最后位置的字母槽 logits | torch | ✅ |
kev |
Qwen + LoRA 0.5B–8B | 多题打包,block-causal,PointerHead | ONNX | 🚧 |
playjev 走 torch:Qwen3.5 的文本塔是混合线性注意力(layer_types 含 linear_attention),
依赖 causal_conv1d / flash-linear-attention 的递归状态核,没有对应的 ONNX 算子。
用这个后端只统一 API 和 serve,没有量化加速。
pip install 'edgejev[vlm]'
edgejev build --backend playjev --out ./playjev # 只落配置,不导 ONNX
ag = Agent("./playjev")
ag.system_one("frame.png", { # state 传图片路径 / URL / bytes / PIL / ndarray
"move": {"type": "choice", "instructions": "Which move should the player make next?",
"criteria": {"left": "move the paddle left", "right": "move the paddle right",
"stay": "keep the paddle still"}}})
4 vCPU CPU 上单次决策约 1.2 s(未装 causal_conv1d / flash-linear-attention 优化核,
transformers 回退到参考实现)。返回里多一个 allowed_mass:全词表 softmax 落在 K 个字母槽上的质量。
这个模型的基座是 base 而非 instruct,概率质量天然分散,allowed_mass 在 1e-4 量级属正常,
有判别力的是字母之间的相对排序。实测五个游戏的缩略图,argmax 与分布形状各不相同,
max(p) 与上游回放记录的真实对局分布(167 步,中位 0.827)落在同一区间。
加后端=在 edgejev/backends/ 写一个模块并在注册表登记。
基准
4 vCPU Intel Xeon Cascade Lake(AVX512-VNNI),laya-multilingual。
延迟(batch=1,seq≈36):
| 精度 | 体积 | 单题 | 三题 | 每题 |
|---|---|---|---|---|
| fp32 | 1290 MB | 32.1 ms | 84.1 ms | 28.0 ms |
| int8(默认,per-tensor) | 324 MB | 15.6 ms | 44.8 ms | 14.9 ms |
| int8-pc | 325 MB | 16.7 ms | 46.5 ms | 15.5 ms |
| mixed | 366 MB | 19.2 ms | 53.0 ms | 17.7 ms |
| uint8-pc | 325 MB | 27.9 ms | 83.4 ms | 27.8 ms |
准确率(各 400 条,edgejev eval --task all --n 400):
| AG News | emotion | |
|---|---|---|
| laya fp32 | 92.8% | 54.0% |
| laya int8 | 91.0% | 50.0% |
| Jev 1.13(Vercel AI Gateway) | 85.5% | 61.5% |
| Jev 1.13(classifier.dev) | 88.0% | 62.7% |
--precision fp32 与上游 laya(PyTorch)逐位一致,最大概率偏差 0.00000。
已知问题
动态量化的结果依赖 batch。 激活 scale 在运行时按实际张量算,padding 变了 scale 就变:
| 单条 vs 批量 logits 最大差 | |
|---|---|
| 上游 laya(PyTorch) | 1e-06 |
| EdgeJev fp32 ONNX | 0.000e+00 |
| EdgeJev int8 动态 | 2.43 |
Agent.system_one 是单请求路径,上面的指标不受影响。自己写批量推理的固定 batch=1 或用 fp32。
edgejev build 会检查这一项并在不满足时警告。
--precision int8-static 不要用。 批次无关(漂移 0.0e+00)但 MinMax 标定掉到随机水平:
AG News 25.8%(随机 25%)、emotion 29.8%、延迟 195.5 ms。保留是为了后续换 Percentile / Entropy 标定。
别用 QUInt8。 同 8 bit 同体积,x86 的 VNNI 只对有符号 int8 有快路径:QUInt8 27.9 ms、QInt8 15.6 ms。 ARM 走 SDOT,不适用。
保留嵌入表不量化没有收益。 322M 里 196.6M 是 256k 词表的嵌入表,保留后精度没变(91.0% / 51.5%), 体积从 325 MB 涨到 915 MB。
fp16 在 x86 CPU 上没有意义。 没有 avx512_fp16,ORT 的 CPU EP 转回 fp32 算;
且 onnxconverter_common 的 fp16 pass 处理不了 dynamo 图里的 _to_copy 节点,转出来加载失败。
导出必须用 dynamo。 旧的 torch.onnx.export 把 head 里 nn.MultiheadAttention 的 batch/seq
固化成导出时的形状,换输入长度就抛 Reshape 错,而用同一批次做数值比对时误差 3.87e-06 看不出来。
edgejev build 因此强制跑多形状自检。
平台
| 平台 | Execution Provider |
|---|---|
| Linux / Windows x86 | CPU(AVX512-VNNI / AVX2) |
| macOS Apple Silicon | CoreML,不支持的算子回退 CPU |
| Linux ARM | CPU(int8 走 SDOT) |
edgejev info 看实际选用的;--provider cpu 或 EDGEJEV_PROVIDER=cpu 强制。
训练
edgejev.train 与推理共用同一个渲染器,训练序列和线上请求逐 token 相同。
losses.py—— 严格恰当评分规则(log score + spherical;score 型加 ranked probability score)。 优化校准而不是 argmax 正确。另含 ECE 与可靠性分桶。data.py—— 硬标签与软标签统一成目标分布。model.py—— 任意 HF 编码器 + 两层 transformer head + marker 打分头,训完可直接被edgejev build --backend laya导出。
许可
Release files for edgejev 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| edgejev-0.1.0.tar.gz | 34.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| edgejev-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 71.7 kB
Release files / edgejev-0.1.0.tar.gz
| Download URL | edgejev-0.1.0.tar.gz |
|---|---|
| Size | 34.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ab8a506a826450e12a835290b41316d588af3ddd6748330669db035ccb06f1cb
|
|
BLAKE2b-256 checksum How to use checksums |
0ef9c009fef933b2f3289a21ee45c50900e0cc91a3c042e7b11827c09c52fde2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / edgejev-0.1.0-py3-none-any.whl
| Download URL | edgejev-0.1.0-py3-none-any.whl |
|---|---|
| Size | 37.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5c58560292b046850c8fdd899654cb1c0a85a82fb399dc00e2f770740866ecd0
|
|
BLAKE2b-256 checksum How to use checksums |
8cb6921f9309cbee561fe7135aa85ca99b69b75bdace20e4b60d5aaeb36db57f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|