基于 OpenMed 中文 PII ONNX 模型的本地 REST API。服务使用 FP32 模型,可通过 ONNX Runtime 的 CPU 或 NVIDIA CUDA Execution Provider 进行推理,也不会把病例材料发送到外部服务。
API 支持超过模型 512-token 上限的长病例:服务会按 480 token 分窗,相邻窗口重叠 64 token,随后恢复实体在完整原文中的字符偏移并过滤重叠结果。
- Git
- Git LFS
- uv
- Python 3.12,由 uv 自动管理
- CUDA 模式需要 NVIDIA 显卡及兼容的驱动
首次使用 Git LFS:
git lfs install模型仓库通过 Git submodule 固定到根仓库记录的版本。完整克隆应用和模型:
git clone --recurse-submodules git@github.com:Sanjeever/OpenMed.git
cd OpenMed如果已经克隆了根仓库:
git submodule update --init --recursive
git -C "models/OpenMed-PII-Chinese-NomicMed-Large-395M-v1-onnx-android" lfs pull模型仓库还包含 INT8、FP16 和 ORT Mobile 文件,但本服务只加载 model.onnx。如果只想下载运行所需的 FP32 文件,可在首次克隆时跳过 Git LFS 自动下载:
$env:GIT_LFS_SKIP_SMUDGE = "1"
git clone --recurse-submodules git@github.com:Sanjeever/OpenMed.git
Remove-Item Env:GIT_LFS_SKIP_SMUDGE
cd OpenMed
git -C "models/OpenMed-PII-Chinese-NomicMed-Large-395M-v1-onnx-android" lfs pull --include="model.onnx"uv sync依赖锁定在 uv.lock 中。项目安装 onnxruntime-gpu 及其 CUDA、cuDNN 运行库;该包同时提供 CPUExecutionProvider,因此 CPU 和 GPU 模式使用同一套 ONNX Runtime。
CPU:
uv run openmed-api --provider cpu --host 127.0.0.1 --port 8000NVIDIA GPU:
uv run openmed-api --provider cuda --host 127.0.0.1 --port 8000--provider 默认为 cpu。启动命令固定使用单 worker,模型在服务启动时只加载一次。CUDA 模式如果没有成功启用 CUDAExecutionProvider 会直接启动失败,不会静默退回 CPU。
原来的 Uvicorn 启动方式仍可使用,并默认选择 CPU:
uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 1健康检查:
curl.exe "http://127.0.0.1:8000/health"CUDA 模式的健康检查应以 CUDAExecutionProvider 为首选 Provider:
{
"status": "ok",
"provider": "cuda",
"variant": "fp32",
"providers": [
"CUDAExecutionProvider",
"CPUExecutionProvider"
]
}PII 检测:
$body = @{
text = "患者联系电话13800138000,于2026年8月17日入院。"
threshold = 0.5
} | ConvertTo-Json
Invoke-RestMethod `
-Method Post `
-Uri "http://127.0.0.1:8000/v1/pii/detect" `
-ContentType "application/json" `
-Body $body响应示例:
{
"model": "OpenMed-PII-Chinese-NomicMed-Large-395M-v1",
"variant": "fp32",
"text_length": 32,
"chunk_count": 1,
"truncated": false,
"entities": [
{
"label": "PHONE",
"score": 0.99,
"start": 6,
"end": 17,
"text": "13800138000"
}
]
}start 和 end 是完整输入文本中的 Python 字符下标,满足:
request_text[entity["start"]:entity["end"]] == entity["text"]examples/long-case-request.json 是一份完全合成的长住院病例请求体,包含分散在病例开头、中段和末尾的姓名、电话、日期、地址、邮箱及住院号等测试信息。文件长度超过单个模型窗口,可用于验证滑窗推理、全局字符偏移和 CPU/GPU 性能。
先启动服务,再读取整个 JSON 文件并发送请求:
$body = Get-Content `
-Raw `
-Encoding UTF8 `
"examples/long-case-request.json"
$response = Invoke-RestMethod `
-Method Post `
-Uri "http://127.0.0.1:8000/v1/pii/detect" `
-ContentType "application/json; charset=utf-8" `
-Body $body查看模型、窗口数量和是否发生截断:
$response | Select-Object `
model, `
variant, `
text_length, `
chunk_count, `
truncated按原文位置查看识别到的实体:
$response.entities |
Sort-Object start |
Format-Table label, score, start, end, text -AutoSize长病例正常处理时,chunk_count 应大于 1,truncated 应为 false。所有实体的 start 和 end 都是相对于完整病例原文的字符位置。
每种模式启动后先发送一次长病例请求进行预热,再执行多次计时。第一次请求不计入结果。
$body = Get-Content -Raw -Encoding UTF8 "examples/long-case-request.json"
1..5 | ForEach-Object {
$elapsed = Measure-Command {
$null = Invoke-RestMethod `
-Method Post `
-Uri "http://127.0.0.1:8000/v1/pii/detect" `
-ContentType "application/json; charset=utf-8" `
-Body $body
}
[PSCustomObject]@{
run = $_
milliseconds = [math]::Round($elapsed.TotalMilliseconds, 2)
}
} | Format-Table停止服务,分别使用 --provider cpu 和 --provider cuda 启动后执行同一段命令,即可得到可比的端到端延迟。
根仓库记录的是模型仓库的确定 commit。更新模型时,先更新 submodule,再在根仓库记录新的 gitlink:
$modelPath = "models/OpenMed-PII-Chinese-NomicMed-Large-395M-v1-onnx-android"
git -C $modelPath fetch origin
git -C $modelPath switch main
git -C $modelPath pull --ff-only
git -C $modelPath lfs pull --include="model.onnx"
git add $modelPath
git status提交根仓库后,其他环境运行以下命令即可切换到同一个模型版本:
git submodule update --init --recursive不要在根仓库中直接添加或提交模型二进制文件;模型版本只通过 submodule gitlink 管理。