Pular para o conteúdo

GPU device selection & power policy (hybrid iGPU/dGPU routing, per-surface preference, dGPU power lifecycle)

gpu specs/gpu/device-selection.kmd

Normative contract for how ANY Koder component that renders or decodes on a GPU decides WHICH physical GPU to use on hybrid machines (integrated iGPU vs discrete dGPU) and how it manages dGPU power. Materializes stack-RFC-025 into testable rules. The contract (this spec + thin types in koder_kit) is the SHARED layer every component inherits; the MECHANISM is implemented per render engine (Kroma for native apps, Kruze for Chromium/CEF, the video path for decode) — each backend conforms to these rules. Policy is hoisted; mechanism stays at the engine. Per-element routing across two physical GPUs is explicitly out of scope (cross-PCIe per-frame copy negates the win); per-element routing within one GPU is already native to the engine and needs no work.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

GPU device selection & power policy

Normative materialization of stack-RFC-025. Three layers: (1) this shared contract (policy) + thin types in koder_kit; (2) per-engine backends (mechanism); (3) apps inherit via their engine. See the RFC for the rationale (why neither "all in Kore/Kroma" nor "per-module" is correct).

1. Model

  • Surface — the unit a preference attaches to: a tab/window (Kruze), a surface/view (Kroma), a media pipeline (video). Each surface has exactly one effective GPU at a time.
  • GpuPreference (tri-state, per-surface): auto (default) · igpu · dgpu.
  • Effective GPU — the device a surface actually runs on, resolved from the preference + (in auto) the heuristic.

2. Normative rules

  • R1 — Single-GPU no-op. On a machine with one usable GPU, the whole contract is a no-op: every surface uses that GPU, no power lifecycle, no UI. A backend MUST detect GPU count before doing anything.
  • R2 — Default is auto. A surface with no explicit preference resolves via the heuristic (R4). igpu/dgpu are hard overrides that skip the heuristic.
  • R3 — Preference precedence. Per-site/per-app stored override > session user setting > auto. A user override MUST persist (per-site for web, per-app for native) and survive restart.
  • R4 — Heuristic (auto only). A surface is routed to the dGPU when ANY canonical signal fires: a WebGL/WebGPU context requested high-performance; a WebGPU requestAdapter({powerPreference:'high-performance'}); a render canvas/surface ≥ the size threshold; video ≥4K; sustained GPU time over the time threshold. Thresholds are defined HERE (numeric values in koder_kit's constants, single source); signal COLLECTION is the backend's job (R8).
  • R5 — dGPU power lifecycle. The dGPU MUST stay in runtime-D3 (asleep) until ≥1 surface is routed to it, and MUST return to D3 within the idle-release window (default 30 s, defined in koder_kit) after the last dGPU surface closes/migrates away. Backends MUST NOT hold the dGPU awake for iGPU-only surfaces.
  • R6 — Migration semantics. Changing a surface's effective GPU MAY require recreating its render context (GPU device cannot switch mid-context). A backend MUST do this without losing user state (web: reload acceptable; native: re-init surface). Migration MUST be gated behind R3 (explicit pref or heuristic crossing), never spontaneous.
  • R7 — User visibility. When a surface runs on a non-default GPU, the UI MUST expose it (indicator: "running on ") and MUST offer the tri-state setting (R2) + per-surface override (R3). Naming/labels come from the shared vocabulary (R9).
  • R8 — Backend contract. A per-engine backend MUST: (a) report GPU inventory + count; (b) collect the R4 signals in its engine's terms; (c) execute routing/spawn on the chosen device; (d) execute the R5 lifecycle; (e) emit the surface's effective-GPU state to telemetry (R10) and the indicator (R7). It MUST consume the shared GpuPreference/GpuRouter types and thresholds from koder_kit rather than defining its own.
  • R9 — Single vocabulary. Setting names, labels, preference values, and GPU-class names (igpu/dgpu) are defined once in koder_kit and reused by every backend and UI. No backend invents its own names.
  • R10 — Telemetry (observability-first). Each routing decision and power transition MUST emit a structured signal (surface id, chosen GPU, trigger reason, D3 enter/exit) with bounded cardinality; never log PII. Backend + reason are labels, not free text.
  • R11 — Stability carve-outs (platform). Backends MUST honor host stability constraints recorded for the platform — e.g. on the owner's Avell laptop, NEVER force nvidia-drm.modeset=1 (freezes boot with external monitors; standing alert) and prefer the documented offload path. A backend MUST degrade to iGPU-only rather than risk a known-unstable path.
  • R12 — Out of scope (permanent). Per-element routing across two physical GPUs is forbidden (cross-PCIe per-frame copy negates the win). Per-element routing within one GPU is the engine's native compositor behavior and is not governed here.

3. Per-engine backends (informative — mechanism lives at the engine)

EngineComponentMechanism (conforms to R8)
Chromium/CEFKruze (KRUZE-277)GPU-process pinning: __NV_PRIME_RENDER_OFFLOAD/DRI_PRIME (Linux), adapter LUID (Windows), native powerPreference (macOS)
wgpu/VelloKroma (engines/sdk/kroma) → all native appswgpu adapter selection (PowerPreference + enumeration)
HW decodevideo players (mpv etc.)VA-API/NVDEC decode-device selection

Note (verified 2026-06-27): on Linux/X11 the WebGL powerPreference page hint is ignored by the browser — physical-GPU choice happens at GPU-process spawn, so the Chromium backend MUST route via process env, not the page hint.

4. Tests

  • T1 (R1): single-GPU host → no routing decisions emitted, no power transitions, no GPU UI shown.
  • T2 (R4): a surface that fires a canonical signal (e.g. WebGPU high-performance adapter) is routed to the dGPU under auto; one that fires none stays on iGPU.
  • T3 (R5): dGPU is in D3 with only iGPU surfaces open; opening a dGPU surface wakes it; closing the last dGPU surface returns it to D3 within the idle window. (Run on the s.khost1 GPU seat, never on owner hardware — heavy-work-isolation.)
  • T4 (R3): a per-surface override persists across restart and beats the heuristic.
  • T5 (R9): two different backends report the same preference value/label strings (consumed from koder_kit), not divergent ones.
  • T6 (R11): a backend on a host with a known-unstable dGPU path degrades to iGPU-only instead of taking it.

5. Adoption

S1 (this spec + koder_kit types/heuristic) → koder_kit#101. Backends: Kruze (KRUZE-277), Kroma, video — each implements §3 against these rules. The contract is normative now; backends conform as they land.