Backend ignores root level ONNX files, and cannot detect full model exports
Hello!
This issue has been generated by my agent based on #3915 (cc @LK-maker-007 ). Ideally, I want to extend or improve ONNX/OpenVINO exporting behaviour in Sentence Transformers so that it doesn't just export the transformers model, but the full ST model. Then, third parties (or a sentence-transformers lite?) could load this checkpoint without needing torch and run it efficiently. However, there's a bit of a problem between the two types of ONNX exporting: transformers only and full-ST. For example, how are you sure that the model checkpoint uses one or the other? And furthermore, the backend parameter is currently passed to the modules, including Transformer, which uses it to load using optimum. If we have a "full ST" model ONNX, then we have to instead load on the BaseModel level perhaps? Maybe it just overrides the modules from modules.json with one single "ONNXModule" (idem. OpenVINO)?
Either way, once I work on that, we have to consider what my agent wrote here:
1. Root level files are invisible on the Hub
backend_should_export builds one glob and uses it two ways. A local directory is matched with pathlib, where **/ means zero or more directories, and a Hub repository is matched with fnmatch, which has no ** and whose * already crosses directories:
>>> from fnmatch import fnmatch, translate
>>> translate("**/*.onnx")
'(?s:(?>.*?/).*\.onnx)\Z'
>>> fnmatch("model.onnx", "**/*.onnx")
FalseThe atomic (?>.*?/) demands a directory, so a file in the root of a repository never matches and export comes back True, even though the docstring says <file_name> in the root should set it to False. A local directory with the same layout decides the opposite.
The worst case has no workaround. Qdrant/all-MiniLM-L6-v2-onnx ships model.onnx and no torch weights, so the export it falls back to cannot run:
SentenceTransformer("Qdrant/all-MiniLM-L6-v2-onnx", backend="onnx")
# OSError: ... does not appear to have a file named pytorch_model.bin, model.safetensors, ...2. Widening the match alone makes things worse
We use the backend for module 0 only. The exported graph replaces Transformer, and Pooling, Dense and Normalize keep running in PyTorch, so a file is only a valid target if it returns last_hidden_state shaped [batch, seq, hidden_size].
Files under onnx/ satisfy that because that is where our exporter writes them. The repo root is not a location we write to, and in practice it often holds a full model export with the pooling and projection already baked in:
| repo | hidden_size |
root model.onnx output |
|---|---|---|
lightonai/GTE-ModernColBERT-v1 |
768 | output, (1, 4, 128), L2 normalized |
lightonai/LateOn |
768 | output, (1, 4, 128), L2 normalized |
mixedbread-ai/mxbai-edge-colbert-v0-32m |
384 | output, (1, 4, 64), L2 normalized |
answerdotai/answerai-colbert-small-v1 |
384 | output, (1, 4, 96), L2 normalized |
colbert-ir/colbertv2.0 |
768 | contextual, (1, 4, 128), L2 normalized |
ibm-granite/granite-embedding-30m-english |
384 | logits plus a pooled tensor |
intfloat/e5-small-v2 |
384 | last_hidden_state, transformer only |
Making the root visible without a compatibility check therefore regresses more than it fixes. Applying #3915's matching on top of main and A/B'ing real loads against the PyTorch reference:
| repo | main today |
with a naive widening |
|---|---|---|
intfloat/e5-small-v2 |
ok, exports, 1.6e-07 | ok, uses the root file, 1.9e-07 |
Qdrant/all-MiniLM-L6-v2-onnx |
fails, unloadable | ok |
ibm-granite/granite-embedding-30m-english |
ok, exports | KeyError: 'last_hidden_state' |
lightonai/GTE-ModernColBERT-v1 |
ok, exports, 2.4e-07 | KeyError |
lightonai/LateOn |
ok, exports, 1.6e-07 | KeyError |
mixedbread-ai/mxbai-edge-colbert-v0-32m |
ok, exports, 1.8e-07 | KeyError |
answerdotai/answerai-colbert-small-v1 |
ok, loads onnx/model.onnx, 1.8e-07 |
KeyError |
Two gain, five regress. Two rows deserve attention:
mxbai-edge-colbert-v0-32mis the model in our own documented example indocs/multi_vector_encoder/usage/efficiency.rst.answerai-colbert-small-v1ships both a full model rootmodel.onnxand a goodonnx/model.onnx. We load the latter today. Widening makes the root file win, becausebackend_should_exportchecks the root path before the backend subfolder, so a working repository is downgraded.
It does fail loudly rather than silently, since the pooled or projected output is named output or contextual and Optimum demands last_hidden_state.
Related symptom reports: #3437, #3666.
- Tom Aarsen
Source: huggingface/sentence-transformers