Files
wehub-resource-sync cddb07a176
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:06 +08:00

82 lines
3.0 KiB
Python

"""Regression tests for Qwen3 Encoder config probing.
See https://github.com/invoke-ai/InvokeAI/issues/9090
`Qwen2.5-1.5B-Instruct` (a standalone causal LM) was being misidentified as a
`Qwen3Encoder` because the diffusers-style config check matched any directory with
`config.json` at the root and a Qwen* class name. A complete causal LM also bundles
tokenizer files at the root, while standalone text_encoder downloads do not — we
use that to disambiguate.
"""
import json
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest.mock import MagicMock
import pytest
from invokeai.backend.model_manager.configs.identification_utils import NotAMatchError
from invokeai.backend.model_manager.configs.qwen3_encoder import Qwen3Encoder_Qwen3Encoder_Config
_OVERRIDE_FIELDS: dict[str, object] = {
"hash": "blake3:fakehash",
"path": "/fake/models/test-model",
"file_size": 1000,
"name": "test-model",
"description": "test",
"source": "test",
"source_type": "path",
"key": "test-key",
}
def _write_config(path: Path, hidden_size: int = 2560, architecture: str = "Qwen2ForCausalLM") -> None:
path.write_text(json.dumps({"architectures": [architecture], "hidden_size": hidden_size}))
@pytest.mark.parametrize("tokenizer_file", ["tokenizer.json", "tokenizer.model", "tokenizer_config.json"])
def test_complete_causal_lm_is_rejected(tokenizer_file: str) -> None:
"""A directory with config.json + tokenizer files at root is a TextLLM, not a Qwen3 encoder."""
with TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
_write_config(root / "config.json")
(root / tokenizer_file).write_text("{}")
mod = MagicMock()
mod.path = root
with pytest.raises(NotAMatchError, match="complete causal LM"):
Qwen3Encoder_Qwen3Encoder_Config.from_model_on_disk(mod, dict(_OVERRIDE_FIELDS))
def test_standalone_text_encoder_subfolder_still_matches() -> None:
"""A standalone text_encoder download (config.json at root, no tokenizer files) should still match."""
with TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
_write_config(root / "config.json")
mod = MagicMock()
mod.path = root
config = Qwen3Encoder_Qwen3Encoder_Config.from_model_on_disk(mod, dict(_OVERRIDE_FIELDS))
assert config.type.value == "qwen3_encoder"
def test_nested_text_encoder_with_root_tokenizer_still_matches() -> None:
"""A model with text_encoder/config.json should match even if tokenizer files exist at root.
The tokenizer-at-root heuristic only applies to the standalone (root-level config.json) case.
"""
with TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
(root / "text_encoder").mkdir()
_write_config(root / "text_encoder" / "config.json")
(root / "tokenizer.json").write_text("{}")
mod = MagicMock()
mod.path = root
config = Qwen3Encoder_Qwen3Encoder_Config.from_model_on_disk(mod, dict(_OVERRIDE_FIELDS))
assert config.type.value == "qwen3_encoder"