mirror of
https://github.com/unslothai/unsloth.git
synced 2026-07-10 00:08:58 +00:00
images/video: apply the fit-on-device toggle to catalog group rows
The Recommended list's fit-on-device toggle filtered the live Hub rows and the flat curated rows, but the canonical catalog GROUP rows (Images / Video pages) were gated only by the format filter. A bare click on a filtered list could then still start an OOM load the toggle was meant to hide (LTX-2 base at 90 GB, the Wan2.2-A14B MoE at 114 GB, both bf16-only with no GGUF fallback). Add catalogGroupFitsDevice: a group stays visible when at least one artifact can actually run here (already downloaded, a GGUF whose quant ladder self-fits, or a sized artifact within 0.7*GPU + 0.7*RAM), mirroring the Recommended fit predicate across a group's formats. Gate the search (matchedCatalogGroups) and the Recommended-section catalog rows (render + roving keys) on it. Node-native catalog:check assertions cover the over-budget, GGUF-fallback, downloaded, unknown -budget, and datacenter-budget cases.
This commit is contained in:
parent
557fc0674a
commit
5e3ab0a8c4
3 changed files with 104 additions and 9 deletions
|
|
@ -13,6 +13,7 @@ import {
|
|||
IMAGE_CATALOG,
|
||||
VIDEO_CATALOG,
|
||||
canonicalKeyFor,
|
||||
catalogGroupFitsDevice,
|
||||
catalogToModelOptions,
|
||||
classifyGgufFit,
|
||||
groupForRepoId,
|
||||
|
|
@ -455,6 +456,49 @@ assert.equal(
|
|||
assert.equal(loadSpecFor("Tongyi-MAI/Z-Image-Turbo", IMAGE_CATALOG)?.kind, "pipeline");
|
||||
assert.equal(loadSpecFor("Qwen/Qwen-Image-2512", IMAGE_CATALOG)?.kind, "pipeline");
|
||||
|
||||
// ── catalogGroupFitsDevice (the fit-on-device toggle for catalog rows) ─────────
|
||||
|
||||
const wanA14b = groupForRepoId("Wan-AI/Wan2.2-T2V-A14B-Diffusers", VIDEO_CATALOG);
|
||||
const ltxBase = groupForRepoId("Lightricks/LTX-2", VIDEO_CATALOG);
|
||||
const hunyuanFit = groupForRepoId(
|
||||
"hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v",
|
||||
VIDEO_CATALOG,
|
||||
);
|
||||
assert.ok(wanA14b && ltxBase && hunyuanFit && ltxGroup);
|
||||
const consumer = { gpuGb: 24, systemRamGb: 64 }; // budget 61.6 GB
|
||||
// A bare-bf16 group over budget is hidden: this is the OOM the toggle must catch.
|
||||
assert.equal(catalogGroupFitsDevice(wanA14b, consumer, notDownloaded), false); // 114 GB
|
||||
assert.equal(catalogGroupFitsDevice(ltxBase, consumer, notDownloaded), false); // 90 GB
|
||||
// A sized bf16 group that fits the budget stays visible (Hunyuan 40/52 GB <= 61.6).
|
||||
assert.equal(catalogGroupFitsDevice(hunyuanFit, consumer, notDownloaded), true);
|
||||
// But on a tiny device even those are hidden.
|
||||
assert.equal(
|
||||
catalogGroupFitsDevice(hunyuanFit, { gpuGb: 8, systemRamGb: 8 }, notDownloaded),
|
||||
false,
|
||||
);
|
||||
// A GGUF in the group is always runnable (its quant ladder self-fits + offloads),
|
||||
// so LTX-2.3 stays visible even on a tiny card despite its 90 GB BF16 sibling.
|
||||
assert.equal(
|
||||
catalogGroupFitsDevice(ltxGroup, { gpuGb: 4, systemRamGb: 4 }, notDownloaded),
|
||||
true,
|
||||
);
|
||||
// An already-downloaded artifact keeps its group visible regardless of budget.
|
||||
assert.equal(
|
||||
catalogGroupFitsDevice(
|
||||
wanA14b,
|
||||
{ gpuGb: 8, systemRamGb: 8 },
|
||||
(id) => id === "Wan-AI/Wan2.2-T2V-A14B-Diffusers",
|
||||
),
|
||||
true,
|
||||
);
|
||||
// Unknown device budget keeps everything (we cannot tell), even a 114 GB group.
|
||||
assert.equal(catalogGroupFitsDevice(wanA14b, { gpuGb: 0, systemRamGb: 0 }, notDownloaded), true);
|
||||
// On a B200-class budget the large bf16 groups fit and stay visible.
|
||||
assert.equal(
|
||||
catalogGroupFitsDevice(wanA14b, { gpuGb: 192, systemRamGb: 256 }, notDownloaded),
|
||||
true,
|
||||
);
|
||||
|
||||
// ── groupMatchesQuery ──────────────────────────────────────────────────────────
|
||||
|
||||
assert.ok(groupMatchesQuery(qwenGroup, "qwen"));
|
||||
|
|
|
|||
|
|
@ -675,3 +675,31 @@ export function pickDefaultArtifact(
|
|||
(a, b) => (a.approxSizeGb ?? Infinity) - (b.approxSizeGb ?? Infinity),
|
||||
)[0];
|
||||
}
|
||||
|
||||
/** Whether the "fit on device" toggle should keep a catalog group. A group stays
|
||||
* visible when at least one artifact can actually run here: one already on disk
|
||||
* (the user has it), a GGUF (its per-quant ladder self-fits and the backend GGUF
|
||||
* path plans its own offload), or a sized artifact whose curated footprint is
|
||||
* within the 0.7*GPU + 0.7*RAM budget. A group of only over-budget (or unsized)
|
||||
* full-precision artifacts -- e.g. the LTX-2 base at 90 GB or Wan2.2-A14B at
|
||||
* 114 GB on a consumer card -- is hidden, so a bare click on a fit-filtered list
|
||||
* can no longer start an OOM load the toggle was meant to hide. An unknown device
|
||||
* budget keeps everything (we cannot tell). Mirrors fitsResident/the Recommended
|
||||
* fit predicate, extended across a group's formats. */
|
||||
export function catalogGroupFitsDevice(
|
||||
group: CatalogGroup,
|
||||
budget: DeviceBudget,
|
||||
isDownloaded: (repoId: string) => boolean,
|
||||
): boolean {
|
||||
const budgetGb =
|
||||
Math.max(0, budget.gpuGb || 0) * 0.7 +
|
||||
Math.max(0, budget.systemRamGb || 0) * 0.7;
|
||||
if (budgetGb <= 0) return true;
|
||||
return group.artifacts.some((a) => {
|
||||
if (isDownloaded(a.repoId)) return true;
|
||||
// A GGUF's quant ladder self-fits (llama-server offloads), so it is always a
|
||||
// runnable fallback -- matching pickDefaultArtifact returning it on any budget.
|
||||
if (a.format === "gguf") return true;
|
||||
return a.approxSizeGb !== undefined && a.approxSizeGb <= budgetGb;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ import {
|
|||
type CatalogGroup,
|
||||
type ModelArtifact,
|
||||
artifactForRepoId,
|
||||
catalogGroupFitsDevice,
|
||||
groupForRepoId,
|
||||
groupMatchesQuery,
|
||||
pickDefaultArtifact,
|
||||
|
|
@ -2435,6 +2436,27 @@ export function HubModelPicker({
|
|||
return map;
|
||||
}, [results, recommendedSearch.results]);
|
||||
|
||||
// The fit-on-device toggle hides a catalog group with nothing runnable here,
|
||||
// exactly as it hides an over-budget Recommended row -- otherwise a bare click
|
||||
// on a fit-filtered list could still start a 90-114 GB OOM load (LTX-2 base,
|
||||
// Wan2.2-A14B). Off = every group passes.
|
||||
const catalogGroupPassesFit = useCallback(
|
||||
(g: CatalogGroup) =>
|
||||
!fitOnDeviceOnly ||
|
||||
catalogGroupFitsDevice(g, deviceBudget, isRepoDownloaded),
|
||||
[fitOnDeviceOnly, deviceBudget, isRepoDownloaded],
|
||||
);
|
||||
|
||||
// Curated catalog rows for the Recommended section (no query): format toggle +
|
||||
// the same fit gate as the live Recommended rows. Shared by the render and the
|
||||
// roving-key list so navigation matches what is on screen.
|
||||
const recommendedCatalogGroups = useMemo(() => {
|
||||
if (!catalog) return [];
|
||||
return catalog.filter(
|
||||
(g) => catalogGroupMatchesFormat(g, formatFilter) && catalogGroupPassesFit(g),
|
||||
);
|
||||
}, [catalog, formatFilter, catalogGroupPassesFit]);
|
||||
|
||||
// Recommended models that match the current search query
|
||||
// Catalog groups matching a typed query (old ids, format tokens, quant names
|
||||
// all match); rendered as canonical rows above the remaining search results.
|
||||
|
|
@ -2443,9 +2465,10 @@ export function HubModelPicker({
|
|||
return catalog.filter(
|
||||
(g) =>
|
||||
groupMatchesQuery(g, debouncedQuery.trim()) &&
|
||||
catalogGroupMatchesFormat(g, formatFilter),
|
||||
catalogGroupMatchesFormat(g, formatFilter) &&
|
||||
catalogGroupPassesFit(g),
|
||||
);
|
||||
}, [catalog, showHfSection, debouncedQuery, formatFilter]);
|
||||
}, [catalog, showHfSection, debouncedQuery, formatFilter, catalogGroupPassesFit]);
|
||||
|
||||
const filteredRecommendedIds = useMemo(() => {
|
||||
if (!showHfSection) return [];
|
||||
|
|
@ -2684,9 +2707,9 @@ export function HubModelPicker({
|
|||
// flat curated safetensors rows.
|
||||
if (catalog) {
|
||||
keys.push(
|
||||
...catalog
|
||||
.filter((g) => catalogGroupMatchesFormat(g, formatFilter))
|
||||
.map((g) => makeModelOptionKey("catalog-group", g.canonicalId)),
|
||||
...recommendedCatalogGroups.map((g) =>
|
||||
makeModelOptionKey("catalog-group", g.canonicalId),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
keys.push(
|
||||
|
|
@ -2713,9 +2736,9 @@ export function HubModelPicker({
|
|||
fineTunedRows,
|
||||
fineTunedCollapsed,
|
||||
filteredRecommendedIds,
|
||||
formatFilter,
|
||||
hfIds,
|
||||
matchedCatalogGroups,
|
||||
recommendedCatalogGroups,
|
||||
sortedLmStudio,
|
||||
lmStudioCollapsed,
|
||||
recommendedRows,
|
||||
|
|
@ -4138,9 +4161,9 @@ export function HubModelPicker({
|
|||
listing's task tags. Without one (legacy), the flat curated
|
||||
safetensors rows. */}
|
||||
{catalog
|
||||
? catalog
|
||||
.filter((g) => catalogGroupMatchesFormat(g, formatFilter))
|
||||
.map((g) => renderCatalogGroupRow(g, "catalog-group"))
|
||||
? recommendedCatalogGroups.map((g) =>
|
||||
renderCatalogGroupRow(g, "catalog-group"),
|
||||
)
|
||||
: curatedSafetensorsRows.map((m) => {
|
||||
const optionKey = makeModelOptionKey(
|
||||
"curated-safetensors",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue