{"record":{"id":"4c6cd2c0cbcbc8b6","repo":"invoke-ai/InvokeAI","slug":"state-dict-looks-sdnq-quantized-use-qwen3encoder","errorCode":null,"errorMessage":"state dict looks SDNQ-quantized; use Qwen3Encoder_SDNQ_Folder_Config","messagePattern":"state dict looks SDNQ-quantized; use Qwen3Encoder_SDNQ_Folder_Config","errorType":"exception","errorClass":"NotAMatchError","httpStatus":null,"severity":"warning","filePath":"invokeai/backend/model_manager/configs/qwen3_encoder.py","lineNumber":361,"sourceCode":"    @classmethod\n    def _reject_if_sdnq_quantized(cls, mod: ModelOnDisk) -> None:\n        # Primary signal: quantization_config.json with quant_method=\"sdnq\" (at root or in\n        # text_encoder/). Fallback: SDNQ-style weight+scale key pairs in the state dict. This mirrors\n        # the detection in Qwen3Encoder_SDNQ_Folder_Config so the two stay mutually exclusive.\n        for folder in (mod.path, mod.path / \"text_encoder\"):\n            quant_config_path = folder / \"quantization_config.json\"\n            if not quant_config_path.exists():\n                continue\n            try:\n                with open(quant_config_path, \"r\", encoding=\"utf-8\") as f:\n                    quant_config = json.load(f)\n            except (json.JSONDecodeError, OSError):\n                continue\n            if quant_config.get(\"quant_method\") == \"sdnq\":\n                raise NotAMatchError(\"folder is SDNQ-quantized; use Qwen3Encoder_SDNQ_Folder_Config\")\n\n        if _has_sdnq_keys(mod.load_state_dict()):\n            raise NotAMatchError(\"state dict looks SDNQ-quantized; use Qwen3Encoder_SDNQ_Folder_Config\")\n\n    @classmethod\n    def _get_variant_from_config(cls, config_path) -> Qwen3VariantType:\n        \"\"\"Get variant from config.json based on hidden_size, or raise NotAMatch if unknown.\"\"\"\n        QWEN3_06B_HIDDEN_SIZE = 1024\n        QWEN3_4B_HIDDEN_SIZE = 2560\n        QWEN3_8B_HIDDEN_SIZE = 4096\n\n        try:\n            with open(config_path, \"r\", encoding=\"utf-8\") as f:\n                config = json.load(f)\n        except (json.JSONDecodeError, OSError) as e:\n            raise NotAMatchError(f\"unable to read Qwen3 config.json: {e}\") from e\n\n        hidden_size = config.get(\"hidden_size\")\n        if hidden_size == QWEN3_8B_HIDDEN_SIZE:\n            return Qwen3VariantType.Qwen3_8B\n        elif hidden_size == QWEN3_4B_HIDDEN_SIZE:","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/backend/model_manager/configs/qwen3_encoder.py#L343-L379","documentation":"NotAMatchError raised by Qwen3Encoder_Qwen3Encoder_Config._reject_if_sdnq_quantized (qwen3_encoder.py:361). The fallback SDNQ check: even without a quantization_config.json, the state dict contains SDNQ-style weight+scale key pairs (_has_sdnq_keys), so the folder is SDNQ-quantized and must go to Qwen3Encoder_SDNQ_Folder_Config. Loading it via the unquantized loader would misread packed uint8 weights.","triggerScenarios":"from_model_on_disk where no sdnq quantization_config.json is present but _has_sdnq_keys(mod.load_state_dict()) finds weight+scale tensor pairs in the safetensors state dict.","commonSituations":"SDNQ conversions that omit quantization_config.json; hand-merged or re-uploaded checkpoints where the quant config file was dropped but quantized tensors remain; downloading weights only (config-only files excluded) from an SDNQ repo.","solutions":["Let the Qwen3Encoder_SDNQ_Folder_Config match the model, or register it explicitly as SDNQ.","Restore/re-add the quantization_config.json from the original SDNQ repo so detection uses the primary signal.","Download the unquantized weights if you want the standard Qwen3Encoder loader.","Inspect state-dict keys (look for *.weight_scale / packed uint8 tensors) to confirm the quantization before re-registering."],"exampleFix":"// before\nmodels/qwen3-encoder/  // model.safetensors has weight_scale tensors, no quantization_config.json\n// after\nmodels/qwen3-encoder/  // + quantization_config.json (quant_method: sdnq), registered via Qwen3Encoder_SDNQ_Folder_Config","handlingStrategy":"validation","validationCode":"def has_sdnq_keys(state_dict: dict) -> bool:\n    # SDNQ packs weights as uint8 with matching *.weight_scale tensors\n    return any(k.endswith('.weight_scale') or k.endswith('_scale') for k in state_dict) and any(\n        getattr(t, 'dtype', None) is not None and 'uint8' in str(t.dtype) for t in state_dict.values()\n    )\n# if True, register with Qwen3Encoder_SDNQ_Folder_Config","typeGuard":"def looks_sdnq_quantized(state_dict: dict) -> bool:\n    scales = [k for k in state_dict if 'scale' in k.lower()]\n    return len(scales) > 0 and any('uint8' in str(getattr(state_dict[k], 'dtype', '')).lower() for k in state_dict)","tryCatchPattern":"try:\n    register_model(model_dir, model_type='Qwen3Encoder')\nexcept NotAMatchError as e:\n    if 'SDNQ' in str(e):\n        register_model(model_dir, config='Qwen3Encoder_SDNQ_Folder_Config')\n    else:\n        raise","preventionTips":["Inspect state-dict keys for weight_scale/packed tensors before choosing a loader config.","Re-add quantization_config.json if an SDNQ checkpoint lost it, so the primary detection path applies.","Never load SDNQ-packed weights through the unquantized loader — weights will be misread."],"tags":["quantization","sdnq","state-dict","model-identification"],"backgroundTag":"quantized-model-config-mismatch","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}