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

Abierto
#868 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

@rtmalikian ya está trabajando en esto.

Desde el 7/11/2026.

  • #869 de @rtmalikian — abierto

Evaluación

Dificultad
3/5
Tiempo estimado
1-2 días
Aptitud para principiantes
25/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Estancado
Stack tecnológico
python
Área
backend

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
Python
Estrellas
3.3k
Forks
296
Merge medio
4 d 11 h
PR fusionados (30 d)
38

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de qdrant/fastembed

Todos los issues de qdrant/fastembed

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.