[Bug] Skip ONNX weights when downloading candle embedding models - #3533
[Bug] Skip ONNX weights when downloading candle embedding models#3533vuhluu wants to merge 2 commits into
Conversation
✅ Deploy Preview for vllm-semantic-router ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
The runtime downloader invoked `hf download <repo> --local-dir <path>` for every missing model, so a candle deployment of llm-semantic-router/mmbert-embed-32k-2d-matryoshka pulled the whole repository: roughly 4.3 GB of onnx/**/model.onnx and model.onnx.data exports on top of the ~620 MB of config.json, model.safetensors and tokenizer.json that the candle runtime actually loads. On a local machine this blocked router startup for the duration of the transfer and could exhaust disk before the embedding model was usable. Give ModelSpec an ExcludePatterns field that is forwarded to `hf download --exclude`, and populate it for the candle embedding model paths (mmbert, qwen3, gemma, multimodal) when the embedding backend is candle. OpenVINO keeps the full snapshot because it consumes the ONNX exports, remote backends provision nothing, and other local models keep their current behaviour. Only weight files are excluded, so the onnx/model_config.json layer manifest read by MmBertAvailableLayers is still fetched, and a test guards that no exclude pattern can ever match a completeness-required file. Signed-off-by: Vu Luu <luuhavu@gmail.com>
1d22597 to
fd526d0
Compare
Xunzhuo
left a comment
There was a problem hiding this comment.
Welcome, and thanks for the focused download-scope fix. One blocker: hf download --exclude accepts one pattern per option, but buildDownloadArgs emits one flag followed by all three patterns, so the remaining patterns become positional filenames. Exact-head CI reports the exclusion ignored and leaves model.safetensors absent. Please repeat --exclude for each pattern, update the argv test, and rerun the required checks.
adaamko
left a comment
There was a problem hiding this comment.
agree with the fix and with xunzhuo's blocker. one reason it bit: the image installs huggingface_hub[cli] unpinned, and the new hf download (typer) takes --exclude as a repeatable option, while the old huggingface-cli download took nargs=*. repeating the flag works on both, so thats the safe form whichever cli the image ends up with.
two more from #3498, whose E2E this download broke twice. IsGatedModelError returns true for any failure when HF_TOKEN is empty (the noToken branch), so a 429 or a connection reset on this exact repo gets logged as "gated model, skipping" and the router comes up with embedding_ready:false; semantic-cache then fails 0/15 and nothing else notices. this PR makes the fetch 7x smaller, which helps, but the skip branch is the real bug: a public repo that fails to download should retry and then fail startup, not get skipped as gated. follow-up, not this PR, i can file it.
and a heads up: #2828 canonicalizes the embedding paths through ResolveModelPath in the same function, and your exclude map is keyed by the raw cfg path. whichever of the two lands second has to key on the resolved path or the exclusion silently stops applying for aliased configs.
The typer-based `hf download` takes `--exclude` as a repeatable single-value option, so `--exclude *.onnx *.onnx.data *.onnx_data` kept only the first pattern and passed the other two as positional filenames. hf 1.30.0 then warns "Ignoring `--exclude` since filenames have been explicitly set", treats the stray positionals as filename globs, and fetches exactly the onnx/*/model.onnx.data files (4 files, 2.12 GB) while model.safetensors is never downloaded. This is the failure exact-head CI reported on this PR. buildDownloadArgs now emits `--exclude <pattern>` once per pattern (skipping empty entries), the form the typer `hf` CLI and the legacy nargs=* `huggingface-cli` parse identically. Also key the candle exclude map on config.ResolveModelPath on both the build and lookup side, so the narrowing still applies when the embedding model is configured by a registry alias and regardless of whether the collected provisioning paths are canonicalized first (vllm-project#2828). Tests: TestBuildDownloadArgsRepeatsExcludeFlagPerPattern replaces the argv test and asserts one flag per pattern; new TestBuildDownloadArgsSkipsEmptyExcludePatterns and TestBuildModelSpecsExcludesOnnxWeightsForAliasedEmbeddingModel. Verified against llm-semantic-router/mmbert-embed-32k-2d-matryoshka in a clean python:3.12-slim container with unpinned huggingface_hub (hf 1.30.0): 16 files / 614 MB; model.safetensors, tokenizer.json and onnx/model_config.json present; no *.onnx, *.onnx.data or *.onnx_data. Signed-off-by: Vu Luu <luuhavu@gmail.com>
|
@Xunzhuo thanks, fixed in 86ce721: one @adaamko thanks, went with the repeated flag for exactly that reason. For #2828, the exclude map is now keyed by |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3533 +/- ##
==========================================
+ Coverage 33.93% 34.17% +0.24%
==========================================
Files 20 20
Lines 2959 2888 -71
==========================================
- Hits 1004 987 -17
+ Misses 1849 1795 -54
Partials 106 106
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Closes #3532
Purpose
The runtime model downloader (
src/semantic-router/pkg/modeldownload) ranhf download <repo> --local-dir <path>with no file filter, so a candle deployment ofllm-semantic-router/mmbert-embed-32k-2d-matryoshkafetched the whole repository: about 4.3 GB ofonnx/**/model.onnx,model.onnx.dataandmodel_fa_fp16.onnxexports on top of the ~650 MB (config.json,model.safetensors,tokenizer.json) the candle runtime actually loads. On a fresh machine that blocks router startup for the whole transfer and can exhaust disk before the embedding model is usable.This change:
ModelSpec.ExcludePatterns, forwarded tohf downloadby a new pure helperbuildDownloadArgsas one--exclude <pattern>flag per pattern. The typer-basedhfCLI takes--excludeas a repeatable single-value option (one flag followed by several patterns keeps only the first and treats the rest as positional filenames), while the legacyhuggingface-clitooknargs=*; the repeated form is parsed identically by both, whichever CLI the image ends up installing;*.onnx,*.onnx.data,*.onnx_datafor every candle embedding model path (mmBERT, Qwen3, Gemma, multimodal) whenEmbeddingModels.EmbeddingBackend()iscandle. The exclude map is keyed and looked up byconfig.ResolveModelPath, so the narrowing holds when the model is configured by a registry alias and does not depend on whether the collected provisioning paths are canonicalized first ([Router] Download aliased model paths to the directory the runtime loads #2828 can land before or after this);onnx/model_config.jsonlayer manifest read byconfig.MmBertAvailableLayersis still downloaded.Modules affected:
src/semantic-router/pkg/modeldownloadonly (router service platform surface,router-corerules). Owner:wg/router-models-inference-runtime(accepted issue #3532).Test Plan
make agent-report ENV=cpu CHANGED_FILES="src/semantic-router/pkg/modeldownload/downloader.go,src/semantic-router/pkg/modeldownload/config_parser.go,src/semantic-router/pkg/modeldownload/types.go,src/semantic-router/pkg/modeldownload/downloader_test.go,src/semantic-router/pkg/modeldownload/download_scope_test.go"→ primary skillproject-change, fast testsmake test-semantic-routerdownloader_test.goanddownload_scope_test.go:TestBuildDownloadArgsFetchesFullSnapshotByDefault: models without a scope keep the historical argument listTestBuildDownloadArgsRepeatsExcludeFlagPerPattern: every pattern carries its own--exclude, the flag count equals the pattern count,--excludetrails--revisionTestBuildDownloadArgsSkipsEmptyExcludePatterns: an empty entry never produces a bare--excludeTestBuildModelSpecsExcludesOnnxWeightsForCandleEmbeddingModels: all four candle paths get the exclude listTestBuildModelSpecsExcludesOnnxWeightsForAliasedEmbeddingModel:models/mom-embedding-ultra(alias) and the canonical path are both narrowed; matches specs by resolvedLocalPath, so it passes with or without [Router] Download aliased model paths to the directory the runtime loads #2828TestBuildModelSpecsKeepsFullSnapshotForOpenVINOBackend: OpenVINO is unfilteredTestBuildModelSpecsLeavesNonEmbeddingModelsUnfiltered: semantic-cache BERT is unfilteredTestOnnxWeightExcludePatternsNeverMatchCandleRequiredFiles: exclude globs can never shadow a completeness-required file (would otherwise cause an endless re-download loop)cd src/semantic-router && go test ./pkg/modeldownload/... && go vet ./pkg/modeldownload/...gofmt -l src/semantic-router/pkg/modeldownload/huggingface_hub.utils.filter_repo_objects(the filterhf download --excludeuses)python:3.12-slimcontainer with unpinnedhuggingface_hub(the same install astools/docker/Dockerfile.extprocandtest-and-build.yml), run the exact argvbuildDownloadArgsproduced before and after the fix against the livellm-semantic-router/mmbert-embed-32k-2d-matryoshkarepository into a fresh--local-dir, then list the files on diskmake agent-ci-gate CHANGED_FILES="..."(same file list)Test Result
go test ./pkg/modeldownload/... -v -run 'DownloadArgs|ExcludesOnnx|OpenVINO|Unfiltered|NeverMatch|Aliased': 8/8 PASS (macOS, Intel, Go 1.25.0)go test ./pkg/modeldownload/...:ok … 1.323s;go vet ./pkg/modeldownload/...: clean;gofmt -l: no outputconfig.json,model.safetensors,tokenizer.json,onnx/model_config.jsonand the per-layeronnx/*/config.json), 12 dropped (everymodel.onnx,model.onnx.data,model_fa_fp16.onnx)hf1.30.0:--exclude "*.onnx" "*.onnx.data" "*.onnx_data", the argv of the first revision):UserWarning: Ignoring '--exclude' since filenames have been explicitly set;Fetching 4 files, 2.12 GB: the directory holds onlyonnx/layer-{6,11,16,22}/model.onnx.data; nomodel.safetensors, notokenizer.json. Reproduces the exact-head CI failure: the stray positionals are treated as filename globs, so the CLI downloads precisely the ONNX weights and nothing the candle runtime needs--exclude "*.onnx" --exclude "*.onnx.data" --exclude "*.onnx_data"):Fetching 16 files, 614 MB;model.safetensors,tokenizer.json,config.json,onnx/model_config.jsonand the per-layeronnx/*/config.jsonpresent;findfor*.onnx,*.onnx.data,*.onnx_datareturns nothingmake agent-ci-gate: passed on the first revision (fd526d02: agent-report, pre-commit baseline, Go structural lint, structure and architecture checks); not re-run on86ce721d, which only touches the same package's Go sources and testsFollow-ups tracked outside this PR: classifier repositories also ship
onnx/subtrees and are intentionally not narrowed here because the ONNX classifier backend may need them (follow-up on #3532);IsGatedModelErrorreturning true for any failure whenHF_TOKENis empty, which turns a transient public-repo failure into a "gated, skipping" startup (raised by @adaamko in review, to be filed separately).Semantic Router PR Checklist
[Feature],[Bug],[Docs],[Test],[Research],[Community], or[CI/Build][Router][Docs]; affected modules belong in labels and the PR bodyacceptedissue with exactly one owner: onewg/*label for project work orowner/maintainersfor repository governancegit commit -s