{"record":{"id":"bbf3893e6ab15039","repo":"unslothai/unsloth","slug":"duplicate-lora-id-spec-id-list-each-adapter-a","errorCode":null,"errorMessage":"duplicate LoRA id '{spec.id}'; list each adapter at most once","messagePattern":"duplicate LoRA id '(.+?)'; list each adapter at most once","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"error","filePath":"studio/backend/models/inference.py","lineNumber":2862,"sourceCode":"    @field_validator(\"attention_backend\", mode = \"before\")\n    @classmethod\n    def _normalize_attention_backend(cls, value):\n        # The dispatcher accepts case/whitespace variants, but the Literal above is validated before any normaliser runs, so fold it here.\n        return value.strip().lower() if isinstance(value, str) else value\n\n    @field_validator(\"loras\")\n    @classmethod\n    def _unique_lora_ids(cls, value: Optional[list[\"LoraSpec\"]]) -> Optional[list[\"LoraSpec\"]]:\n        # Same guard DiffusionGenerateRequest carries, and it matters more here: _resolve_lora_set\n        # suffixes colliding adapter names, so a repeated id resolves the SAME adapter twice and\n        # set_adapters stacks both copies past the per-adapter weight bound. On the generation path\n        # that is one bad image; on this path the adapters are baked into the quantized build\n        # before compilation, so the unintended combination rides every image until a reload.\n        if value:\n            seen: set[str] = set()\n            for spec in value:\n                if spec.id in seen:\n                    raise ValueError(\n                        f\"duplicate LoRA id '{spec.id}'; list each adapter at most once\"\n                    )\n                seen.add(spec.id)\n        return value\n\n\nclass LoraSpec(BaseModel):\n    \"\"\"One LoRA adapter to apply for a generation, referenced by its discovery id.\n\n    The id is resolved against the backend's own LoRA catalog + local scan (see\n    core/inference/diffusion_lora.py); the client never supplies a raw filesystem\n    path, so an arbitrary file can't be loaded. Weight 0 disables the adapter.\n    \"\"\"\n\n    id: str = Field(\n        ..., min_length = 1, max_length = 512, description = \"LoRA discovery id (repo id or local stem)\"\n    )\n    weight: float = Field(","sourceCodeStart":2844,"sourceCodeEnd":2880,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/models/inference.py#L2844-L2880","documentation":"Raised by the loras field_validator on the quantize/build request when the same LoRA id appears more than once. _resolve_lora_set suffixes colliding adapter names, so a repeated id resolves the SAME adapter twice and set_adapters stacks both copies past the per-adapter weight bound. On this path the mistake is baked into the quantized build before compilation, so it rides every generated image until a reload.","triggerScenarios":"POSTing a quantize request with loras: [{\"id\": \"sketch\", \"weight\": 0.5}, {\"id\": \"sketch\", \"weight\": 0.3}] — an attempt to double-apply or layer the same adapter.","commonSituations":"Prompt-driven pipelines that merge multiple LoRA lists (user + preset) without deduping; UI state where re-adding an adapter appends instead of replaces; users trying to strengthen an effect by repeating the adapter rather than raising weight.","solutions":["Deduplicate by id client-side before submitting (keep the intended weight for each).","To strengthen an adapter, raise its weight (0 disables; per-adapter bound applies) instead of listing it twice.","If merging preset + user LoRA lists, make the merge key the adapter id."],"exampleFix":"# before\nloras = user_loras + preset_loras  # may contain 'sketch' twice\n\n# after\nby_id = {s['id']: s for s in user_loras + preset_loras}\nloras = list(by_id.values())","handlingStrategy":"validation","validationCode":"def dedupe_loras(loras: list[dict] | None) -> list[dict] | None:\n    if not loras:\n        return loras\n    return list({spec['id']: spec for spec in loras}.values())","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Key adapter selections by id in UI state so re-add replaces","Deduplicate merged preset+user lists before submitting","Strengthen an effect by raising weight, never by repeating the adapter"],"tags":["pydantic","validation","lora","diffusion","quantization","duplicates"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}