{"record":{"id":"034b7382a124d2cf","repo":"invoke-ai/InvokeAI","slug":"multiple-generation-devices-require-defaultsession","errorCode":null,"errorMessage":"Multiple generation devices require DefaultSessionRunner; got {type(template).__name__}. Provide a DefaultSessionRunner (with callbacks), or configure a single device in generation_devices.","messagePattern":"Multiple generation devices require DefaultSessionRunner; got (.+?)\\. Provide a DefaultSessionRunner \\(with callbacks\\), or configure a single device in generation_devices\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"invokeai/app/services/session_processor/session_processor_default.py","lineNumber":597,"sourceCode":"        \"\"\"Create an independent runner for an additional worker.\n\n        Each worker needs its own runner because the runner stores its session's cancel event.\n        We carry over the template's callbacks so all workers behave identically.\n        \"\"\"\n        # `type is`, not isinstance: a subclass would be silently downgraded to a plain\n        # DefaultSessionRunner, losing its overrides.\n        if type(template) is DefaultSessionRunner:\n            return DefaultSessionRunner(\n                on_before_run_session_callbacks=list(template._on_before_run_session_callbacks),\n                on_before_run_node_callbacks=list(template._on_before_run_node_callbacks),\n                on_after_run_node_callbacks=list(template._on_after_run_node_callbacks),\n                on_node_error_callbacks=list(template._on_node_error_callbacks),\n                on_after_run_session_callbacks=list(template._on_after_run_session_callbacks),\n            )\n        # Any other implementation cannot be cloned generically, and sharing one instance across\n        # workers is not safe either: every start() overwrites the runner's stored cancel event, so\n        # only the last worker's cancellation would ever fire. Fail loudly at startup instead.\n        raise ValueError(\n            f\"Multiple generation devices require DefaultSessionRunner; got {type(template).__name__}. \"\n            \"Provide a DefaultSessionRunner (with callbacks), or configure a single device in \"\n            \"generation_devices.\"\n        )\n\n    def start(self, invoker: Invoker) -> None:\n        self._invoker: Invoker = invoker\n\n        self._resume_event = ThreadEvent()\n        self._stop_event = ThreadEvent()\n        self._poll_now_event = ThreadEvent()\n\n        register_events(QueueClearedEvent, self._on_queue_cleared)\n        register_events(BatchEnqueuedEvent, self._on_batch_enqueued)\n        register_events(QueueItemStatusChangedEvent, self._on_queue_item_status_changed)\n        register_events(UserAccessChangedEvent, self._on_user_access_changed)\n\n        devices = self._resolve_devices()","sourceCodeStart":579,"sourceCodeEnd":615,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/services/session_processor/session_processor_default.py#L579-L615","documentation":"When multiple generation devices are configured, the session processor must clone the session runner template once per worker. Only DefaultSessionRunner instances can be cloned safely (their callback lists can be copied); any other implementation is rejected with this ValueError because cloning it generically is impossible and sharing one instance is unsafe — every start() overwrites the runner's cancel event, so only the last worker's cancellation would fire. The check fails loudly at startup rather than misbehaving at runtime.","triggerScenarios":"Starting the session processor with generation_devices containing more than one device while the configured session runner is a subclass of DefaultSessionRunner or an entirely unrelated ISessionRunner implementation.","commonSituations":"Multi-GPU setups (e.g. two CUDA cards) where the app was wired with a custom runner for logging or queuing; tests exercising multi-device startup with a stub runner; copy-pasted custom runner classes that worked fine with a single device.","solutions":["Use a DefaultSessionRunner instance (configured with the desired callbacks) as the template when multiple generation devices are enabled.","Reduce generation_devices to a single device if you must keep the custom runner.","Move custom behavior into DefaultSessionRunner callbacks (on getSession/on node/session callbacks) instead of subclassing or replacing the runner.","If a DefaultSessionRunner subclass is intended, pass a plain DefaultSessionRunner with the callbacks attached instead."],"exampleFix":"// before\nclass MyRunner(DefaultSessionRunner): ...  # subclass used as template\nprocessor = SessionProcessor(session_runner=MyRunner(callbacks), generation_devices=[dev1, dev2])\n// after\nprocessor = SessionProcessor(\n    session_runner=DefaultSessionRunner(\n        on_session_start=..., on_session_end=...,\n        on_node_start=..., on_node_error=..., on_after_run_session=...\n    ),\n    generation_devices=[dev1, dev2],\n)","handlingStrategy":"validation","validationCode":"if len(generation_devices) > 1 and not isinstance(session_runner, DefaultSessionRunner):\n    raise TypeError(\"multi-device startup requires a DefaultSessionRunner template\")","typeGuard":"def clonable_for_multi_device(runner, devices) -> bool:\n    return len(devices) <= 1 or isinstance(runner, DefaultSessionRunner)\n# note: exact DefaultSessionRunner is required, not a subclass","tryCatchPattern":"try:\n    processor.start(invoker)\nexcept ValueError as e:\n    if \"Multiple generation devices\" in str(e):\n        logger.error(\"switch to DefaultSessionRunner or single generation device\")\n    raise","preventionTips":["Wire custom behavior through DefaultSessionRunner callbacks, not subclasses","Only use custom ISessionRunner implementations with a single generation device","Test multi-GPU config at startup before production runs","Keep one runner instance per worker; never share runners across devices"],"tags":["invokeai","session-processor","multi-device","configuration","valueerror"],"backgroundTag":"unsupported-configuration","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}