fix: standardize OOM/TIGHT model status indicators across model dropdowns
This commit is contained in:
parent
ce33d673b9
commit
42f5ba5fcc
4 changed files with 91 additions and 29 deletions
|
|
@ -103,9 +103,6 @@ function ModelRow({
|
|||
{vramStatus === "tight" && (
|
||||
<span className="text-[9px] font-medium text-amber-400">TIGHT</span>
|
||||
)}
|
||||
{vramStatus === "fits" && (
|
||||
<span className="text-[9px] font-medium text-emerald-500/90">FIT</span>
|
||||
)}
|
||||
{meta ? (
|
||||
<span className="text-[10px] text-muted-foreground">{meta}</span>
|
||||
) : null}
|
||||
|
|
@ -253,9 +250,6 @@ function GgufVariantExpander({
|
|||
{fitStatus === "tight" && (
|
||||
<span className="text-[9px] font-medium text-amber-400">TIGHT</span>
|
||||
)}
|
||||
{fitStatus === "fits" && (
|
||||
<span className="text-[9px] font-medium text-emerald-500/90">FIT</span>
|
||||
)}
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{formatBytes(v.size_bytes)}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -33,11 +33,17 @@ import {
|
|||
import { MODEL_TYPE_TO_HF_TASK } from "@/config/training";
|
||||
import {
|
||||
useDebouncedValue,
|
||||
useGpuInfo,
|
||||
useHfModelSearch,
|
||||
useHfTokenValidation,
|
||||
useInfiniteScroll,
|
||||
} from "@/hooks";
|
||||
import { formatCompact } from "@/lib/utils";
|
||||
import {
|
||||
type TrainingMethod as VramTrainingMethod,
|
||||
type VramFitStatus,
|
||||
buildModelVramMap,
|
||||
} from "@/lib/vram";
|
||||
import { useTrainingConfigStore } from "@/features/training";
|
||||
import type { TrainingMethod } from "@/types/training";
|
||||
import {
|
||||
|
|
@ -50,6 +56,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|||
import { useShallow } from "zustand/react/shallow";
|
||||
|
||||
export function ModelSelectionStep() {
|
||||
const gpu = useGpuInfo();
|
||||
const {
|
||||
modelType,
|
||||
selectedModel,
|
||||
|
|
@ -93,6 +100,24 @@ export function ModelSelectionStep() {
|
|||
|
||||
const resultIds = useMemo(() => hfResults.map((r) => r.id), [hfResults]);
|
||||
|
||||
// Match Studio behavior: only show exception signals (OOM/TIGHT) in training flows.
|
||||
const vramMap = useMemo(() => {
|
||||
const fitMap = buildModelVramMap(
|
||||
hfResults,
|
||||
trainingMethod as VramTrainingMethod,
|
||||
gpu,
|
||||
);
|
||||
const map = new Map<string, { status: VramFitStatus | null; detail: string | null }>();
|
||||
for (const r of hfResults) {
|
||||
const fit = fitMap.get(r.id);
|
||||
map.set(r.id, {
|
||||
status: fit?.status ?? null,
|
||||
detail: r.totalParams ? formatCompact(r.totalParams) : null,
|
||||
});
|
||||
}
|
||||
return map;
|
||||
}, [hfResults, gpu, trainingMethod]);
|
||||
|
||||
const comboboxAnchorRef = useRef<HTMLDivElement>(null);
|
||||
const { scrollRef, sentinelRef } = useInfiniteScroll(
|
||||
fetchMore,
|
||||
|
|
@ -218,19 +243,21 @@ export function ModelSelectionStep() {
|
|||
>
|
||||
<ComboboxList className="p-1 !max-h-none !overflow-visible">
|
||||
{(id: string) => {
|
||||
const r = hfResults.find((r) => r.id === id);
|
||||
const sizeLabel = r?.totalParams
|
||||
? formatCompact(r.totalParams)
|
||||
: null;
|
||||
const entry = vramMap.get(id);
|
||||
const sizeLabel = entry?.detail ?? null;
|
||||
const fitStatus = entry?.status ?? null;
|
||||
const exceeds = fitStatus === "exceeds";
|
||||
return (
|
||||
<ComboboxItem
|
||||
key={id}
|
||||
value={id}
|
||||
className="justify-between"
|
||||
className={`justify-between ${exceeds ? "opacity-50" : ""}`}
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild={true}>
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
<span
|
||||
className={`min-w-0 flex-1 truncate ${exceeds ? "line-through decoration-muted-foreground/50" : ""}`}
|
||||
>
|
||||
{id}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
|
|
@ -241,11 +268,23 @@ export function ModelSelectionStep() {
|
|||
{id}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{sizeLabel ? (
|
||||
<span className="text-xs text-muted-foreground shrink-0">
|
||||
{sizeLabel}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="flex items-center gap-1.5 shrink-0">
|
||||
{fitStatus === "exceeds" && (
|
||||
<span className="text-[9px] font-medium text-red-400">
|
||||
OOM
|
||||
</span>
|
||||
)}
|
||||
{fitStatus === "tight" && (
|
||||
<span className="text-[9px] font-medium text-amber-400">
|
||||
TIGHT
|
||||
</span>
|
||||
)}
|
||||
{sizeLabel ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{sizeLabel}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
</ComboboxItem>
|
||||
);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -37,8 +37,7 @@ import { formatCompact } from "@/lib/utils";
|
|||
import {
|
||||
type TrainingMethod as VramTrainingMethod,
|
||||
type VramFitStatus,
|
||||
checkVramFit,
|
||||
estimateLoadingVram,
|
||||
buildModelVramMap,
|
||||
} from "@/lib/vram";
|
||||
import {
|
||||
listLocalModels,
|
||||
|
|
@ -218,22 +217,23 @@ export function ModelSection() {
|
|||
// Keyed by model id so the render callback is a simple O(1) lookup.
|
||||
// Re-computes when the training method changes (QLoRA=4-bit vs LoRA/Full=fp16).
|
||||
const vramMap = useMemo(() => {
|
||||
const method = trainingMethod as VramTrainingMethod;
|
||||
const fitMap = buildModelVramMap(
|
||||
hfResults,
|
||||
trainingMethod as VramTrainingMethod,
|
||||
gpu,
|
||||
);
|
||||
const map = new Map<
|
||||
string,
|
||||
{ est: number; status: VramFitStatus | null; detail: string | null }
|
||||
>();
|
||||
for (const r of hfResults) {
|
||||
const detail = r.totalParams ? formatCompact(r.totalParams) : null;
|
||||
if (r.totalParams) {
|
||||
const est = estimateLoadingVram(r.totalParams, method);
|
||||
const status = gpu.available
|
||||
? checkVramFit(est, gpu.memoryTotalGb)
|
||||
: null;
|
||||
map.set(r.id, { est, status, detail });
|
||||
} else {
|
||||
map.set(r.id, { est: 0, status: null, detail });
|
||||
}
|
||||
const fit = fitMap.get(r.id);
|
||||
map.set(r.id, {
|
||||
est: fit?.est ?? 0,
|
||||
status: fit?.status ?? null,
|
||||
detail,
|
||||
});
|
||||
}
|
||||
return map;
|
||||
}, [hfResults, gpu, trainingMethod]);
|
||||
|
|
|
|||
|
|
@ -93,3 +93,32 @@ export function checkVramFit(
|
|||
if (ratio <= 1.0) return "tight";
|
||||
return "exceeds";
|
||||
}
|
||||
|
||||
export interface ModelVramMapInput {
|
||||
id: string;
|
||||
totalParams?: number;
|
||||
}
|
||||
|
||||
export interface ModelVramMapEntry {
|
||||
est: number;
|
||||
status: VramFitStatus | null;
|
||||
}
|
||||
|
||||
export function buildModelVramMap(
|
||||
models: ModelVramMapInput[],
|
||||
method: TrainingMethod,
|
||||
gpu: { available: boolean; memoryTotalGb: number },
|
||||
): Map<string, ModelVramMapEntry> {
|
||||
const map = new Map<string, ModelVramMapEntry>();
|
||||
for (const model of models) {
|
||||
if (!model.totalParams) {
|
||||
map.set(model.id, { est: 0, status: null });
|
||||
continue;
|
||||
}
|
||||
|
||||
const est = estimateLoadingVram(model.totalParams, method);
|
||||
const status = gpu.available ? checkVramFit(est, gpu.memoryTotalGb) : null;
|
||||
map.set(model.id, { est, status });
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue