OpenMed Transformers.js 导出:将 ONNX 临床 NER 模型打包为浏览器端 Token-Classification 推理包
【免费下载链接】openmedLocal-first healthcare AI: clinical NER & HIPAA PII de-identification that runs 100% on-device. 2,200+ medical models, 21 languages, Apple MLX + Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed
导读
本文讲解 OpenMed 如何将 ONNX token-classification 产物打包为 Transformers.js 可直接加载的浏览器推理包(transformersjs/目录),覆盖"从零转换"与"基于既有 ONNX 导出增量打包"两条路径、图契约校验、Python/Node 双重验证,以及浏览器端pipeline("token-classification", ...)的加载方式。读完本文,你将掌握如何把临床 NER / HIPAA PII 脱敏模型导出为纯前端可运行的本地优先推理资产,让 PHI 始终停留在用户浏览器会话内。
背景:浏览器端本地优先推理的打包需求
OpenMed 的 ONNX 转换子系统 负责把 Hugging Face token-classification 检查点导出为多种运行时格式(fp32 ONNX、WebGPU fp16、Android ONNX Runtime、OpenVINO IR 等)。其中,面向浏览器场景的格式由 openmed/onnx/transformersjs.py 完成:它从 ONNX 导出目录生成一份 Transformers.js 兼容的 bundle,包含浏览器 pipeline 期望的完整文件布局,并在写入前对 ONNX 图的张量契约进行严格校验。
从源码结构看,
openmed.onnx.convert与openmed.onnx.transformersjs是两个正交的入口:convert面向"导出 ONNX + 附带打包",transformersjs面向"仅基于已有 ONNX 产物增量打包",二者最终都会调用export_transformersjs_bundle(见 convert.py#L548-L563 与 transformersjs.py#L74-L178)。
Bundle 文件布局
打包产物是transformersjs/目录,文件布局如下:
transformersjs/ config.json tokenizer.json tokenizer_config.json quantize_config.json transformersjs-contract.json onnx/ model.onnx model_quantized.onnx其中model_quantized.onnx默认由 onnxruntime 对model.onnx做动态 INT8 权重量化生成(quantize_dynamic(..., weight_type=QuantType.QInt8),见 transformersjs.py#L427-L442)。源码中REQUIRED_BUNDLE_FILES(transformersjs.py#L31-L38)把上述 6 个必需文件定义为契约的一部分:
REQUIRED_BUNDLE_FILES = ( "onnx/model.onnx", "onnx/model_quantized.onnx", "tokenizer.json", "tokenizer_config.json", "config.json", "quantize_config.json", )tokenizer.json是硬性要求——它是 Transformers.js 在浏览器端执行快速分词所必需的 fast tokenizer 资产。打包逻辑会尝试从源导出目录复制 9 类分词器资产(tokenizer.json、tokenizer_config.json、special_tokens_map.json、added_tokens.json、vocab.txt、vocab.json、merges.txt、spiece.model、sentencepiece.bpe.model);若tokenizer.json缺失,则退回到用AutoTokenizer.from_pretrained(..., use_fast=True)从模型 ID 重新保存,且明确要求 fast tokenizer(transformersjs.py#L356-L405)。
图契约:浏览器 pipeline 校验什么
转换器会校验 ONNX 图是否暴露 token-classification 契约(transformersjs.py#L218-L290):
- 输入:
input_ids与attention_mask(必需),token_type_ids(可选) - 输出:仅
logits(不允许存在其他输出) - 动态轴:输入为
[batch, sequence](两轴都必须动态);logits为[batch, sequence, labels],其中batch、sequence动态,labels必须是静态轴 - opset 下限:要求 ONNX 模型 opset >= 18(
MINIMUM_TOKEN_CLASSIFICATION_OPSET = 18,transformersjs.py#L26),opset 17 及更旧的图会被直接拒绝
校验通过后,契约数据写入transformersjs-contract.json,结构形如:
{ "task": "token-classification", "format": "transformersjs", "minimum_opset": 18, "model_opset": 18, "inputs": [ {"name": "input_ids", "axes": ["batch", "sequence"], "shape": [{"kind": "dynamic", "name": "batch"}, {"kind": "dynamic", "name": "sequence"}]}, {"name": "attention_mask", "axes": ["batch", "sequence"], "shape": [{"kind": "dynamic", "name": "batch"}, {"kind": "dynamic", "name": "sequence"}]} ], "outputs": [ {"name": "logits", "axes": ["batch", "sequence", "labels"], "shape": [{"kind": "dynamic", "name": "batch"}, {"kind": "dynamic", "name": "sequence"}, {"kind": "static", "value": 3}]} ] }说明:
labels轴静态是因为它由config.json中的id2label标签表唯一决定(如{"0": "O", "1": "B-NAME", "2": "I-NAME"}),浏览器端解码 logits 需要确定性的类别数。
此外,config.json会被规范化:id2label必须是非空映射,同时补写label2id反查表与task字段(默认token-classification),见 transformersjs.py#L408-L424。
路径一:从全新 ONNX 转换打包
在创建 ONNX 产物时直接加--include-transformersjs:
.venv/bin/python -m openmed.onnx.convert \ --model dslim/bert-base-NER \ --output dist/example-onnx \ --include-transformersjs该参数在 convert.py#L1569-L1573 注册,转换流程内部调用export_transformersjs_bundle生成dist/example-onnx/transformersjs,并把transformersjs作为一条 artifact 记录进openmed-onnx.json清单(convert.py#L548-L563)。清单中的 formats 列表(如["onnx", "webgpu", "transformersjs"])随后可以被发布流程整体携带,供publish_to_hub等后续步骤使用(convert.py#L620-L637)。
测试 tests/unit/onnx/test_transformersjs_export.py#L133-L202 验证了这条路径:include_transformersjs=True时,最终 formats 为["onnx", "webgpu", "transformersjs"],清单中transformersjsartifact 的记录为{"format": "transformersjs", "path": "transformersjs", "precision": "int8"},且该格式列表被完整透传给发布调用。
路径二:基于既有 ONNX 导出增量打包
如果导出目录已存在model.onnx、config.json、tokenizer.json与tokenizer_config.json,可以只构建浏览器 bundle:
.venv/bin/python -m openmed.onnx.transformersjs \ --onnx-export-dir dist/example-onnx默认输出到dist/example-onnx/transformersjs,并更新源目录下的openmed-onnx.json(若存在);传--no-manifest-update则保持源清单不变。CLI 的全部参数见 transformersjs.py#L293-L353:
| 参数 | 默认值 | 说明 |
|---|---|---|
--onnx-export-dir | 必填 | 含model.onnx、config.json与分词器资产的目录 |
--output | <onnx-export-dir>/transformersjs | 目标 bundle 目录 |
--tokenizer-source | ONNX 导出目录 | 已保存分词器目录或 HF 模型 ID |
--config | <onnx-export-dir>/config.json | 源config.json路径 |
--no-quantize | 关闭 | 不量化,直接把model.onnx复制为model_quantized.onnx |
--no-manifest-update | 关闭 | 不修改源目录的openmed-onnx.json |
--minimum-opset | 18 | bundle 契约要求的最低 opset |
--segmenter | 无 | 打包紧凑的 Han / Indic 分词器资源集(见下节) |
底层函数export_transformersjs_bundle(transformersjs.py#L74-L178)还支持onnx_filename(源 ONNX 文件名,默认model.onnx)、quantize、update_manifest、segmenter_id等关键字参数,供程序化调用。
清单更新行为
更新openmed-onnx.json时,若formats列表尚未包含transformersjs则追加;若artifacts中没有transformersjs条目则新增,precision字段记录实际精度(量化时为int8,--no-quantize时为float32),见 transformersjs.py#L518-L557。quantize_config.json则固定记录量化元数据(algorithm: "dynamic"、weight_type: "qint8"或"copied"),见 transformersjs.py#L505-L515。
可选:打包紧凑分词器资源集
对于中英文混排等场景,--segmenter可以把紧凑的 Han / Indic 分词器资源一并装入 bundle,可选值来自 openmed/processing/tokenization.py#L70-L74:
openmed-han-v1:Han 脚本分词词表(MIT)openmed-indic-v1:Indic(天城文)断词规则(ICU,含许可声明文件)openmed-cjk-indic-v1:Han + Devanagari 组合(默认值,MIT AND ICU)
打包后 bundle 内会多出一个segmenter/资源目录,并在openmed-onnx.json中写入segmenter描述符(含资源文件路径与许可信息)。测试 test_transformersjs_export.py#L93-L117 验证了该行为:segmenter_id="openmed-cjk-indic-v1"时 bundle 清单记录license: "MIT AND ICU",且segmenter/ICU.txt文件确实落盘。
验证:Python 校验器与 Node smoke fixture
打包完成后可用 Python 校验器做全面检查,覆盖必需文件、config.json标签元数据与 ONNX 图契约:
from openmed.onnx import validate_transformersjs_bundle validate_transformersjs_bundle("dist/example-onnx/transformersjs")validate_transformersjs_bundle(transformersjs.py#L181-L208)会依次执行:文件完整性检查(find_missing_bundle_files)、id2label非空校验、可选segmenter资源校验,以及 ONNX 图契约校验。单测覆盖了关键失败路径:静态sequence轴被拒绝(test_transformersjs_export.py#L80-L90)、opset 17 被拒绝(test_transformersjs_export.py#L120-L130)。
仓库还自带一个无头 Node smoke fixture tests/fixtures/onnx/transformersjs_smoke.mjs,用法:
node tests/fixtures/onnx/transformersjs_smoke.mjs dist/example-onnx/transformersjs它独立复述同一套契约:校验 7 个必需文件、config.json含非空id2label、quantize_config.json标识transformersjs格式、输入含input_ids/attention_mask且前两维动态、输出唯一且为logits([batch, sequence, labels],labels 静态)。全部通过后打印Transformers.js token-classification contract ok。该 fixture 由测试 test_transformersjs_export.py#L241-L263 驱动,在 Node 可用时对真实打包结果执行端到端校验。
浏览器端使用
将transformersjs/目录通过静态服务器暴露(或放入模型资产路径)后,即可用 Transformers.js 加载:
import { pipeline } from "@huggingface/transformers"; const detector = await pipeline( "token-classification", "/models/openmed-pii/transformersjs", { device: "webgpu" }, ); const entities = await detector("Patient Casey Example called 212-555-0198.");- 加载路径指向
transformersjs目录本身(即含config.json的那一层),浏览器端会依据config.json/tokenizer.json/onnx/完成 pipeline 装配,并自动选择model_quantized.onnx(INT8)以减小下载体积。 - 设备选择:
device: "webgpu"让推理走 WebGPU 后端;也可省略该选项由 Transformers.js 按运行时能力回退到 WebAssembly 等执行后端。 - 数据边界:该 bundle 专为本地优先(local-first)token 分类设计——PHI 在用户浏览器会话内完成检测与脱敏,不外发到任何服务器。
- 注意事项:导出步骤只负责打包模型资产,不改变 Hugging Face 仓库的可见性设置;浏览器端可用的 token 分类能力同时受模型本身(如多语言覆盖)与 WebGPU/WASM 运行时支持范围约束,实际精度以所导出检查点为准。
离线演练:无模型下载的合成走查
无需下载模型、不发起网络请求即可查看期望的 bundle 文件清单与浏览器加载代码片段:
uv run python examples/v17_multimodal_browser_interop.py该示例(examples/v17_multimodal_browser_interop.py)使用合成数据与内置假引擎执行公开 API;其run_transformersjs_example(L205-L213)通过find_missing_bundle_files打印期望的 7 个必需文件,并输出BROWSER_PIPELINE_SNIPPET(即上文浏览器加载代码),输出 JSON 中transformersjs一节即为此演示结果,适合作为 CI 或本地快速验证的参照。
小结
OpenMed 的 Transformers.js 导出把 ONNX 图契约、分词器资产、INT8 量化与清单管理收敛为一个自校验的transformersjs/目录:
- 两种入口:
convert --include-transformersjs(转换时同步打包)或transformersjs --onnx-export-dir(既有产物增量打包); - 一道契约:
input_ids/attention_mask(+可选token_type_ids)→logits,batch/sequence 动态、labels 静态、opset ≥ 18,由 Python 校验器与 Node smoke fixture 双重把关; - 一种消费方式:静态目录 +
pipeline("token-classification", ...),配合device: "webgpu"实现纯浏览器端的本地优先 PHI 脱敏推理。
相关实现与测试可继续在仓库中深挖:openmed/onnx/transformersjs.py、openmed/onnx/convert.py、tests/unit/onnx/test_transformersjs_export.py、tests/fixtures/onnx/transformersjs_smoke.mjs。
【免费下载链接】openmedLocal-first healthcare AI: clinical NER & HIPAA PII de-identification that runs 100% on-device. 2,200+ medical models, 21 languages, Apple MLX + Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考