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

  1. Use a DefaultSessionRunner instance (configured with the desired callbacks) as the template when multiple generation devices are enabled.
  2. Reduce generation_devices to a single device if you must keep the custom runner.
  3. Move custom behavior into DefaultSessionRunner callbacks (on getSession/on node/session callbacks) instead of subclassing or replacing the runner.
  4. 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

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


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/034b7382a124d2cf. Report an issue: GitHub.