[Bug]: URL-backed custom models with the same basename reuse the wrong cached model

Aperta
#868 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@rtmalikian ci sta già lavorando.

Dal 7/11/2026.

  • #869 di @rtmalikian — aperta

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
25/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Ferma
Stack tecnologico
python
Ambito
backend

Direzione di ricerca

Read fastembed/common/model_management.py around the URL loader at lines 535–550 and trace how the model name determines the cache directory and cache-hit behavior. Use the issue’s reproduction to compare both registrations with a shared cache against the fresh-cache control; done means each registered name loads its own archive, with existing basename-only cache entries handled consistently for offline users.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

What happened?

Two URL-backed custom models can be registered under different names, such as owner-a/shared-model and owner-b/shared-model. When they use the same cache directory, loading the second model silently reuses the first model's files. It returns the first model's embeddings without requesting the second archive.

The URL loader derives the on-disk directory from the last segment of model_name and treats an existing nonempty directory as a cache hit. Both registrations therefore look up cache_dir/shared-model. Source at the tested revision

Expected behavior

Each registration should load its own model files. A cache hit for one registered name should leave other names free to download their own archives. A separate cache_dir for each model is a workaround.

Before implementing a change, I'd appreciate guidance on how the loader should handle existing basename-only cache entries, especially for offline users.

Reproduction

Save this as repro.py and run it with FastEmbed and onnx installed. It builds two small ONNX archives and serves them on loopback. No pretrained model or external service is needed.

from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
import json
from pathlib import Path
import tarfile
from tempfile import TemporaryDirectory
from threading import Thread

import onnx
from onnx import TensorProto as T, helper as h
from tokenizers import Tokenizer
from tokenizers.models import WordLevel
from tokenizers.pre_tokenizers import Whitespace

from fastembed import TextEmbedding
from fastembed.common.model_description import ModelSource, PoolingType


def archive(root, name, factor):
    folder = root / name / "shared-model"
    folder.mkdir(parents=True)
    tokenizer = Tokenizer(WordLevel({"[UNK]": 0, "[PAD]": 1, "hello": 2, "world": 3}, unk_token="[UNK]"))
    tokenizer.pre_tokenizer = Whitespace()
    tokenizer.enable_padding(pad_id=1, pad_token="[PAD]")
    tokenizer.enable_truncation(max_length=8)
    tokenizer.save(str(folder / "tokenizer.json"))
    (folder / "tokenizer_config.json").write_text(json.dumps({"model_max_length": 8, "pad_token": "[PAD]"}))
    (folder / "special_tokens_map.json").write_text(json.dumps({"unk_token": "[UNK]", "pad_token": "[PAD]"}))
    graph = h.make_graph(
        [
            h.make_node("Cast", ["input_ids"], ["tokens"], to=T.FLOAT),
            h.make_node("ReduceSum", ["tokens"], ["sum"], axes=[1], keepdims=1),
            h.make_node("Mul", ["sum", "factor"], ["scaled"]),
            h.make_node("Concat", ["sum", "scaled"], ["embedding"], axis=1),
        ],
        name,
        [h.make_tensor_value_info("input_ids", T.INT64, ["batch", "sequence"])],
        [h.make_tensor_value_info("embedding", T.FLOAT, ["batch", 2])],
        [h.make_tensor("factor", T.FLOAT, [1], [factor])],
    )
    model = h.make_model(graph, opset_imports=[h.make_opsetid("", 11)], ir_version=9)
    onnx.checker.check_model(model)
    onnx.save(model, str(folder / "model.onnx"))
    with tarfile.open(root / f"{name}.tar.gz", "w:gz") as out:
        out.add(folder, arcname="shared-model")


with TemporaryDirectory() as tmp:
    root = Path(tmp)
    archive(root, "a", 1.0)
    archive(root, "b", -1.0)
    with ThreadingHTTPServer(("127.0.0.1", 0), partial(SimpleHTTPRequestHandler, directory=tmp)) as server:
        thread = Thread(target=server.serve_forever)
        thread.start()
        try:
            base = f"http://127.0.0.1:{server.server_port}"
            for owner in ("a", "b"):
                TextEmbedding.add_custom_model(
                    model=f"owner-{owner}/shared-model",
                    pooling=PoolingType.DISABLED,
                    normalization=False,
                    sources=ModelSource(url=f"{base}/{owner}.tar.gz"),
                    dim=2,
                    model_file="model.onnx",
                )

            for owner, cache in (("a", "shared"), ("b", "shared"), ("b", "fresh")):
                model = TextEmbedding(f"owner-{owner}/shared-model", cache_dir=str(root / cache), threads=1)
                print(owner, cache, next(model.embed(["hello world"])).tolist(), flush=True)
        finally:
            server.shutdown()
            thread.join()

Observed:

GET /a.tar.gz
a shared [5.0, 5.0]
b shared [5.0, 5.0]
GET /b.tar.gz
b fresh [5.0, -5.0]

The expected output for b shared is [5.0, -5.0], matching the fresh-cache control.

Environment

Python 3.11.15 on macOS arm64. FastEmbed source from main at f91d9168c0291daeafb0a54cebc7c5b4b83f312f (project version 0.9.1), loaded through PYTHONPATH with the installed runtime dependencies. ONNX 1.23.2, ONNX Runtime 1.30.0 and NumPy 2.4.6.

Lingua principale
Python
Stelle
3.3k
Fork
296
Merge medio
4g 11h
PR unite (30g)
38

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di qdrant/fastembed

Tutte le issue di qdrant/fastembed

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.