{"record":{"id":"764c99053d6c2de6","repo":"invoke-ai/InvokeAI","slug":"directory-looks-like-a-complete-causal-lm-config","errorCode":null,"errorMessage":"directory looks like a complete causal LM (config.json and tokenizer files at root), not a standalone Qwen3 encoder","messagePattern":"directory looks like a complete causal LM \\(config\\.json and tokenizer files at root\\), not a standalone Qwen3 encoder","errorType":"exception","errorClass":"NotAMatchError","httpStatus":null,"severity":"info","filePath":"invokeai/backend/model_manager/configs/qwen3_encoder.py","lineNumber":318,"sourceCode":"            raise NotAMatchError(\n                \"directory looks like a full diffusers pipeline (has model_index.json or transformer folder), \"\n                \"not a standalone Qwen3 encoder\"\n            )\n\n        # Check for text_encoder config - support both:\n        # 1. Full model structure: model_root/text_encoder/config.json\n        # 2. Standalone text_encoder download: model_root/config.json (when text_encoder subfolder is downloaded separately)\n        config_path_nested = mod.path / \"text_encoder\" / \"config.json\"\n        config_path_direct = mod.path / \"config.json\"\n\n        if config_path_nested.exists():\n            expected_config_path = config_path_nested\n        elif config_path_direct.exists():\n            # Standalone text_encoder downloads do not bundle tokenizer files. If we see tokenizer files at the\n            # root next to config.json, this is a complete causal LM (TextLLM), not a Qwen3 encoder subfolder.\n            tokenizer_files = (\"tokenizer.json\", \"tokenizer.model\", \"tokenizer_config.json\")\n            if any((mod.path / f).exists() for f in tokenizer_files):\n                raise NotAMatchError(\n                    \"directory looks like a complete causal LM (config.json and tokenizer files at root), \"\n                    \"not a standalone Qwen3 encoder\"\n                )\n            expected_config_path = config_path_direct\n        else:\n            raise NotAMatchError(\n                f\"unable to load config file(s): {{PosixPath('{config_path_nested}'): 'file does not exist'}}\"\n            )\n\n        # Qwen3 uses Qwen2VLForConditionalGeneration or similar\n        raise_for_class_name(expected_config_path, _QWEN3_ENCODER_ARCHITECTURES)\n\n        # Reject SDNQ-quantized encoders so Qwen3Encoder_SDNQ_Folder_Config matches them instead.\n        # A real SDNQ Qwen3 encoder has the same Qwen3 config class name as an unquantized one, so\n        # without this guard both configs accept the folder — and since they share the Qwen3Encoder\n        # type, the factory tiebreak is non-deterministic. If it picked this (unquantized) config,\n        # the non-SDNQ loader would then mis-read the packed uint8 weights.\n        cls._reject_if_sdnq_quantized(mod)","sourceCodeStart":300,"sourceCodeEnd":336,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/backend/model_manager/configs/qwen3_encoder.py#L300-L336","documentation":"NotAMatchError raised in Qwen3Encoder_Qwen3Encoder_Config.from_model_on_disk (qwen3_encoder.py:318). The directory has config.json at root plus tokenizer files (tokenizer.json, tokenizer.model, or tokenizer_config.json). A standalone Qwen3 text-encoder download never bundles tokenizer files; their presence indicates a complete causal LM (TextLLM), so the config rejects the folder so the TextLLM config can match it.","triggerScenarios":"from_model_on_disk scanning a directory where mod.path/config.json exists AND any of tokenizer.json / tokenizer.model / tokenizer_config.json exists at the root.","commonSituations":"Downloading a full Qwen3-4B/8B causal LM repo (which always ships tokenizer files) and expecting it to register as a Qwen3 text encoder; confusing Qwen3ForCausalLM checkpoints with the Z-Image text_encoder subfolder.","solutions":["Install the folder as a TextLLM / main model — that is what it is.","If you need a Qwen3 encoder, download only the text_encoder subfolder contents (config.json + model weights, no tokenizer) into a dedicated directory.","If the tokenizer files are accidental leftovers, remove them and rescan.","Place the complete LM outside the directory the encoder scanner walks."],"exampleFix":"// before\nmodels/qwen3-encoder/  // config.json + tokenizer.json + safetensors (a full LM)\n// after\nmodels/qwen3-textllm/  // full LM, registered as TextLLM\nmodels/qwen3-encoder/  // only config.json + model.safetensors","handlingStrategy":"validation","validationCode":"def is_complete_causal_lm(path) -> bool:\n    if not (path / 'config.json').exists():\n        return False\n    return any((path / f).exists() for f in ('tokenizer.json', 'tokenizer.model', 'tokenizer_config.json'))","typeGuard":"def is_standalone_qwen3_encoder_dir(path) -> bool:\n    has_cfg = (path / 'config.json').exists() or (path / 'text_encoder' / 'config.json').exists()\n    has_tokenizer = any((path / f).exists() for f in ('tokenizer.json', 'tokenizer.model', 'tokenizer_config.json'))\n    return has_cfg and not has_tokenizer","tryCatchPattern":"if is_complete_causal_lm(model_dir):\n    register_model(model_dir, model_type='TextLLM')\nelse:\n    try:\n        register_model(model_dir, model_type='Qwen3Encoder')\n    except NotAMatchError as e:\n        logger.warning('Not a standalone encoder: %s', e)","preventionTips":["Exclude tokenizer files when downloading only encoder weights (e.g. allow_patterns=['config.json','*.safetensors']).","Register full Qwen3 LM repos as TextLLM, not encoders.","Keep tokenizer assets in a separate folder from encoder-only downloads."],"tags":["model-identification","tokenizer","directory-structure","qwen3"],"backgroundTag":"model-not-a-match","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}