diff --git a/studio/frontend/src/components/assistant-ui/model-selector/pickers.tsx b/studio/frontend/src/components/assistant-ui/model-selector/pickers.tsx index 767e586a07..8ae2111d1e 100644 --- a/studio/frontend/src/components/assistant-ui/model-selector/pickers.tsx +++ b/studio/frontend/src/components/assistant-ui/model-selector/pickers.tsx @@ -2074,8 +2074,13 @@ export function HubModelPicker({ [downloadedSet], ); const deviceBudget = useMemo( + // Largest single device, NOT the multi-GPU sum: the diffusion/video + // backends place the whole pipeline on one device (pipe.to / cpu-offload, + // never device_map), so summed VRAM would pass groups no single card can + // hold (e.g. a 114 GB group "fits" a 4x24 GB host) and a bare group click + // would OOM -- the exact load the fit toggle exists to prevent. () => ({ - gpuGb: gpu.available ? gpu.memoryTotalGb : 0, + gpuGb: gpu.available ? gpu.maxDeviceMemoryGb : 0, systemRamGb: gpu.systemRamAvailableGb || 0, }), [gpu], diff --git a/studio/frontend/src/hooks/use-gpu-info.ts b/studio/frontend/src/hooks/use-gpu-info.ts index 1e313acdf3..9f4cc48eb9 100644 --- a/studio/frontend/src/hooks/use-gpu-info.ts +++ b/studio/frontend/src/hooks/use-gpu-info.ts @@ -8,7 +8,13 @@ import type { SystemInfoResponse } from "./use-system"; export interface GpuInfo { available: boolean; name: string; + /** Sum across every GPU. Right for the chat/llama.cpp path (tensor-split + * shards across cards); wrong as a single-placement budget. */ memoryTotalGb: number; + /** Largest single device. The diffusion/video backends place the whole + * pipeline on one device (pipe.to / cpu-offload, never device_map), so + * their fit budget must not credit VRAM from the other cards. */ + maxDeviceMemoryGb: number; cpuCore: number; cpuThread: number; systemRamAvailableGb: number; @@ -19,6 +25,7 @@ const DEFAULT_GPU: GpuInfo = { available: false, name: "Unknown", memoryTotalGb: 0, + maxDeviceMemoryGb: 0, cpuCore: 0, cpuThread: 0, systemRamAvailableGb: 0, @@ -59,6 +66,9 @@ async function fetchGpuOnce(): Promise { available: true, name: devices[0]?.name ?? "Unknown", memoryTotalGb: devices.reduce((sum, d) => sum + (d.memory_total_gb ?? 0), 0), + maxDeviceMemoryGb: devices.reduce( + (max, d) => Math.max(max, d.memory_total_gb ?? 0), 0, + ), } : { ...DEFAULT_GPU, ...base }; cachedGpu = info;