Skip to content

模型工件(Model Artifact SPI) ​

第三方本地离线 ASR / MT / TTS 插件声明模型工件后,宿主自动接管 下载 / 镜像切换 / 就绪检查 / 设置页卡片 / 硬件评级——你不需要写任何下载 UI。

不要在 config_schema 里手写「模型下载路径」「国内镜像开关」「下载按钮」!

0.2.4 起:每个声明源都必须有真实、可验证的仓库地址。 ModelScope 不再复用 Hugging Face 的 repo id;没有 repos.modelscope 就视为该源不存在。请同时填写 manual_url,让下载失败时能把手动下载页展示给用户。


声明方式(二选一,model_artifacts() 优先) ​

方式一:mod.json(推荐) ​

json
{
  "requires_cuda": false,
  "model_artifacts": [
    {
      "id": "my-asr-weights",
      "name": "My ASR 1.2B",
      "category": "asr",
      "repo": "org/my-asr-1.2b",
      "repos": {
        "huggingface": "org/my-asr-1.2b",
        "hf-mirror": "org/my-asr-1.2b",
        "modelscope": "RealOrg/My-ASR-1.2B"
      },
      "manual_url": "https://huggingface.co/org/my-asr-1.2b/tree/main",
      "est_bytes": 1200000000,
      "sources": ["huggingface", "modelscope"],
      "ready_globs": ["*.safetensors", "*.bin"],
      "allow_patterns": ["*.safetensors", "*.bin"],
      "requires_cuda": false
    }
  ]
}

方式二:Provider 覆盖 ​

python
def model_artifacts(self) -> list[dict]: ...

返回的字典结构与上面完全一致。优先于 manifest,适合按配置/已下载情况动态挑变体。


字段全表 ​

字段必填说明
id是工件 id(宿主 API 里用它定位)
name建议展示名
category建议asr / mt / tts——决定挂到哪个设置页
repo见下规范 id(通常 HF 侧),决定本地目录 data/models/<org>--<name>
est_bytes建议预估体积(进度条与磁盘预检用);缺失时显示「未知大小」
sources否允许的下载源,缺省 ["huggingface", "modelscope"]。宿主按用户选择的节点决定顺序:全球官方 → HF;hf-mirror → hf-mirror → HF 官方(镜像大文件可能 308 回源);国内 → ModelScope(若声明)→ hf-mirror。★ 若模型仅在 HF 存在,请声明 ["huggingface", "hf-mirror"],宿主国内模式下直接走 hf-mirror,绝不向 ModelScope 撞墙。
repos否按源覆写的真实 repo id,键为 huggingface / hf-mirror / modelscope。ModelScope 必须显式声明;没有 repos.modelscope 就视为该源不存在,宿主不会拿 HF id 去猜。
manual_url0.2.4 起建议必填该工件的模型文件页/官方下载页。下载失败时设置页的「无法下载?手动下载」直接打开它。必须指向真实存在的页面;不要写猜测地址。
homepage否manual_url 的兼容别名;两者同时存在时优先 manual_url。
ready_globs是就绪判定:这些通配符在目标目录的直接子文件里都能匹配到才算就绪
allow_patterns否下载白名单。未显式提供时宿主自动复用 ready_globs,避免把多量化分支仓库(GGUF repo,几十 GB)全量拖下来
requires_cuda否false = 不强依赖 NVIDIA(支持 AMD ROCm / Vulkan / DirectML / 多核 CPU)→ 宿主在 AMD/Intel/纯 CPU 上不弹受限红框,改评级为良好/极佳
dir否自定义落盘目录名(相对 <数据目录>/models)。只接受单层安全路径段(禁 / \ .. :),非法值自动忽略
parts否一个工件由多个仓库拼成时使用,≤ 8 项。见下

常用 repos 示例(ModelScope 与 HF 的组织名常不同):

json
"repos": {
  "huggingface": "tencent/Hy-MT2-1.8B-GGUF",
  "hf-mirror":   "tencent/Hy-MT2-1.8B-GGUF",
  "modelscope":  "Tencent-Hunyuan/Hy-MT2-1.8B-GGUF"
}

真实地址补齐清单(发布前逐项检查) ​

  1. 打开每个 repos 地址,确认页面/仓库真实存在,且文件列表包含 ready_globs。
  2. ModelScope 与 HF 的组织名/仓库名经常不同;不要复制 HF id。没有 ModelScope 仓库就从 sources 删除 modelscope,不要保留一个必然 404 的声明。
  3. manual_url 指向用户能直接打开、能看到文件列表的页面(通常是 https://huggingface.co/<org>/<repo>/tree/main 或对应的 ModelScope 文件页)。
  4. ready_globs 写最终文件名,不要写模糊的 *.gguf;这样手动下载面板才能明确 告诉用户下哪几个文件、放到哪个目录。
  5. 多仓库 parts 的每个 part 都要有自己的真实 repo;part 的 repos 不会继承工件级。

宿主在下载失败后的模型卡里提供「无法下载?手动下载」:展示 manual_url、 ready_globs 和目标目录。地址写错会直接把用户带到 404,因此发布前必须实际点开验证。

仅 Hugging Face 权重(无 ModelScope 源)最佳实践 ​

如果你的模型仅发布在 Hugging Face(没有在 ModelScope 上架搬运),务必在工件或各 part 中声明:

json
"repo": "org/my-model-gguf",
"repos": {
  "huggingface": "org/my-model-gguf",
  "hf-mirror": "org/my-model-gguf"
},
"manual_url": "https://huggingface.co/org/my-model-gguf/tree/main",
"sources": ["huggingface", "hf-mirror"],
"ready_globs": ["my-model-Q4_K_M.gguf"]

宿主行为保证:

  1. 拒绝盲目撞墙:当国内用户采用默认的「国内高速镜像」下载时,宿主会严格遵循工件的源声明,识别到未包含 modelscope,直接使用 hf-mirror 国内镜像高速拉取 Hugging Face 资产,彻底避免去 ModelScope 尝试一个不存在的仓库而遭遇 404 或超时。
  2. 多节点降级与看门狗时间重置:如果模型同时支持 ModelScope 与 hf-mirror,当首选的 ModelScope 节点由于网络波动或服务端断流发生异常时,宿主会自动无缝降级至 hf-mirror。在切换到备用镜像的瞬间,宿主会立即清空重置看门狗的停滞计数器,为备用镜像提供完整的首包握手(宽限 96 秒)与传输等待时间,彻底杜绝前序节点的耗时拖垮后序备用节点。

多仓库拼接 parts + 自定义 dir ​

问题:权重与 tokenizer / 编解码器 / 视觉塔分属不同仓库时,一个工件 = 一个仓库表达不了 (文件永远凑不齐,卡在「未就绪」)。

json
{
  "id": "qwen3-tts-1.7b-customvoice-q8",
  "name": "Qwen3-TTS 1.7B CustomVoice (Q8_0)",
  "category": "tts",
  "dir": "qwen3-tts-1.7b-customvoice",
  "est_bytes": 2480000000,
  "sources": ["huggingface"],
  "ready_globs": ["qwen3-tts-12hz-1.7b-customvoice-q8_0.gguf",
                  "qwen3-tts-tokenizer-12hz-q8_0.gguf"],
  "parts": [
    { "repo": "cstr/qwen3-tts-1.7b-customvoice-GGUF",
      "allow_patterns": ["*customvoice-q8_0.gguf"] },
    { "repo": "cstr/qwen3-tts-tokenizer-12hz-GGUF",
      "allow_patterns": ["*tokenizer-12hz-q8_0.gguf"] }
  ]
}
  • 每个 part 支持自己的 repo / repos / sources / allow_patterns(按 part 各自生效),上限 8 项; 同一个 repo 写两次会合并 allow_patterns。
  • part 未写 sources 时继承工件级 sources;repos 不继承(它是对具体仓库的覆写)。
  • 所有 part 的文件落进同一个目录(dir → repo 规范名 → 首个 part 仓库名); 宿主先下主仓库,再依次补齐其余片段;全部成功才算就绪。
  • 任一片段失败即整体未就绪;重试会跳过已落盘文件(可续传),进度文案带 1/2、2/2。
  • ready_globs 依旧只在该目录的直接子文件里判定。

两个坑 ​

  1. 每个 part 都要写 allow_patterns:没写白名单的片段会被整仓下载 (主链路的第一个片段例外——它会回落到工件级 allow_patterns / ready_globs)。
  2. 不要让不同 part 产出同名文件(后下的会静默覆盖前面的)。同名只应出现在被 allow_patterns 收窄到互不重叠的仓库里。

宿主 API ​

python
host.list_model_artifacts() -> list[dict]
host.inspect_model_artifact(plugin_id: str, artifact_id: str) -> dict
host.download_model_artifact(plugin_id: str, artifact_id: str) -> None

inspect_model_artifact() 返回:

python
{
  "plugin_id": str,
  "artifact_id": str,
  "name": str,
  "category": str,
  "repo": str,
  "est_bytes": int,
  "is_downloaded": bool,
  "model_dir": str,     # 实际命中权重的目录;未就绪时 ""
  "target_dir": str,    # ★ 确定落盘目录,无论是否已下载都有值
  "size_str": str,
}

用 target_dir 定位文件(model_dir 未就绪为空)。 不要自己拼 <org>--<name>——多仓库 parts / 自定义 dir 时拼不出来。

python
info = self.host.inspect_model_artifact(self.manifest.id, "my-asr-weights")
root = Path(info["target_dir"])          # 该去哪读文件
exe  = root / "model.gguf"
ready = bool(info.get("is_downloaded"))

QML 侧对应槽:listModelArtifacts() / inspectModelArtifact(pid, aid) / downloadModelArtifact(pid, aid)。


就绪检查钩子 ​

python
def check_ready(self) -> tuple[bool, str, str]:
    """返回 (ok, issue_type, message)。
    issue_type: '' | 'missing_key' | 'model_not_ready' | 'unassigned'"""

未实现时宿主降级为跳过该校验。

语义:check_ready() 回答的是「能不能用」(运行时在不在、权重齐不齐、Key 配了没), 不是「服务此刻在不在跑」。本地服务型插件请把「服务未启动」判为可用(True), 否则用户会被拒绝开启音轨(宿主只在真正要用的路径上才调 ensure_loaded() 拉起服务)。 见 本地服务型引擎强制契约。


多能力模型(变体) ​

同一模型里有多个互斥变体时(例:Qwen3-TTS 的 base 克隆 / customvoice 内置音色 / voicedesign 文字描述音色),把它们声明成多个 model_artifacts(各写自己的 ready_globs), 并在 model_artifacts() 里把当前生效的那份排到第一位:

python
def model_artifacts(self) -> list[dict]:
    arts = [...]                       # 全部变体
    active = self._resolve_variant()   # 按配置 / 已下载情况选出生效的
    arts.sort(key=lambda a: 0 if a["id"] == active else 1)
    return arts

宿主模型卡按第一个同 category 的工件展示与下载,用户因此只需要下当前变体: 既不会被误报「未就绪」,也不会被迫下全部变体(以 Qwen3-TTS 为例:每个变体都是一份完整 LM,约 1.1 GB)。

实测参考:extensions/community_qwen3_tts 的 _VARIANTS / _ARTIFACT_BY_VARIANT。


设置页零代码呈现 ​

只要插件声明了模型工件,并且 tags 里含 ["local"] 或 ["offline"]:

  • HardwareRatingBanner:自动按模型体量与 GPU/CPU 环境给出算力评级 (「极佳配置: RTX 4070 Ti(显存充足,极速毫秒级离线秒翻)」/「极速轻量: 纯 CPU 离线运行良好」…)
  • LocalModelStorageCard:全球官方 / 国内镜像 / hf-mirror 节点切换、代理开关与地址、 真实字节进度、存放路径打开、自定义迁移重定向、一键清除缓存
  • GpuReleaseRow:统一显存释放与加速框架状态

config_schema 里只放推理超参。


其他相关属性 ​

python
@property
def supports_concurrent_streams(self) -> bool:
    """默认 True。底层为单活句柄时返回 False,宿主才降级另一轨。"""

@property
def requires_hf_runtime(self) -> bool | None:
    """True=需要 HF;False=纯 API/云端可跳过预热;None=按 manifest.is_local 推断。"""

requires_hf_runtime 也可在 mod.json 写 "requires_hf": false。 第三方云端插件应声明 false 或 tags: ["cloud"],避免无谓的 transformers 预热。

搜索路径(宿主解析已下载权重时):插件目录 models/{artifact_id} → HF cache models--{org--name} → ModelScope cache。


相关 ​

TransBox - 赋能全人类无界限跨语言交流