{"record":{"id":"84e5d64d9d397d63","repo":"unslothai/unsloth","slug":"a-diffusion-load-is-already-in-progress","errorCode":null,"errorMessage":"A diffusion load is already in progress.","messagePattern":"A diffusion load is already in progress\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"studio/backend/core/inference/diffusion.py","lineNumber":1813,"sourceCode":"            family_override = family_override,\n            model_kind = model_kind,\n        )\n        # Refuse an EXPLICIT precision this host can never honor BEFORE the load starts, so the\n        # route answers 409 with the reason instead of evicting the resident model, downloading\n        # several GB and only then failing. The declines that need the real footprint (a VRAM\n        # misfit, a failed build) can only be found mid-load and surface through load-progress.\n        self.assert_precision_available(\n            fam,\n            model_kind = resolve_model_kind(gguf_filename, model_kind),\n            transformer_quant = transformer_quant,\n            text_encoder_quant = text_encoder_quant,\n            gpu_ordinal = gpu_ordinal,\n        )\n\n        with self._lock:\n            # Allow starting over a previously-failed load, but not over a live one.\n            if self._loading is not None and self._loading.error is None:\n                raise RuntimeError(\"A diffusion load is already in progress.\")\n            self._load_token += 1\n            token = self._load_token\n            # A NEW event per load, never a clear() of the shared one: unload() sets the event the running worker holds but also\n            # drops _loading, so clearing here would un-cancel that worker. Download preemption is best-effort; the token is the\n            # real commit guard.\n            cancel_event = threading.Event()\n            self._cancel_event = cancel_event\n            # Seed with the family fallback; the worker resolves the real base and updates this.\n            self._loading = _LoadingState(repo_id = repo_id, base_repo = fam.base_repo)\n\n        threading.Thread(\n            target = self._run_load,\n            kwargs = dict(\n                repo_id = repo_id,\n                gguf_filename = gguf_filename,\n                base_repo = base_repo,\n                family_override = family_override,\n                hf_token = hf_token,","sourceCodeStart":1795,"sourceCodeEnd":1831,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/inference/diffusion.py#L1795-L1831","documentation":"Concurrency guard inside the load lock: a new diffusion load is refused while another load is in progress. The check is `self._loading is not None and self._loading.error is None` - i.e. a LIVE load blocks, but a previously FAILED one does not (starting over a failed load is explicitly allowed). A fresh cancel_event and incremented token are created per load so an unload() can preempt the running worker's download.","triggerScenarios":"Issuing a second load (or route-triggered model switch) while the first load's background thread is still downloading/building - e.g. the user clicks a second model while the first is still in load-progress, or an automation fires two loads concurrently.","commonSituations":"Impatient double-click in the Studio UI; an orchestrator retrying/switching models without waiting for the previous load to finish; a load that stalls on a slow download while the user picks a different model.","solutions":["Wait for the in-progress load to finish (poll the load-progress/status surface) before issuing another.","If the running load is wrong or stuck, call unload() first - it cancels the worker via the cancel event - then start the new load.","In automation, serialize loads behind a queue or lock on the caller side so only one load request is ever outstanding.","Note a FAILED load does not block: if this error appears with no visible load, inspect _loading.error - a stale failed state should have been cleared."],"exampleFix":"# before: fire-and-forget second load\nmanager.load(repo_id=\"unsloth/FLUX.1-dev\")     # still downloading\nmanager.load(repo_id=\"unsloth/SD3.5-large\")     # RuntimeError: already in progress\n\n# after: wait for idle, or unload first\nwhile manager.is_loading():\n    time.sleep(0.5)\nmanager.load(repo_id=\"unsloth/SD3.5-large\")","handlingStrategy":"retry","validationCode":"def can_start_load(manager) -> bool:\n    \"\"\"True when no live load is running (a failed one does not block).\"\"\"\n    loading = manager._loading  # or the public load-progress/status surface\n    return loading is None or loading.error is not None","typeGuard":null,"tryCatchPattern":"try:\n    manager.load(repo_id=repo)\nexcept RuntimeError as e:\n    if \"already in progress\" in str(e):\n        wait_for_load_completion(manager)   # poll status, then retry once\n        manager.load(repo_id=repo)\n    else:\n        raise","preventionTips":["Serialize model loads client-side: one outstanding load request at a time, disable the load button while in progress.","Offer unload/cancel as the escape hatch - unload() drops _loading and cancels the worker via its cancel event.","Poll load-progress until idle rather than guessing timings; downloads can be long.","Remember a FAILED load never blocks a new one - if this fires with nothing visible, inspect the failed state instead of waiting."],"tags":["concurrency","load-state","diffusion","locking","model-loading"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}