fix(embedding): pre-warm embedding client at startup

Addresses coderabbit review: Tokenizer.from_pretrained for the hf: spec
fetches from the Hugging Face Hub and ran synchronously under the
singleton lock on the first embed/batch_embed call, stalling the event
loop and blocking concurrent callers behind the download.

Add EmbeddingClient.warmup() and call it from the API lifespan and the
deriver startup, right after validate_embedding_schema. This moves any
tokenizer download off the request path and fails the process fast on
a bad hf: spec instead of stalling the first request. tiktoken: and
file: paths are unaffected (no network); unset tokenizer still
auto-detects lazily but never hits the network.
This commit is contained in:
Vansh-Sharma27 2026-07-19 14:47:58 +00:00
parent e83ab842c1
commit 8c0709eec6
No known key found for this signature in database
3 changed files with 20 additions and 0 deletions

View File

@ -71,6 +71,11 @@ async def run_deriver():
# gate the API runs in its lifespan. Inside the try block so the
# telemetry buffer is still flushed if validation raises.
await validate_embedding_schema(engine)
# Eagerly build the embedding client so an hf: tokenizer download
# happens here, not on the first reconciler call under the lock.
from src.embedding_client import embedding_client
embedding_client.warmup()
await main()
finally:
# Shutdown telemetry (flush CloudEvents buffer)

View File

@ -799,6 +799,16 @@ class EmbeddingClient:
def _resolve_runtime_config(self) -> EmbeddingModelConfig:
return resolve_embedding_model_config(settings.EMBEDDING.MODEL_CONFIG)
def warmup(self) -> None:
"""Eagerly construct the underlying client at process startup.
``hf:`` tokenizer specs fetch from the Hugging Face Hub; doing that on
the first request would stall the event loop under the singleton lock.
Calling this from the API and deriver lifespans moves any tokenizer
download off the request path and fails the process fast on a bad spec.
"""
self._get_client()
def _get_settings_signature(self) -> tuple[object, ...]:
runtime_config = self._resolve_runtime_config()
return (

View File

@ -18,6 +18,7 @@ from src._version import HONCHO_VERSION
from src.cache.client import close_cache, init_cache
from src.config import settings
from src.db import engine, register_db_query_instrumentation, request_context
from src.embedding_client import embedding_client
from src.exceptions import HonchoException
from src.routers import (
conclusions,
@ -116,6 +117,10 @@ async def lifespan(_: FastAPI):
# writing wrong-dim vectors.
await validate_embedding_schema(engine)
# Eagerly build the embedding client so an hf: tokenizer download happens
# here, not on the first request under the singleton lock.
embedding_client.warmup()
try:
await init_cache()
except Exception as e: