Co-authored-by: Mick Qian <mickqian@radixark.ai> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1027 lines
49 KiB
React
1027 lines
49 KiB
React
// MiniMax-H3 diffusion deployment matrix. Consumed by _deployment.jsx.
|
||
//
|
||
// H3 is the first opt-in user of the scoped commandBuilder renderer. Topology,
|
||
// checkpoint, server overlays, and request fields share one semantic selection,
|
||
// while the UI presents them by lifecycle and composes them through the existing
|
||
// deployment command engine.
|
||
|
||
|
||
// Single-GPU consumer cards run H3 lossless through layerwise offload. The
|
||
// flags carry only what differs from the defaults; what changes with the
|
||
// machine is the expectation, which the hints spell out per budget. Measured
|
||
// on one RTX 4090 (denoise medians across interleaved runs, outputs verified
|
||
// end to end); 48-64 GB hosts sit between the measured points.
|
||
export const config = (() => {
|
||
// One recipe per VRAM tier, measured under a hard allocator cap of that
|
||
// size: the figures were taken at 12/16/24 GiB caps, so every card of a
|
||
// tier shares them. 30-series cards run the same recipe; their step times
|
||
// land above the measured 40/50-series figures.
|
||
const CONSUMER_12G = ["rtx4070", "rtx5070", "rtx3060"];
|
||
const CONSUMER_16G = ["rtx4080", "rtx5080", "rtx5070ti", "rtx4060ti"];
|
||
const CONSUMER_24G = ["rtx4090", "rtx3090"];
|
||
// The only tier measured on a physical desktop rather than under an allocator
|
||
// cap: an RTX 5090 with 60 GB of host RAM and a PCIe 5.0 NVMe, where the VRAM,
|
||
// the host RAM and the drive are all real at once.
|
||
const CONSUMER_32G = ["rtx5090"];
|
||
// Workstation cards a home builder can actually buy. No hard-cap anchor was
|
||
// measured for these sizes (the lab card is 24 GB and caps only shrink), so
|
||
// their recipes are derived from the tier logic, not verified runs.
|
||
const WORKSTATION_48G = ["rtx6000ada"];
|
||
const WORKSTATION_96G = ["rtxpro6000"];
|
||
// GB10 unified memory: 128 GB shared between CPU and GPU (121.7 GB visible
|
||
// to torch), so the VRAM/host split that shapes every tier above does not
|
||
// exist. The 134 GiB deployment still exceeds the pool, and the loader's
|
||
// automatic placement handles that split better than any explicit flag set:
|
||
// verified on DGX Spark, see unified128Flags().
|
||
const UNIFIED_128G = ["dgx-spark"];
|
||
const CONSUMER_SINGLE = [
|
||
...CONSUMER_12G,
|
||
...CONSUMER_16G,
|
||
...CONSUMER_24G,
|
||
...CONSUMER_32G,
|
||
...WORKSTATION_48G,
|
||
...WORKSTATION_96G,
|
||
...UNIFIED_128G,
|
||
];
|
||
const CONSUMER_VRAM_16_PLUS = [...CONSUMER_16G, ...CONSUMER_24G, ...CONSUMER_32G];
|
||
const CONSUMER_AMPERE = ["rtx3060", "rtx3090"];
|
||
|
||
// The consumer recipes below are single-card. The 5090 is the one consumer
|
||
// card with a verified two-card recipe (TP2, twenty resident layers -- half a
|
||
// layer per GPU), so a two-card selection keeps the generic offload path.
|
||
function consumerSingleCard(s) {
|
||
return (
|
||
CONSUMER_SINGLE.includes(s.hw)
|
||
&& !(CONSUMER_32G.includes(s.hw) && Number(s.gpus_per_node) > 1)
|
||
);
|
||
}
|
||
|
||
function consumerFlags(s) {
|
||
if (UNIFIED_128G.includes(s.hw)) return unified128Flags();
|
||
if (WORKSTATION_96G.includes(s.hw)) return workstation96Flags();
|
||
// The whole video decoder held for the decode only: residency arms at the
|
||
// decoder's first block and releases when it finishes, so the denoise still
|
||
// runs on an empty card. All 36 blocks fit 12 GB because decoder weights are
|
||
// held in their decode compute dtype (fp16, ~4.9 GiB) from load -- the
|
||
// rounding was already in every output, so the result is bit-identical --
|
||
// and the decode drops from 60 s streamed to ~10 s.
|
||
const flags = [
|
||
"--performance-mode memory",
|
||
"--layerwise-offload-components dit,text_encoder,vae",
|
||
"--layerwise-resident-layers video_vae=36",
|
||
];
|
||
if (CONSUMER_VRAM_16_PLUS.includes(s.hw) && s.host_ram === "ram96") {
|
||
flags.push("--dit-layerwise-resident-layers 4");
|
||
}
|
||
// A 24 GB card has headroom for resident DiT layers, but their benefit
|
||
// flattened once the decoder went fp16 and the courier overlapped the
|
||
// streaming: measured at a 22 GiB cap (2 GiB desktop headroom), r10/r6/r4
|
||
// land at 8.41/8.48/8.51 s/step. Six layers keep ~2.4 GiB more free than
|
||
// ten for under 1% of speed -- the desktop-safe point. A 16 GB card keeps
|
||
// the plain recipe; even four resident layers measured slower there.
|
||
if (CONSUMER_24G.includes(s.hw) && s.host_ram === "ram32") {
|
||
flags.push("--dit-layerwise-resident-layers 6");
|
||
}
|
||
// Fourteen layers (~17 GiB) leave the decode its room on a 32 GB card and
|
||
// shrink the streamed set the host has to pin. Measured at a 60 GB host;
|
||
// a smaller host does not change what fits on the card, so the count holds
|
||
// there too and matters more, since fewer streamed layers get pinned.
|
||
if (CONSUMER_32G.includes(s.hw) && s.host_ram !== "ram96") {
|
||
flags.push("--dit-layerwise-resident-layers 14");
|
||
}
|
||
if (WORKSTATION_48G.includes(s.hw)) {
|
||
flags.push("--dit-layerwise-resident-layers 40");
|
||
}
|
||
return flags;
|
||
}
|
||
|
||
function unified128Flags() {
|
||
// No flags: the deployment (134 GiB) exceeds the pool, automatic offload
|
||
// engages on its own and pins 42 of 50 DiT layers. Measured on DGX Spark,
|
||
// the explicit discrete-GPU recipe (--performance-mode memory + offload
|
||
// components + video_vae=36) ran the same denoise 2.1x slower (25.8 vs
|
||
// 12.1 s/it) -- do not carry discrete-card flags onto unified memory.
|
||
return [];
|
||
}
|
||
|
||
function workstation96Flags() {
|
||
// 96 GB holds the whole 61.7 GB DiT; only the encoders and VAEs step aside.
|
||
return [
|
||
"--performance-mode memory",
|
||
"--layerwise-offload-components text_encoder,vae",
|
||
"--layerwise-resident-layers video_vae=36",
|
||
];
|
||
}
|
||
|
||
function consumerHints(s) {
|
||
const hints = [];
|
||
const bigHost = s.host_ram === "ram96";
|
||
const midHost = s.host_ram === "ram64";
|
||
if (bigHost) {
|
||
if (CONSUMER_VRAM_16_PLUS.includes(s.hw)) {
|
||
hints.push(CONSUMER_32G.includes(s.hw)
|
||
? "with the DiT pinned the denoise runs at this card's compute wall, measured at 5.14-5.17 s per step on a physical 5090; the decode holds all 36 blocks in their fp16 decode dtype and takes ~6.4 s"
|
||
: "verified end to end: ~6 s per denoise step, 13 s decode");
|
||
hints.push("fewer resident layers than the 32 GB rows is not a typo: with the DiT pinned in a big host, streamed layers arrive at pinned-copy speed and GPU residency buys little; on a 32 GB host the stream is the bottleneck residency cuts");
|
||
} else {
|
||
hints.push("~6 s per step once the host pins the DiT; the decode holds all 36 blocks in their fp16 decode dtype and takes ~10 s");
|
||
}
|
||
return hints;
|
||
}
|
||
if (CONSUMER_32G.includes(s.hw)) {
|
||
if (midHost) {
|
||
hints.push("measured end to end on a physical desktop (RTX 5090, 60 GB host, PCIe 5.0 NVMe -- VRAM, host RAM and drive all real): 112.2 / 112.1 s per request at 864x480 / 124 frames / 20 steps, against ComfyUI's 140.9-145.9 s on the same weights and sampler settings. Text encoding 4.2 s, denoise 98.0-98.5 s (5.14-5.17 s/step), decode 6.4 s");
|
||
hints.push("at this host size the pin budget covers 34 of the 50 DiT layers, so only ~2 layers per step and the 46 GiB text encoder come off the drive -- 96 GiB per request, read with O_DIRECT and fully hidden behind compute (the compute thread waited 0.9 s per request)");
|
||
} else {
|
||
hints.push("derived, not yet measured at this host size: the card holds the same fourteen resident layers, but the pin budget covers only ~13 of the 36 streamed layers, so ~28 GiB per step comes off the drive. That stays hidden behind the 5.14 s/step compute wall on a drive delivering ~5.5 GiB/s or better (a PCIe 4.0 NVMe); a PCIe 3.0 drive becomes the clock at ~8.8 s/step");
|
||
}
|
||
} else if (CONSUMER_24G.includes(s.hw)) {
|
||
hints.push("measured at 32 GB host under a 22 GiB cap (desktop headroom): ~8.5 s per denoise step with six resident layers, ~9.6 s decode -- ahead of ComfyUI (249-260 s at the 24 GiB cap); a headless card can raise to ten layers for under 1% more");
|
||
} else if (CONSUMER_16G.includes(s.hw)) {
|
||
hints.push("measured at 32 GB host: ~11.9 s per denoise step, ~11 s decode, ~250 s per request -- ahead of ComfyUI (292-301 s) under the same hard 16 GiB cap");
|
||
} else {
|
||
hints.push("measured at 32 GB host: ~10.6 s per denoise step, ~9.4 s decode, ~235 s per request -- ahead of ComfyUI (276-302 s) on the same weights under the same hard 12 GiB cap, output bit-identical");
|
||
}
|
||
if (CONSUMER_AMPERE.includes(s.hw)) {
|
||
hints.push("the recipe and its memory behavior are tier-exact for this card; the step times above were measured on 40-series compute, and Ampere lands above them");
|
||
}
|
||
if (UNIFIED_128G.includes(s.hw)) {
|
||
return [
|
||
"verified on DGX Spark at 480P: ~12.1 s per denoise step steady-state, ~40 s decode, ~12 min per warm request -- with no flags at all; adding the discrete-GPU offload flags measured 2.1x slower on the same box",
|
||
"the text encoder runs ~5.5 min per request and does not warm up: it is steady-state compute on this chip, not a stall -- budget for it",
|
||
"expect ~12 min of server load before the first request; the first request itself runs at full speed (no JIT tax was measured)",
|
||
"step times sit above the discrete-GPU rows because the GB10's ~273 GB/s memory bandwidth is the denoise ceiling, not the placement",
|
||
];
|
||
}
|
||
if (WORKSTATION_96G.includes(s.hw)) {
|
||
hints.push("derived recipe, not yet verified: 96 GB holds the whole 61.7 GB DiT resident, so only the text encoder and VAEs stream -- expect near-datacenter step times rather than the offload figures above");
|
||
}
|
||
if (WORKSTATION_48G.includes(s.hw)) {
|
||
hints.push("derived recipe, not yet verified: 48 GB holds forty of the fifty DiT layers; the figures above are the 24 GB tier's and this card should land well under them");
|
||
}
|
||
hints.push("run with PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True -- the decode sits close enough to the cap that fragmentation otherwise tips it over");
|
||
if (midHost) {
|
||
hints.push("measured on a 12 GB card at a 48 GB host: ~9.6 s/step, ~218 s per request (ComfyUI 246-267 s); at 64 GB: ~8.1 s/step, ~180 s (ComfyUI 194-195 s); larger cards land at or below these");
|
||
} else {
|
||
hints.push("a 32 GB host cannot cache the 108 GB checkpoint, so the drive is in the loop on every step -- see the rate it has to clear, below. A physical desktop that clears it reaches the quoted step times rather than falling short of them");
|
||
}
|
||
hints.push('the startup log should say "leaving ... GiB of weights on the checkpoint mapping" -- if it does not, the host is not the constraint you set');
|
||
hints.push("every figure here is anchored at 480P: activations grow with the pixel count, so at 768P drop the resident DiT layers to 0 first, then video_vae to 24 if the decode still collides -- the flags trade speed for headroom in that order");
|
||
hints.push("what the drive has to deliver is computable: each step re-reads (50 - resident DiT layers - the layers the host pin budget covered) x 1.23 GiB, and the courier overlaps that read with compute, so the drive stops being the denoise clock once its sequential rate exceeds those bytes divided by this card's step time. A 32 GB host covers ~13 layers, which puts a 24 GB card at ~38 GiB/step and a 12/16 GB card at ~46 GiB/step: a PCIe 4.0 NVMe (6-7 GiB/s) clears both, a PCIe 3.0 drive (~3.2 GiB/s) becomes the clock, and a SATA SSD is not usable for this model");
|
||
hints.push("the 38 s/step a physical 4090 with a 990 Pro once measured (52.9 GB per step at ~1.4 GB/s) was the old path: the pinned-store pool rounded every block up to a power of two, so the pin budget over-committed and the machine had to run with pinning off, and the layers that stayed mapped faulted in 4 KiB at a time. Exact-size pins and O_DIRECT reads through the courier removed both -- on the physical 5090 the drive is now fully hidden behind compute. A 4090 on a 32 GB host is derived to return to its compute wall (~8.5 s/step) on a PCIe 4.0 drive; that machine has not been re-measured");
|
||
hints.push("resident DiT layers still remove 1.23 GiB/step of drive traffic each, but only while the streamed set is larger than the pin budget; once the read fits under the step's compute time, more resident layers buy nothing and only cost VRAM");
|
||
hints.push("warm at the shape you will serve (--warmup-resolutions WxH --warmup-num-frames N): the default warmup is 1344x768 x 124 frames, which cost 7.6 min of startup on the physical 5090 and sizes the residency plan for a request you are not going to make");
|
||
hints.push("the pin budget takes 95% of the memory available after loading, so on a machine you also use as a desktop expect swap pressure: the 5090 run above finished with its 8 GB swap file full. There is no supported knob for a smaller budget yet; giving the card more resident layers is the lever that exists, since a resident layer is one the host no longer has to pin");
|
||
hints.push("on Windows run under WSL2, and keep the checkpoint inside the ext4 side (under ~), never on /mnt/c -- the NTFS bridge reads an order of magnitude slower and multiplies the disk clock");
|
||
return hints;
|
||
}
|
||
|
||
return {
|
||
modelName: "MiniMax-H3",
|
||
|
||
supportedHardware: [
|
||
"b200",
|
||
"b300",
|
||
"gb300",
|
||
"gb200",
|
||
"h200",
|
||
"h100",
|
||
"mi300x",
|
||
"mi355x",
|
||
"rtxpro6000",
|
||
"rtx6000ada",
|
||
"dgx-spark",
|
||
"rtx5090",
|
||
"rtx4090",
|
||
"rtx3090",
|
||
"rtx5080",
|
||
"rtx5070ti",
|
||
"rtx4080",
|
||
"rtx4060ti",
|
||
"rtx5070",
|
||
"rtx4070",
|
||
"rtx3060",
|
||
],
|
||
hardware: [
|
||
{ id: "rtxpro6000", label: "RTX PRO 6000", vram: "96GB", vendor: "consumer" },
|
||
{ id: "rtx6000ada", label: "RTX 6000 Ada", vram: "48GB", vendor: "consumer" },
|
||
{ id: "rtx5090", label: "RTX 5090", vram: "32GB", vendor: "consumer" },
|
||
{ id: "rtx4090", label: "RTX 4090", vram: "24GB", vendor: "consumer" },
|
||
{ id: "rtx3090", label: "RTX 3090", vram: "24GB", vendor: "consumer" },
|
||
{ id: "rtx5080", label: "RTX 5080", vram: "16GB", vendor: "consumer" },
|
||
{ id: "rtx5070ti", label: "RTX 5070 Ti", vram: "16GB", vendor: "consumer" },
|
||
{ id: "rtx4080", label: "RTX 4080", vram: "16GB", vendor: "consumer" },
|
||
{ id: "rtx4060ti", label: "RTX 4060 Ti", vram: "16GB", vendor: "consumer" },
|
||
{ id: "rtx5070", label: "RTX 5070", vram: "12GB", vendor: "consumer" },
|
||
{ id: "rtx4070", label: "RTX 4070", vram: "12GB", vendor: "consumer" },
|
||
{ id: "rtx3060", label: "RTX 3060", vram: "12GB", vendor: "consumer" },
|
||
],
|
||
groupHardware: false,
|
||
|
||
matchDims: [],
|
||
|
||
overlayDims: [
|
||
{
|
||
id: "host_ram",
|
||
title: "Host RAM",
|
||
scope: "serve",
|
||
description: "System memory decides where the DiT weights wait between steps: pinned when they fit, on the checkpoint mapping when they do not.",
|
||
default: "ram32",
|
||
showWhen: (s) =>
|
||
CONSUMER_SINGLE.includes(s.hw) && !UNIFIED_128G.includes(s.hw),
|
||
options: [
|
||
{ id: "ram32", label: "32 GB" },
|
||
{ id: "ram64", label: "48-64 GB" },
|
||
{ id: "ram96", label: "96 GB+" },
|
||
],
|
||
},
|
||
{
|
||
id: "weights",
|
||
title: "Checkpoint Weights",
|
||
scope: "base",
|
||
description: "Choose the checkpoint partition required by the request mode.",
|
||
default: "fl2va",
|
||
options: [
|
||
{
|
||
id: "fl2va",
|
||
label: "FL2VA",
|
||
subtitle: "(First-and-Last-Frame-to-Video-and-Audio)",
|
||
flags: ["--model-variant fl2va"],
|
||
},
|
||
{
|
||
id: "ref2va",
|
||
label: "Ref2VA",
|
||
subtitle: "(Reference-to-Video-and-Audio)",
|
||
flags: ["--model-variant ref2va"],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: "mode",
|
||
title: "Request Mode",
|
||
scope: "base",
|
||
description: "The visible modes follow the selected checkpoint.",
|
||
default: "t2va",
|
||
options: [
|
||
{
|
||
id: "t2va",
|
||
label: "Text only",
|
||
showWhen: (s) => s.weights === "fl2va",
|
||
},
|
||
{
|
||
id: "i2va",
|
||
label: "First frame",
|
||
showWhen: (s) => s.weights === "fl2va",
|
||
},
|
||
{
|
||
id: "l2va",
|
||
label: "Last frame",
|
||
showWhen: (s) => s.weights === "fl2va",
|
||
},
|
||
{
|
||
id: "fl2va",
|
||
label: "First + last frames",
|
||
showWhen: (s) => s.weights === "fl2va",
|
||
},
|
||
{
|
||
id: "ref_image",
|
||
label: "Image reference",
|
||
showWhen: (s) => s.weights === "ref2va",
|
||
},
|
||
{
|
||
id: "ref_image_audio",
|
||
label: "Image + audio",
|
||
showWhen: (s) => s.weights === "ref2va",
|
||
},
|
||
{
|
||
id: "v2v",
|
||
label: "Video reference",
|
||
showWhen: (s) => s.weights === "ref2va",
|
||
},
|
||
{
|
||
id: "video_audio",
|
||
label: "Video + soundtrack",
|
||
showWhen: (s) => s.weights === "ref2va",
|
||
},
|
||
{
|
||
id: "audio_only",
|
||
label: "Audio reference",
|
||
showWhen: (s) => s.weights === "ref2va",
|
||
},
|
||
{
|
||
id: "mixed_ref",
|
||
label: "Mixed references",
|
||
showWhen: (s) => s.weights === "ref2va",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: "placement",
|
||
title: "Placement",
|
||
scope: "serve",
|
||
docsHref: "/docs/sglang-diffusion/api/cli#component-residency",
|
||
description: "Keep weights resident for latency; shard or offload only when capacity requires it.",
|
||
quality: "Memory policy",
|
||
learnMore: "#7-feature-contracts-and-advanced-recipes",
|
||
default: "resident",
|
||
options: [
|
||
{
|
||
id: "auto",
|
||
label: "Auto",
|
||
flags: (s) => {
|
||
if (consumerSingleCard(s)) return consumerFlags(s);
|
||
const recipe = config.commandBuilder.resource.verifiedRecipes.find((entry) =>
|
||
entry.hw === s.hw && entry.nodes === Number(s.nodes)
|
||
&& entry.gpus_per_node === Number(s.gpus_per_node));
|
||
const placement = recipe?.placement || (s.hw === "rtx5090" ? "offload" : "resident");
|
||
if (placement === "fsdp") return ["--performance-mode speed", "--use-fsdp-inference true"];
|
||
return placement === "offload" ? [
|
||
"--performance-mode memory",
|
||
"--layerwise-offload-components dit,text_encoder,vae",
|
||
"--dit-layerwise-resident-layers 20",
|
||
] : ["--performance-mode speed"];
|
||
},
|
||
hints: (s) => (consumerSingleCard(s) ? consumerHints(s) : []),
|
||
description: "Use the recommended placement for the selected hardware and resource shape.",
|
||
},
|
||
{
|
||
id: "resident",
|
||
label: "Resident",
|
||
flags: ["--performance-mode speed"],
|
||
disabled: (s) => CONSUMER_SINGLE.includes(s.hw),
|
||
disableReason: "The 61.7 GB DiT cannot be resident on a single consumer card.",
|
||
recommendedWhen: (s) => s.hw !== "rtx5090" && !CONSUMER_SINGLE.includes(s.hw),
|
||
description: "Lowest-latency path when the full pipeline fits in aggregate GPU memory.",
|
||
},
|
||
{
|
||
id: "fsdp",
|
||
label: "FSDP",
|
||
flags: ["--performance-mode speed", "--use-fsdp-inference true"],
|
||
soft: (s) => !["b200", "b300", "h200", "h100"].includes(s.hw) || s.nodes > 1,
|
||
softReason: "Verified on single-node B200/B300/H200/H100. Other hardware and multi-node runs take the same flags but have not been through a verification round.",
|
||
description: "Reduces resident DiT memory but adds parameter collectives on every block.",
|
||
},
|
||
{
|
||
id: "offload",
|
||
label: "Layerwise offload",
|
||
flags: (s) => {
|
||
if (consumerSingleCard(s)) return consumerFlags(s);
|
||
return [
|
||
"--performance-mode memory",
|
||
"--layerwise-offload-components dit,text_encoder,vae",
|
||
"--dit-layerwise-resident-layers 20",
|
||
];
|
||
},
|
||
hints: (s) => (consumerSingleCard(s) ? consumerHints(s) : []),
|
||
soft: (s) => s.hw !== "rtx5090" && !CONSUMER_SINGLE.includes(s.hw),
|
||
softReason: "Tuned and verified on the consumer cards. It runs on the datacenter GPUs too, where a resident recipe is simply faster.",
|
||
recommendedWhen: (s) => s.hw === "rtx5090" || CONSUMER_SINGLE.includes(s.hw),
|
||
description: "Capacity-first PCIe path. It is substantially slower than a resident datacenter recipe.",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: "attention",
|
||
title: "Attention",
|
||
scope: "serve",
|
||
docsHref: "/docs/sglang-diffusion/attention_backends",
|
||
description: "Select the packed-attention kernel used by H3 transformer modules.",
|
||
quality: "Kernel policy",
|
||
learnMore: "#7-feature-contracts-and-advanced-recipes",
|
||
default: "platform",
|
||
options: [
|
||
{
|
||
id: "platform",
|
||
label: "Automatic",
|
||
flags: (s) => ["mi300x", "mi355x"].includes(s.hw) ? ["--attention-backend aiter"] : [],
|
||
env: (s) => ["mi300x", "mi355x"].includes(s.hw) ? ["SGLANG_USE_AITER=1"] : [],
|
||
recommended: true,
|
||
description: "Applies the verified backend policy for the selected hardware.",
|
||
},
|
||
{
|
||
id: "fa",
|
||
label: "FlashAttention",
|
||
flags: ["--attention-backend fa"],
|
||
disabled: (s) => ["mi300x", "mi355x"].includes(s.hw),
|
||
disableReason: "Use the verified AITER platform default on AMD.",
|
||
description: "An explicit native-dtype CUDA comparison path; reduction ordering may still differ.",
|
||
},
|
||
{
|
||
id: "sage",
|
||
label: "SageAttention",
|
||
flags: ["--attention-backend sage_attn"],
|
||
disabled: (s) => ["mi300x", "mi355x"].includes(s.hw),
|
||
disableReason: "SageAttention is not exposed for the AMD recipes.",
|
||
description: "Approximate attention math. Install its packed-varlen dependency and inspect video and audio quality.",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: "precision",
|
||
title: "Precision",
|
||
scope: "serve",
|
||
docsHref: "/docs/sglang-diffusion/quantization",
|
||
description: "Choose native mixed precision or a validated online weight quantization path.",
|
||
quality: "Weight precision",
|
||
learnMore: "#7-feature-contracts-and-advanced-recipes",
|
||
default: "native",
|
||
options: [
|
||
{
|
||
id: "native",
|
||
label: "BF16 / FP32",
|
||
recommended: true,
|
||
description: "Reference mixed precision: BF16 transformer weights with required projections retained in FP32.",
|
||
},
|
||
{
|
||
id: "fp8",
|
||
label: "Online FP8",
|
||
flags: ["--quantization fp8"],
|
||
soft: (s) => !["b200", "b300"].includes(s.hw)
|
||
|| !["auto", "resident"].includes(s.placement)
|
||
|| s.nodes !== 1,
|
||
softReason: "Verified for resident single-node B200/B300. Other hardware and placements take the same flag, but those recipes have not been verified yet.",
|
||
description: "Approximate transformer weight quantization with the required H3 projections protected.",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: "encoder",
|
||
title: "Encoder",
|
||
scope: "serve",
|
||
docsHref: "/docs/sglang-diffusion/encoder_parallel",
|
||
description: "Control text-encoder work placement independently from DiT topology.",
|
||
quality: "Parallel policy",
|
||
learnMore: "#7-feature-contracts-and-advanced-recipes",
|
||
default: "auto",
|
||
options: [
|
||
{
|
||
id: "auto",
|
||
label: "Auto",
|
||
flags: (s) => (s.nodes > 1 ? ["--encoder-parallel replicate"] : []),
|
||
recommended: true,
|
||
description: "Folds on verified single-host P2P systems and resolves to replicate across nodes.",
|
||
},
|
||
{
|
||
id: "dp",
|
||
label: "Data parallel",
|
||
flags: ["--encoder-parallel dp"],
|
||
disabled: (s) => CONSUMER_SINGLE.includes(s.hw) || (s.topology_mode === "manual"
|
||
? Number(s.tp_size)
|
||
: config.commandBuilder.resource.autoTopology(s).tp_size) > 1,
|
||
disableReason: "Encoder DP requires TP1 and a multi-GPU DP group; TP > 1 and the single-card consumer recipes do not qualify.",
|
||
soft: (s) => s.nodes > 1,
|
||
softReason: "Runs across nodes, but the measured 1.9× encode speedup comes from a single-node 2× H100 run; cross-node encoder DP is unverified.",
|
||
description: "Useful for a real request batch; it is not bitwise-identical to fold scheduling.",
|
||
},
|
||
{
|
||
id: "fold",
|
||
label: "Fold",
|
||
flags: ["--encoder-parallel fold"],
|
||
disabled: (s) => s.nodes > 1,
|
||
disableReason: "Fold assumes fast node-local peer-to-peer access.",
|
||
description: "Uses one folded encoder copy across a node-local group and preserves native weights.",
|
||
},
|
||
{
|
||
id: "replicate",
|
||
label: "Replicate",
|
||
flags: ["--encoder-parallel replicate"],
|
||
recommendedWhen: (s) => s.nodes > 1,
|
||
description: "The safe cross-node default because encoder auto is not node-boundary aware.",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: "execution",
|
||
title: "Execution",
|
||
scope: "serve",
|
||
description: "Choose eager execution or the measured breakable CUDA graph path.",
|
||
quality: "Graph policy",
|
||
learnMore: "#7-feature-contracts-and-advanced-recipes",
|
||
default: "eager",
|
||
options: [
|
||
{
|
||
id: "eager",
|
||
label: "Eager",
|
||
recommended: true,
|
||
description: "Reference execution and the consistency baseline.",
|
||
},
|
||
{
|
||
id: "bcg",
|
||
label: "Compatible BCG",
|
||
flags: [
|
||
"--enable-breakable-cuda-graph true",
|
||
"--warmup-resolutions 1344x768",
|
||
"--bcg-text-buckets 5504",
|
||
],
|
||
soft: (s) => !(["b200", "h200"].includes(s.hw) && s.weights === "ref2va"),
|
||
softReason: "Verified for B200/H200 Ref2VA; BCG runs on the other recipes but they have not been through a verification round yet.",
|
||
description: "Reuses matching execution signatures and reserves capture memory; it takes precedence over Cache-DiT.",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: "quality",
|
||
title: "Quality",
|
||
scope: "request",
|
||
docsHref: "/docs/sglang-diffusion/cache_dit",
|
||
description: "Cumulative reference, fusion-only, or audited Cache-DiT execution.",
|
||
quality: "Sampling policy",
|
||
learnMore: "#choose-the-quality-level",
|
||
default: "lossless",
|
||
options: [
|
||
{
|
||
id: "lossless",
|
||
label: "Lossless",
|
||
recommended: true,
|
||
description: "Reference-exact denoising without Cache-DiT approximation.",
|
||
},
|
||
{
|
||
id: "extra-high",
|
||
label: "Extra high",
|
||
description: "Includes fusion-only request paths but not Cache-DiT; MiniMax-H3 currently follows its lossless denoise path at this tier.",
|
||
},
|
||
{
|
||
id: "high",
|
||
label: "Audited high",
|
||
disabled: (s) => s.execution !== "eager",
|
||
disableReason: "BCG supersedes Cache-DiT, so the preset would have no effect — switch Execution to Eager to use it.",
|
||
soft: (s) => !(s.hw === "h200" && s.nodes === 1 && s.gpus_per_node === 4
|
||
&& ["auto", "resident"].includes(s.placement)),
|
||
softReason: "The 1.40× / SSIM 0.931 audit covers the resident eager 4× H200 workload; elsewhere the preset runs but its quality figures are unaudited.",
|
||
description: "Measured 1.40× with SSIM 0.931 and PSNR 28.16 dB on the audited workload.",
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: "outputs",
|
||
title: "Outputs",
|
||
scope: "request",
|
||
description: "Generate independent variants from one request.",
|
||
quality: "1–10",
|
||
kind: "number",
|
||
min: 1,
|
||
max: 10,
|
||
unit: "outputs per prompt",
|
||
default: 1,
|
||
options: [],
|
||
},
|
||
],
|
||
|
||
commandBuilder: {
|
||
defaultSelection: {
|
||
hw: "b200",
|
||
nodes: 1,
|
||
gpus_per_node: 8,
|
||
topology_mode: "auto",
|
||
tp_size: 1,
|
||
ulysses_degree: 8,
|
||
ring_degree: 1,
|
||
},
|
||
resource: {
|
||
limits: {
|
||
nodes: { min: 1, max: 8 },
|
||
gpus_per_node: { min: 1, max: 8 },
|
||
},
|
||
verifiedRecipes: [
|
||
{ id: "b200-resident-8", hw: "b200", nodes: 1, gpus_per_node: 8, placement: "resident", tp_size: 1, ulysses_degree: 8, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "b200-fsdp-4", hw: "b200", nodes: 1, gpus_per_node: 4, placement: "fsdp", tp_size: 1, ulysses_degree: 4, ring_degree: 1, encoder: "auto" },
|
||
{ id: "b300-resident-8", hw: "b300", nodes: 1, gpus_per_node: 8, placement: "resident", tp_size: 1, ulysses_degree: 8, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "b300-fsdp-8", hw: "b300", nodes: 1, gpus_per_node: 8, placement: "fsdp", tp_size: 1, ulysses_degree: 8, ring_degree: 1, encoder: "auto" },
|
||
{ id: "gb300-resident-4", hw: "gb300", nodes: 1, gpus_per_node: 4, placement: "resident", tp_size: 1, ulysses_degree: 4, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "gb200-resident-4", hw: "gb200", nodes: 1, gpus_per_node: 4, placement: "resident", tp_size: 1, ulysses_degree: 4, ring_degree: 1, encoder: "auto", default: true, unverified: true },
|
||
{ id: "h200-resident-4", hw: "h200", nodes: 1, gpus_per_node: 4, placement: "resident", tp_size: 1, ulysses_degree: 4, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "h200-fsdp-4", hw: "h200", nodes: 1, gpus_per_node: 4, placement: "fsdp", tp_size: 1, ulysses_degree: 4, ring_degree: 1, encoder: "auto" },
|
||
{ id: "h200-cross-node-16", hw: "h200", nodes: 2, gpus_per_node: 8, placement: "resident", tp_size: 1, ulysses_degree: 8, ring_degree: 2, encoder: "replicate" },
|
||
{ id: "h100-resident-4", hw: "h100", nodes: 1, gpus_per_node: 4, placement: "resident", tp_size: 2, ulysses_degree: 2, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "h100-fsdp-4", hw: "h100", nodes: 1, gpus_per_node: 4, placement: "fsdp", tp_size: 1, ulysses_degree: 4, ring_degree: 1, encoder: "auto" },
|
||
{ id: "mi300x-resident-1", hw: "mi300x", nodes: 1, gpus_per_node: 1, placement: "resident", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto" },
|
||
{ id: "mi300x-resident-2", hw: "mi300x", nodes: 1, gpus_per_node: 2, placement: "resident", tp_size: 1, ulysses_degree: 2, ring_degree: 1, encoder: "auto" },
|
||
{ id: "mi300x-resident-4", hw: "mi300x", nodes: 1, gpus_per_node: 4, placement: "resident", tp_size: 1, ulysses_degree: 4, ring_degree: 1, encoder: "auto" },
|
||
{ id: "mi300x-resident-8", hw: "mi300x", nodes: 1, gpus_per_node: 8, placement: "resident", tp_size: 1, ulysses_degree: 8, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "mi355x-resident-1", hw: "mi355x", nodes: 1, gpus_per_node: 1, placement: "resident", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto" },
|
||
{ id: "mi355x-resident-2", hw: "mi355x", nodes: 1, gpus_per_node: 2, placement: "resident", tp_size: 1, ulysses_degree: 2, ring_degree: 1, encoder: "auto" },
|
||
{ id: "mi355x-resident-4", hw: "mi355x", nodes: 1, gpus_per_node: 4, placement: "resident", tp_size: 1, ulysses_degree: 4, ring_degree: 1, encoder: "auto" },
|
||
{ id: "mi355x-resident-8", hw: "mi355x", nodes: 1, gpus_per_node: 8, placement: "resident", tp_size: 1, ulysses_degree: 8, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "dgx-spark-offload-1", hw: "dgx-spark", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true, unverified: true },
|
||
{ id: "rtxpro6000-offload-1", hw: "rtxpro6000", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true, unverified: true },
|
||
{ id: "rtx6000ada-offload-1", hw: "rtx6000ada", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true, unverified: true },
|
||
{ id: "rtx5090-offload-1", hw: "rtx5090", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "rtx5090-offload-2", hw: "rtx5090", nodes: 1, gpus_per_node: 2, placement: "offload", tp_size: 2, ulysses_degree: 1, ring_degree: 1, encoder: "auto" },
|
||
{ id: "rtx4090-offload-1", hw: "rtx4090", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "rtx4080-offload-1", hw: "rtx4080", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "rtx3090-offload-1", hw: "rtx3090", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "rtx5080-offload-1", hw: "rtx5080", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "rtx5070ti-offload-1", hw: "rtx5070ti", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "rtx4060ti-offload-1", hw: "rtx4060ti", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "rtx5070-offload-1", hw: "rtx5070", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "rtx3060-offload-1", hw: "rtx3060", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true },
|
||
{ id: "rtx4070-offload-1", hw: "rtx4070", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", default: true },
|
||
],
|
||
autoTopology: (s) => {
|
||
const recipes = config.commandBuilder.resource.verifiedRecipes;
|
||
const exact = recipes.find((recipe) => recipe.hw === s.hw
|
||
&& recipe.nodes === Number(s.nodes)
|
||
&& recipe.gpus_per_node === Number(s.gpus_per_node)
|
||
&& (s.placement === "auto" || recipe.placement === s.placement));
|
||
if (exact) {
|
||
return {
|
||
tp_size: exact.tp_size,
|
||
ulysses_degree: exact.ulysses_degree,
|
||
ring_degree: exact.ring_degree,
|
||
};
|
||
}
|
||
return {
|
||
tp_size: 1,
|
||
ulysses_degree: Number(s.gpus_per_node),
|
||
ring_degree: Number(s.nodes),
|
||
};
|
||
},
|
||
validateTopology: (s, topology) => {
|
||
const errors = [];
|
||
const nodes = Number(s.nodes);
|
||
const perNode = Number(s.gpus_per_node);
|
||
const world = nodes * perNode;
|
||
const tp = Number(topology.tp_size);
|
||
const ulysses = Number(topology.ulysses_degree);
|
||
const ring = Number(topology.ring_degree);
|
||
if (!Number.isInteger(nodes) || nodes < 1 || nodes > 8) errors.push("H3 supports 1–8 nodes.");
|
||
if (!Number.isInteger(perNode) || perNode < 1 || perNode > 8) errors.push("H3 supports 1–8 GPUs per node.");
|
||
if (![1, 2, 4, 8].includes(tp)) errors.push("Tensor parallel size must be one of 1, 2, 4, or 8.");
|
||
if (world !== tp * ulysses * ring) errors.push(`World size ${world} must equal TP × Ulysses × Ring (${tp * ulysses * ring}).`);
|
||
if (56 % tp !== 0 || (56 / tp) % ulysses !== 0) errors.push("H3's 56 attention heads must divide evenly across TP and Ulysses.");
|
||
if (64 % (ulysses * ring) !== 0) errors.push("Ulysses × Ring must divide the 64 packed sequence partitions.");
|
||
return errors;
|
||
},
|
||
},
|
||
resolveDeployment: (s) => {
|
||
const resource = config.commandBuilder.resource;
|
||
const topology = s.topology_mode === "manual"
|
||
? {
|
||
tp_size: Number(s.tp_size),
|
||
ulysses_degree: Number(s.ulysses_degree),
|
||
ring_degree: Number(s.ring_degree),
|
||
}
|
||
: resource.autoTopology(s);
|
||
const errors = resource.validateTopology(s, topology);
|
||
const automaticRecipe = resource.verifiedRecipes.find((entry) => entry.hw === s.hw
|
||
&& entry.nodes === Number(s.nodes)
|
||
&& entry.gpus_per_node === Number(s.gpus_per_node)
|
||
&& entry.tp_size === topology.tp_size
|
||
&& entry.ulysses_degree === topology.ulysses_degree
|
||
&& entry.ring_degree === topology.ring_degree);
|
||
const resolvedPlacement = s.placement === "auto"
|
||
? (automaticRecipe?.placement
|
||
|| (s.hw === "rtx5090" || CONSUMER_SINGLE.includes(s.hw) ? "offload" : "resident"))
|
||
: s.placement;
|
||
const coverageWarnings = [];
|
||
if (resolvedPlacement === "offload" && s.hw !== "rtx5090"
|
||
&& !CONSUMER_SINGLE.includes(s.hw)) {
|
||
coverageWarnings.push("Layerwise offload is tuned and verified on consumer cards; on this hardware it runs unverified and a resident recipe is faster.");
|
||
}
|
||
if (CONSUMER_SINGLE.includes(s.hw) && s.host_ram === "ram64") {
|
||
coverageWarnings.push("48-64 GB hosts sit between the measured 32 GB and 96 GB points and have not been through their own verification round.");
|
||
}
|
||
if (resolvedPlacement === "fsdp" && (s.nodes !== 1 || !["b200", "b300", "h200", "h100"].includes(s.hw))) {
|
||
coverageWarnings.push("FSDP outside the single-node NVIDIA recipes runs unverified.");
|
||
}
|
||
if (s.precision === "fp8" && (! ["b200", "b300"].includes(s.hw)
|
||
|| resolvedPlacement !== "resident" || s.nodes !== 1)) {
|
||
coverageWarnings.push("Online FP8 outside resident single-node B200/B300 runs unverified.");
|
||
}
|
||
const highAudited = s.hw === "h200" && s.nodes === 1
|
||
&& s.gpus_per_node === 4 && resolvedPlacement === "resident";
|
||
if (s.quality === "high" && s.execution !== "eager") {
|
||
coverageWarnings.push("BCG supersedes Cache-DiT, so the high preset has no effect under this execution mode.");
|
||
} else if (s.quality === "high" && !highAudited) {
|
||
coverageWarnings.push("The high preset's 1.40× / SSIM 0.931 figures were audited on resident eager 4× H200; this workload is unaudited.");
|
||
}
|
||
|
||
const recipe = resource.verifiedRecipes.find((entry) => entry.hw === s.hw
|
||
&& entry.nodes === Number(s.nodes)
|
||
&& entry.gpus_per_node === Number(s.gpus_per_node)
|
||
&& entry.placement === resolvedPlacement
|
||
&& entry.tp_size === topology.tp_size
|
||
&& entry.ulysses_degree === topology.ulysses_degree
|
||
&& entry.ring_degree === topology.ring_degree);
|
||
const topologyVerified = !!recipe && !recipe.unverified && errors.length === 0;
|
||
const encoderVerified = s.encoder === "auto"
|
||
|| s.encoder === recipe?.encoder
|
||
|| (s.nodes > 1 && s.encoder === "replicate");
|
||
const attentionVerified = s.attention === "platform";
|
||
const precisionVerified = s.precision === "native"
|
||
|| (s.precision === "fp8" && ["b200", "b300"].includes(s.hw));
|
||
const executionVerified = s.execution === "eager"
|
||
|| (s.execution === "bcg" && ["b200", "h200"].includes(s.hw) && s.weights === "ref2va");
|
||
const checkpointVerified = s.hw !== "gb300" || s.weights === "fl2va";
|
||
const serveVerified = topologyVerified && encoderVerified && attentionVerified
|
||
&& precisionVerified && executionVerified && checkpointVerified;
|
||
const requestCovered = s.hw !== "gb300" || (serveVerified && s.weights === "fl2va"
|
||
&& s.mode === "t2va" && s.quality === "lossless" && Number(s.outputs) === 1);
|
||
const requestVerified = topologyVerified && requestCovered && (["lossless", "extra-high"].includes(s.quality)
|
||
|| (s.quality === "high" && highAudited && s.execution === "eager"));
|
||
|
||
const topologyParts = [];
|
||
if (topology.tp_size > 1) topologyParts.push(`TP ${topology.tp_size}`);
|
||
if (Number(s.nodes) > 1) {
|
||
topologyParts.push(`Ulysses ${topology.ulysses_degree} inside each node`);
|
||
topologyParts.push(`Ring ${topology.ring_degree} across nodes`);
|
||
} else {
|
||
topologyParts.push(`Ulysses ${topology.ulysses_degree}`);
|
||
}
|
||
topologyParts.push({ resident: "Resident", fsdp: "FSDP", offload: "Layerwise offload" }[resolvedPlacement]);
|
||
topologyParts.push(Number(s.nodes) > 1 ? `${s.nodes} nodes` : "Single node");
|
||
|
||
const world = Number(s.nodes) * Number(s.gpus_per_node);
|
||
const flags = ["--model-path {{MODEL_NAME}}"];
|
||
if (world > 1) flags.push(`--num-gpus ${world}`);
|
||
if (topology.ring_degree > 1) flags.push(`--sp-degree ${world}`);
|
||
if (topology.tp_size > 1) flags.push(`--tp-size ${topology.tp_size}`);
|
||
if (topology.ulysses_degree > 1) flags.push(`--ulysses-degree ${topology.ulysses_degree}`);
|
||
if (topology.ring_degree > 1) flags.push(`--ring-degree ${topology.ring_degree}`);
|
||
flags.push("--host {{HOST_IP}}", "--port {{PORT}}");
|
||
|
||
const warnings = [...coverageWarnings];
|
||
if (!topologyVerified && errors.length === 0) {
|
||
warnings.push("This topology satisfies H3's static constraints but has not completed an exact end-to-end verification run.");
|
||
}
|
||
if (resolvedPlacement === "fsdp") {
|
||
warnings.push("FSDP lowers resident DiT memory but adds per-block parameter collectives; prefer Resident when the pipeline fits.");
|
||
}
|
||
if (s.hw === "rtx5090" && Number(s.gpus_per_node) === 2) {
|
||
warnings.push("The 2× RTX 5090 path requires a 384 GiB-class host and prioritizes capacity over latency.");
|
||
}
|
||
|
||
let automaticAttention = "FlashAttention (auto)";
|
||
if (["mi300x", "mi355x"].includes(s.hw)) {
|
||
automaticAttention = "AITER (auto)";
|
||
} else if (topology.ring_degree === 1 && ["b200", "b300", "gb200", "gb300"].includes(s.hw)) {
|
||
automaticAttention = "Dynamic cuDNN / FA (auto)";
|
||
} else if (topology.ring_degree === 1 && ["rtx5090", "rtx4090"].includes(s.hw)) {
|
||
automaticAttention = "Torch SDPA (auto)";
|
||
}
|
||
|
||
return {
|
||
match: { hw: s.hw },
|
||
nnodes: Number(s.nodes),
|
||
verified: serveVerified,
|
||
verificationStatus: serveVerified ? "verified" : "unverified",
|
||
flags,
|
||
builder: {
|
||
topology,
|
||
topologySummary: topologyParts.filter(Boolean).join(" · "),
|
||
errors,
|
||
warnings,
|
||
verification: {
|
||
serve: errors.length ? "error" : (serveVerified ? "verified" : "unverified"),
|
||
request: errors.length ? "error" : (requestVerified ? "verified" : "unverified"),
|
||
},
|
||
resolvedSettings: {
|
||
placement: { resident: "Resident", fsdp: "FSDP", offload: "Layerwise offload" }[resolvedPlacement],
|
||
attention: s.attention === "platform" ? automaticAttention : undefined,
|
||
encoder: s.encoder === "auto" ? (s.nodes > 1 ? "Replicate (auto)" : "Auto") : undefined,
|
||
},
|
||
},
|
||
};
|
||
},
|
||
},
|
||
|
||
modelNames: {
|
||
default: "MiniMaxAI/MiniMax-H3",
|
||
},
|
||
|
||
placeholders: {
|
||
HOST_IP: {
|
||
target: "command",
|
||
label: "Bind host",
|
||
default: "0.0.0.0",
|
||
},
|
||
PORT: {
|
||
target: "command",
|
||
label: "Bind port",
|
||
default: "30010",
|
||
},
|
||
HF_TOKEN: {
|
||
target: "command",
|
||
label: "HF token (Docker)",
|
||
default: "<your-hf-token>",
|
||
},
|
||
MEDIA_DIR: {
|
||
target: "command",
|
||
label: "Host media directory (Docker)",
|
||
default: "/data/minimax-h3",
|
||
},
|
||
CURL_HOST: {
|
||
target: "curl",
|
||
label: "Server host",
|
||
default: "localhost",
|
||
},
|
||
CURL_PORT: {
|
||
target: "curl",
|
||
label: "Server port",
|
||
default: "30010",
|
||
},
|
||
DURATION_SECONDS: {
|
||
target: "curl",
|
||
label: "Duration (seconds, 4-15)",
|
||
default: "5",
|
||
},
|
||
FIRST_FRAME: {
|
||
target: "curl",
|
||
label: "FL2VA first frame URI",
|
||
default: "file:///data/minimax-h3/first-frame.png",
|
||
},
|
||
LAST_FRAME: {
|
||
target: "curl",
|
||
label: "FL2VA last frame URI",
|
||
default: "file:///data/minimax-h3/last-frame.png",
|
||
},
|
||
INPUT_VIDEO: {
|
||
target: "curl",
|
||
label: "First video URI",
|
||
default: "file:///data/minimax-h3/video-1.mp4",
|
||
},
|
||
INPUT_VIDEO_START_SECONDS: {
|
||
target: "curl",
|
||
label: "First video start (seconds)",
|
||
default: "0",
|
||
},
|
||
SECOND_INPUT_VIDEO: {
|
||
target: "curl",
|
||
label: "Second video URI (mixed ref)",
|
||
default: "file:///data/minimax-h3/video-2.mp4",
|
||
},
|
||
SECOND_INPUT_VIDEO_START_SECONDS: {
|
||
target: "curl",
|
||
label: "Second video start (seconds)",
|
||
default: "0",
|
||
},
|
||
REFERENCE_IMAGE: {
|
||
target: "curl",
|
||
label: "First reference image URI",
|
||
default: "file:///data/minimax-h3/reference-1.png",
|
||
},
|
||
SECOND_REFERENCE_IMAGE: {
|
||
target: "curl",
|
||
label: "Second reference image URI",
|
||
default: "file:///data/minimax-h3/reference-2.png",
|
||
},
|
||
REFERENCE_AUDIO: {
|
||
target: "curl",
|
||
label: "First reference audio URI",
|
||
default: "file:///data/minimax-h3/reference-1.mp3",
|
||
},
|
||
SECOND_REFERENCE_AUDIO: {
|
||
target: "curl",
|
||
label: "Second reference audio URI",
|
||
default: "file:///data/minimax-h3/reference-2.mp3",
|
||
},
|
||
},
|
||
|
||
curl: (s) => {
|
||
const request = {
|
||
model: "{{MODEL_NAME}}",
|
||
prompt:
|
||
"Night-vision bedroom footage: while the owner sleeps, three cats burst in playing tiny brass instruments at full volume, freeze, then march out as if nothing happened.",
|
||
seconds: "{{DURATION_SECONDS}}",
|
||
task: "t2va",
|
||
conditions: [],
|
||
target: {
|
||
short_edge: 768,
|
||
aspect_ratio: "16:9",
|
||
duration_seconds: "{{DURATION_SECONDS}}",
|
||
},
|
||
quality: s.quality,
|
||
num_outputs_per_prompt: Number(s.outputs),
|
||
num_inference_steps: 50,
|
||
flow_shift: 12.0,
|
||
audio_flow_shift: 3.0,
|
||
seed: 1101,
|
||
};
|
||
const imageReference = (uri) => ({
|
||
type: "image",
|
||
uri,
|
||
role: "reference",
|
||
});
|
||
const audioReference = (uri) => ({
|
||
type: "audio",
|
||
uri,
|
||
role: "reference",
|
||
});
|
||
const videoReference = (uri, start, type = "video") => ({
|
||
type,
|
||
uri,
|
||
role: "reference",
|
||
start_time_seconds: start,
|
||
});
|
||
|
||
if (["i2va", "l2va", "fl2va"].includes(s.mode)) {
|
||
request.task = "fl2va";
|
||
request.prompt =
|
||
"Continue naturally between the supplied endpoint frame or frames, with synchronized ambient sound.";
|
||
request.target.aspect_ratio = "auto";
|
||
request.seed = 2101;
|
||
request.conditions = [];
|
||
if (s.mode !== "l2va") {
|
||
request.conditions.push({
|
||
type: "image",
|
||
uri: "{{FIRST_FRAME}}",
|
||
role: "keyframe",
|
||
frame_index: 0,
|
||
});
|
||
}
|
||
if (s.mode !== "i2va") {
|
||
request.conditions.push({
|
||
type: "image",
|
||
uri: "{{LAST_FRAME}}",
|
||
role: "keyframe",
|
||
frame_index: -1,
|
||
});
|
||
}
|
||
} else if (s.mode === "ref_image") {
|
||
request.task = "ref2va";
|
||
request.prompt = "Use <Picture 1> as the visual subject and style reference.";
|
||
request.target.aspect_ratio = "auto";
|
||
request.conditions = [imageReference("{{REFERENCE_IMAGE}}")];
|
||
request.seed = 3101;
|
||
} else if (s.mode === "ref_image_audio") {
|
||
request.task = "ref2va";
|
||
request.prompt =
|
||
"Use <Picture 1> as the visual subject and <Audio 1> as the sound reference.";
|
||
request.target.aspect_ratio = "auto";
|
||
request.conditions = [
|
||
imageReference("{{REFERENCE_IMAGE}}"),
|
||
audioReference("{{REFERENCE_AUDIO}}"),
|
||
];
|
||
request.seed = 3102;
|
||
} else if (s.mode === "v2v" || s.mode === "video_audio") {
|
||
request.task = "ref2va";
|
||
request.prompt =
|
||
s.mode === "video_audio"
|
||
? "Follow <Video 1> and its required <Audio 1> soundtrack with coherent synchronized motion."
|
||
: "Follow the appearance and motion of <Video 1>; use its soundtrack when present.";
|
||
request.conditions = [
|
||
videoReference(
|
||
"{{INPUT_VIDEO}}",
|
||
"{{INPUT_VIDEO_START_SECONDS}}",
|
||
s.mode === "video_audio" ? "video_audio" : "video",
|
||
),
|
||
];
|
||
request.seed = s.mode === "video_audio" ? 4102 : 4101;
|
||
} else if (s.mode === "audio_only") {
|
||
request.task = "ref2va";
|
||
request.prompt = "Build a coherent visual scene around <Audio 1>.";
|
||
request.conditions = [audioReference("{{REFERENCE_AUDIO}}")];
|
||
request.seed = 3103;
|
||
} else if (s.mode === "mixed_ref") {
|
||
request.task = "ref2va";
|
||
request.prompt =
|
||
"Combine <Picture 1>, <Picture 2>, <Audio 1>, <Audio 2>, <Video 1>, and <Video 2> in their one-based modality order.";
|
||
request.conditions = [
|
||
imageReference("{{REFERENCE_IMAGE}}"),
|
||
imageReference("{{SECOND_REFERENCE_IMAGE}}"),
|
||
audioReference("{{REFERENCE_AUDIO}}"),
|
||
audioReference("{{SECOND_REFERENCE_AUDIO}}"),
|
||
videoReference("{{INPUT_VIDEO}}", "{{INPUT_VIDEO_START_SECONDS}}"),
|
||
videoReference(
|
||
"{{SECOND_INPUT_VIDEO}}",
|
||
"{{SECOND_INPUT_VIDEO_START_SECONDS}}",
|
||
),
|
||
];
|
||
request.seed = 3104;
|
||
}
|
||
|
||
const body = JSON.stringify(request, null, 2).replace(
|
||
/"{{(DURATION_SECONDS|INPUT_VIDEO_START_SECONDS|SECOND_INPUT_VIDEO_START_SECONDS)}}"/g,
|
||
"{{$1}}",
|
||
);
|
||
return `curl -sS -X POST http://{{CURL_HOST}}:{{CURL_PORT}}/v1/videos \\
|
||
-H 'Content-Type: application/json' \\
|
||
-d '${body}'`;
|
||
},
|
||
|
||
dockerMounts: ["{{MEDIA_DIR}}:/data/minimax-h3:ro"],
|
||
|
||
dockerRunCommand: (s) =>
|
||
["mi300x", "mi355x"].includes(s.hw)
|
||
? `bash -lc 'python -m pip install -e "/sgl-workspace/sglang/python[diffusion_hip]" && exec sglang serve "$@"' --`
|
||
: `bash -lc 'python -m pip install -e "/sgl-workspace/sglang/python[diffusion]" && exec sglang serve "$@"' --`,
|
||
|
||
// Publish Docker only after the platform's H3 image/command has been validated.
|
||
runModes: (s) =>
|
||
["mi300x", "mi355x", "gb200", "gb300"].includes(s.hw)
|
||
? ["python"]
|
||
: ["python", "docker"],
|
||
|
||
dockerImages: {
|
||
b200: "lmsysorg/sglang:dev",
|
||
b300: "lmsysorg/sglang:dev",
|
||
h200: "lmsysorg/sglang:dev",
|
||
h100: "lmsysorg/sglang:dev",
|
||
},
|
||
|
||
showPlaygroundLink: false,
|
||
|
||
cells: [],
|
||
};
|
||
})();
|