invoke-ai/InvokeAI · error · ValueError
Multiple generation devices require DefaultSessionRunner; go
Error message
Multiple generation devices require DefaultSessionRunner; got {type(template).__name__}. Provide a DefaultSessionRunner (with callbacks), or configure a single device in generation_devices. What it means
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.
Source
Thrown at invokeai/app/services/session_processor/session_processor_default.py:597
"""Create an independent runner for an additional worker.
Each worker needs its own runner because the runner stores its session's cancel event.
We carry over the template's callbacks so all workers behave identically.
"""
# `type is`, not isinstance: a subclass would be silently downgraded to a plain
# DefaultSessionRunner, losing its overrides.
if type(template) is DefaultSessionRunner:
return DefaultSessionRunner(
on_before_run_session_callbacks=list(template._on_before_run_session_callbacks),
on_before_run_node_callbacks=list(template._on_before_run_node_callbacks),
on_after_run_node_callbacks=list(template._on_after_run_node_callbacks),
on_node_error_callbacks=list(template._on_node_error_callbacks),
on_after_run_session_callbacks=list(template._on_after_run_session_callbacks),
)
# Any other implementation cannot be cloned generically, and sharing one instance across
# workers is not safe either: every start() overwrites the runner's stored cancel event, so
# only the last worker's cancellation would ever fire. Fail loudly at startup instead.
raise ValueError(
f"Multiple generation devices require DefaultSessionRunner; got {type(template).__name__}. "
"Provide a DefaultSessionRunner (with callbacks), or configure a single device in "
"generation_devices."
)
def start(self, invoker: Invoker) -> None:
self._invoker: Invoker = invoker
self._resume_event = ThreadEvent()
self._stop_event = ThreadEvent()
self._poll_now_event = ThreadEvent()
register_events(QueueClearedEvent, self._on_queue_cleared)
register_events(BatchEnqueuedEvent, self._on_batch_enqueued)
register_events(QueueItemStatusChangedEvent, self._on_queue_item_status_changed)
register_events(UserAccessChangedEvent, self._on_user_access_changed)
devices = self._resolve_devices()View on GitHub (pinned to 0b6a024f2f)
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.
Example fix
// before
class MyRunner(DefaultSessionRunner): ... # subclass used as template
processor = SessionProcessor(session_runner=MyRunner(callbacks), generation_devices=[dev1, dev2])
// after
processor = SessionProcessor(
session_runner=DefaultSessionRunner(
on_session_start=..., on_session_end=...,
on_node_start=..., on_node_error=..., on_after_run_session=...
),
generation_devices=[dev1, dev2],
) Defensive patterns
Strategy: validation
Validate before calling
if len(generation_devices) > 1 and not isinstance(session_runner, DefaultSessionRunner):
raise TypeError("multi-device startup requires a DefaultSessionRunner template") Type guard
def clonable_for_multi_device(runner, devices) -> bool:
return len(devices) <= 1 or isinstance(runner, DefaultSessionRunner)
# note: exact DefaultSessionRunner is required, not a subclass Try / catch
try:
processor.start(invoker)
except ValueError as e:
if "Multiple generation devices" in str(e):
logger.error("switch to DefaultSessionRunner or single generation device")
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Algorithm {algorithm} not available
- Model '{model_key}' is not a TextLLM model (got {model_confi
- Model '{model_key}' is not a LLaVA OneVision model (got {mod
- LoRA '{lora.lora.key}' has conflicting weights on the transf
- Model '{main_config.name}' is not a Krea-2 main model. Selec
AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29).
Data as JSON: /api/errors/034b7382a124d2cf.
Report an issue: GitHub.