read the config, do not build it, when refusing an image request

The pre-load check went through AutoConfig, which reaches the network — 20 s
on the first call against 0.8 s once local — and with trust_remote_code
EXECUTES the repo own config code, another 2.7-5.6 s, to read two plain JSON
fields. A validation that can hang is not a validation.

It now reads config.json from what is already on disk (a local directory, or
the hub cache via try_to_load_from_cache) and judges from vision_config,
model_type and architectures: 0.1 ms, no network, no remote code. A model
whose config is not here yet is still not judged — the load fetches it and the
check on the loaded object decides.
Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T3rZozi1Ehk96NG1WesgC
parent 2833d7f7
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
# Canonical product version for CoderAI — single source of truth. Both the API # Canonical product version for CoderAI — single source of truth. Both the API
# metadata and the admin web UI read from here. # metadata and the admin web UI read from here.
__version__ = "0.5.24" __version__ = "0.5.25"
__tagline__ = "Complete orchestration, distribution and escalation of remotizable advanced inference." __tagline__ = "Complete orchestration, distribution and escalation of remotizable advanced inference."
# Configure the CUDA caching allocator BEFORE torch is imported anywhere. # Configure the CUDA caching allocator BEFORE torch is imported anywhere.
......
...@@ -1094,6 +1094,35 @@ def _load_embedding_model(model_name: str, device: str, model_config: dict = Non ...@@ -1094,6 +1094,35 @@ def _load_embedding_model(model_name: str, device: str, model_config: dict = Non
raise RuntimeError(f"Cannot load embedding model '{model_name}': {e}") raise RuntimeError(f"Cannot load embedding model '{model_name}': {e}")
def _local_config_json(model_name: str):
"""The repo's ``config.json`` as a dict, from local files only, or None.
Deliberately not ``AutoConfig``: on a validation path a network round trip
can hang the request it is meant to protect, and ``trust_remote_code``
would execute the repo's own code to tell us two plain JSON fields.
"""
import json as _json
import os as _os
try:
if _os.path.isdir(model_name):
path = _os.path.join(model_name, 'config.json')
if not _os.path.isfile(path):
return None
with open(path, encoding='utf-8') as fh:
return _json.load(fh)
from huggingface_hub import try_to_load_from_cache
hit = try_to_load_from_cache(model_name, 'config.json')
except Exception:
return None
if not isinstance(hit, str) or not _os.path.isfile(hit):
return None
try:
with open(hit, encoding='utf-8') as fh:
return _json.load(fh)
except Exception:
return None
def _may_embed_images(model_name: str, model_config: dict = None) -> bool: def _may_embed_images(model_name: str, model_config: dict = None) -> bool:
"""Whether this model COULD embed images, decided before it is loaded. """Whether this model COULD embed images, decided before it is loaded.
...@@ -1129,19 +1158,25 @@ def _may_embed_images(model_name: str, model_config: dict = None) -> bool: ...@@ -1129,19 +1158,25 @@ def _may_embed_images(model_name: str, model_config: dict = None) -> bool:
if low in ('geoclip', 'geoclip-location'): if low in ('geoclip', 'geoclip-location'):
return low == 'geoclip' return low == 'geoclip'
trust = bool(cfg.get('trust_remote_code', raw.get('trust_remote_code', True))) # The repo's config.json, read as JSON from what is already on disk. Not
try: # AutoConfig: this runs on a validation path, before any work has been
if _is_dual_encoder(name, trust) or _is_vision_only(name, trust): # committed, and AutoConfig would reach the network (20 s measured against
return True # 0.8 s once local) and, with trust_remote_code, EXECUTE the repo's own
except Exception: # config code (another 2.7-5.6 s). The two fields that answer the question
return True # unreadable config: let the load decide # are plain JSON keys.
# The config was readable and describes neither an image tower nor an hf = _local_config_json(name)
if hf is None:
return True # not here yet: the load will fetch it and decide
if hf.get('vision_config') is not None:
return True
mt = str(hf.get('model_type') or '')
if mt in _DUAL_ENCODER_TYPES or mt in _VISION_ONLY_TYPES:
return True
arch = " ".join(str(a) for a in (hf.get('architectures') or []))
if any(w in arch.lower() for w in ('clip', 'vision', 'siglip')):
return True
# The config is here, and describes neither an image tower nor an
# image-only encoder. # image-only encoder.
try:
from transformers import AutoConfig
AutoConfig.from_pretrained(name, trust_remote_code=trust)
except Exception:
return True # cannot be read at all: not our call to make
return False return False
......
...@@ -138,3 +138,55 @@ def test_both_directions_are_refused_with_400(): ...@@ -138,3 +138,55 @@ def test_both_directions_are_refused_with_400():
forty-second load for nothing.""" forty-second load for nothing."""
assert "is text-only; image embedding needs a" in _SRC assert "is text-only; image embedding needs a" in _SRC
assert "this embedding model is image-only" in _SRC assert "this embedding model is image-only" in _SRC
# --------------------------------------------------------------------------
# A validation must not cost what it is protecting against
# --------------------------------------------------------------------------
def test_the_precheck_does_not_touch_the_network_or_run_repo_code():
"""Measured while building this: AutoConfig took 20 s on the first call
(network) and 2.7-5.6 s after that, because `trust_remote_code` executes
the repo's own config code — to read two plain JSON fields. A validation
that can hang is not a validation."""
src = _SRC.split("def _may_embed_images", 1)[1].split("\ndef ", 1)[0]
# Code only: the comment above the call explains why AutoConfig is NOT
# used, and naming it there must not fail this.
code = "\n".join(l for l in src.splitlines()
if not l.lstrip().startswith("#"))
assert "AutoConfig" not in code, (
"the pre-check builds an AutoConfig, which reaches the network and "
"runs remote code")
assert "_local_config_json(" in code
def test_the_config_is_read_from_local_files_only():
src = _SRC.split("def _local_config_json", 1)[1].split("\ndef ", 1)[0]
assert "try_to_load_from_cache" in src, "it can fetch from the Hub"
assert "hf_hub_download" not in src and "snapshot_download" not in src
def test_a_config_that_is_not_here_is_not_judged(monkeypatch):
monkeypatch.setattr(E, "_local_config_json", lambda name: None)
assert E._may_embed_images("some/model-we-have-never-seen", {}) is True
def test_a_vision_tower_in_the_config_is_enough(monkeypatch):
monkeypatch.setattr(E, "_local_config_json",
lambda name: {"model_type": "qwen2_vl",
"vision_config": {"depth": 32}})
assert E._may_embed_images("any/model", {}) is True
def test_a_text_config_is_refused(monkeypatch):
monkeypatch.setattr(E, "_local_config_json",
lambda name: {"model_type": "qwen3",
"architectures": ["Qwen3Model"]})
assert E._may_embed_images("any/model", {}) is False
def test_a_clip_architecture_without_a_vision_config_still_counts(monkeypatch):
monkeypatch.setattr(E, "_local_config_json",
lambda name: {"model_type": "clip_text_model",
"architectures": ["CLIPModel"]})
assert E._may_embed_images("any/model", {}) is True
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment