[diffusion] docs: add verified DGX Spark recipe for Qwen-Image 2.1 (#40487)

Co-authored-by: Mick Qian <mickqian@users.noreply.github.com>
This commit is contained in:
Mick
2026-09-21 08:49:35 +08:00
committed by GitHub
co-authored by Mick Qian
parent b912db67ea
commit 6ad78f2281
2 changed files with 54 additions and 28 deletions
@@ -14,9 +14,10 @@ import { config } from '/src/snippets/configs/Qwen/qwen-image-2.1.jsx';
Install the runtime dependencies with `uv pip install "sglang[diffusion]" --prerelease=allow`, Install the runtime dependencies with `uv pip install "sglang[diffusion]" --prerelease=allow`,
then install this integration from its source checkout with then install this integration from its source checkout with
`uv pip install -e "python[diffusion]"`. Use an authorized checkpoint directory in `uv pip install -e "python[diffusion]"`. The picker uses `Qwen/Qwen-Image-2.1`;
place of `/models/qwen-image-2.1`. The recipes below target NVIDIA CUDA on Linux; you can also set a local checkpoint directory under **Variables**. The recipes
the hardware picker selects a tested single-GPU recipe for the full checkpoint. target NVIDIA CUDA on Linux; the picker marks which single-GPU workloads have
been verified with the full checkpoint.
<Deployment config={config} /> <Deployment config={config} />
@@ -27,7 +28,7 @@ steps, and output count. Set reference PNG paths under **Variables**; edits
upload files from the machine running cURL, so they need not exist on the server. upload files from the machine running cURL, so they need not exist on the server.
Hardware selection applies the recommended placement for that GPU. H200, Hardware selection applies the recommended placement for that GPU. H200,
B200, and RTX PRO 6000 96GB keep weights resident; RTX 5090 and RTX 4090 B200, RTX PRO 6000 96GB, and DGX Spark keep weights resident; RTX 5090 and RTX 4090
offload selected components to fit the full pipeline. offload selected components to fit the full pipeline.
Untested topologies and feature combinations remain selectable and are labeled Untested topologies and feature combinations remain selectable and are labeled
**Unverified**. Invalid topology combinations disable Copy. This integration **Unverified**. Invalid topology combinations disable Copy. This integration
@@ -58,6 +59,7 @@ execution, and full-image VAE decoding.
| B200 192GB | Resident / FlashAttention | 2.46 s | 3.02 s | 38.5 GiB | | B200 192GB | Resident / FlashAttention | 2.46 s | 3.02 s | 38.5 GiB |
| RTX PRO 6000 96GB | Resident / Torch SDPA | 8.03 s | 9.63 s | 38.4 GiB | | RTX PRO 6000 96GB | Resident / Torch SDPA | 8.03 s | 9.63 s | 38.4 GiB |
| RTX 4090 24GB | DiT and VAE resident, encoder layerwise offload / FlashAttention | 18.68 s | 21.68 s | 22.7 GiB | | RTX 4090 24GB | DiT and VAE resident, encoder layerwise offload / FlashAttention | 18.68 s | 21.68 s | 22.7 GiB |
| DGX Spark 128GB unified | Resident / Torch SDPA | 35.36 s | 42.23 s | — (unified) |
Measured on 2026-09-20 at 1024×1024, 40 steps, CFG 1, and one RGBA PNG per Measured on 2026-09-20 at 1024×1024, 40 steps, CFG 1, and one RGBA PNG per
request. Times are median HTTP latency after warmup, including PNG serialization request. Times are median HTTP latency after warmup, including PNG serialization
@@ -68,6 +70,25 @@ RTX 5090 uses DiT layerwise offload and Torch SDPA; its recipe has not been
retested with the updated checkpoint. Both RTX 5090 and RTX PRO 6000 use SDPA retested with the updated checkpoint. Both RTX 5090 and RTX PRO 6000 use SDPA
when FlashAttention is selected in this runtime. CPU offload requires host RAM. when FlashAttention is selected in this runtime. CPU offload requires host RAM.
### DGX Spark
Select **DGX Spark** for one GB10 GPU on Linux ARM64 with CUDA 13. Use the
source installation above. The recommended configuration keeps all components
resident, uses native BF16/FP32 precision, and lets the runtime select Torch SDPA:
```bash Command
sglang serve \
--model-path Qwen/Qwen-Image-2.1 \
--performance-mode speed
```
The [128 GB unified memory](https://docs.nvidia.com/dgx/dgx-spark/hardware.html)
is shared by the CPU and GPU. CPU offload is unnecessary for the verified
single-image 1024×1024 workload. Keep full-image VAE decoding and eager execution.
Generation, editing, transparent generation, and transparent editing were
verified with PyTorch 2.13.0+cu130. Spark reports no separate VRAM usage in `nvidia-smi`.
This recipe covers one Spark; multi-node deployment and batching remain unverified.
### Batching ### Batching
Keep **Request batching → Off** and **Outputs → 1** for interactive use. Keep **Request batching → Off** and **Outputs → 1** for interactive use.
@@ -203,7 +224,7 @@ See the [GGUF guide](/docs/sglang-diffusion/quantization#gguf).
NVFP4 requires Blackwell and compatible ModelOpt exports. Select the component NVFP4 requires Blackwell and compatible ModelOpt exports. Select the component
directories using `--component-paths.transformer` and/or directories using `--component-paths.transformer` and/or
`--component-paths.text_encoder`. Keep the FlashInfer backend at `auto` on `--component-paths.text_encoder`. Keep the FlashInfer backend at `auto` on
RTX 5090 and RTX PRO 6000: TensorRT-LLM FP4 GEMM does not support SM120. RTX 5090, RTX PRO 6000, and DGX Spark: TensorRT-LLM FP4 GEMM does not support SM12.x.
These GPUs remain unverified for this model's NVFP4 exports. See the These GPUs remain unverified for this model's NVFP4 exports. See the
[NVFP4 guide](/docs/sglang-diffusion/quantization#modelopt-nvfp4). [NVFP4 guide](/docs/sglang-diffusion/quantization#modelopt-nvfp4).
@@ -1,11 +1,11 @@
export const config = (() => { export const config = (() => {
const sm120Hardware = ["rtx5090", "rtxpro6000"]; const sm12Hardware = ["rtx5090", "rtxpro6000", "dgx-spark"];
const platformAttention = (s) => sm120Hardware.includes(s.hw) ? "sdpa" : "fa"; const platformAttention = (s) => sm12Hardware.includes(s.hw) ? "sdpa" : "fa";
const effectiveAttention = (s) => s.attention === "platform" || (sm120Hardware.includes(s.hw) && s.attention === "fa") ? platformAttention(s) : s.attention; const effectiveAttention = (s) => s.attention === "platform" || (sm12Hardware.includes(s.hw) && s.attention === "fa") ? platformAttention(s) : s.attention;
const config = { const config = {
modelName: "Qwen-Image 2.1", modelName: "Qwen-Image 2.1",
supportedHardware: ["h200", "b200", "rtxpro6000", "rtx5090", "rtx4090"], supportedHardware: ["h200", "b200", "rtxpro6000", "rtx5090", "rtx4090", "dgx-spark"],
hardware: [ hardware: [
{ id: "rtxpro6000", label: "RTX PRO 6000", vram: "96GB", vendor: "consumer" }, { id: "rtxpro6000", label: "RTX PRO 6000", vram: "96GB", vendor: "consumer" },
{ id: "rtx5090", label: "RTX 5090", vram: "32GB", vendor: "consumer" }, { id: "rtx5090", label: "RTX 5090", vram: "32GB", vendor: "consumer" },
@@ -19,7 +19,7 @@ const config = {
id: "weights", id: "weights",
title: "Checkpoint weights", title: "Checkpoint weights",
scope: "base", scope: "base",
description: "One checkpoint serves generation and editing. Set its authorized local path under Variables.", description: "One checkpoint serves generation and editing. Set its Hugging Face repository or local path under Variables.",
default: "default", default: "default",
options: [{ id: "default", label: "Qwen-Image 2.1", flags: [] }], options: [{ id: "default", label: "Qwen-Image 2.1", flags: [] }],
}, },
@@ -45,11 +45,11 @@ const config = {
options: [ options: [
{ {
id: "resident", label: "Resident", id: "resident", label: "Resident",
recommendedWhen: (s) => ["h200", "b200", "rtxpro6000"].includes(s.hw), recommendedWhen: (s) => ["h200", "b200", "rtxpro6000", "dgx-spark"].includes(s.hw),
disabled: (s) => ["rtx5090", "rtx4090"].includes(s.hw) && Number(s.gpus_per_node) === 1, disabled: (s) => ["rtx5090", "rtx4090"].includes(s.hw) && Number(s.gpus_per_node) === 1,
disableReason: "The full resident pipeline exceeds one consumer GPU's memory. Select CPU offload.", disableReason: "The full resident pipeline exceeds one consumer GPU's memory. Select CPU offload.",
flags: ["--performance-mode speed"], flags: ["--performance-mode speed"],
description: "Keep all components on the GPU. Recommended for H200, B200, and RTX PRO 6000 96GB. RTX 5090 and RTX 4090 need offload.", description: "Keep all components resident. Recommended for H200, B200, RTX PRO 6000 96GB, and DGX Spark 128GB unified memory. RTX 5090 and RTX 4090 need offload.",
}, },
{ {
id: "offload", label: "CPU offload", id: "offload", label: "CPU offload",
@@ -80,10 +80,10 @@ const config = {
options: [ options: [
{ {
id: "platform", label: "Automatic", recommended: true, id: "platform", label: "Automatic", recommended: true,
flags: (s) => [`--attention-backend ${platformAttention(s) === "sdpa" ? "torch_sdpa" : "fa"}`], flags: (s) => s.hw === "b200" ? ["--attention-backend fa"] : [],
description: "Uses SDPA on RTX PRO 6000 and RTX 5090, and FlashAttention on the other listed GPUs.", description: "Uses SDPA on RTX PRO 6000, RTX 5090, and DGX Spark, and FlashAttention on the other listed GPUs.",
}, },
{ id: "fa", label: "FlashAttention", flags: ["--attention-backend fa"], description: "Exact attention with a fused kernel. This runtime falls back to Torch SDPA on RTX PRO 6000 and RTX 5090." }, { id: "fa", label: "FlashAttention", flags: ["--attention-backend fa"], description: "Exact attention with a fused kernel. This runtime falls back to Torch SDPA on RTX PRO 6000, RTX 5090, and DGX Spark." },
{ {
id: "sdpa", label: "Torch SDPA", flags: ["--attention-backend torch_sdpa"], id: "sdpa", label: "Torch SDPA", flags: ["--attention-backend torch_sdpa"],
soft: (s) => !config.commandBuilder.resource.verifiedRecipes.some((r) => r.hw === s.hw && r.placement === s.placement && r.attentions.includes("sdpa") && Number(s.gpus_per_node) === r.gpus_per_node), soft: (s) => !config.commandBuilder.resource.verifiedRecipes.some((r) => r.hw === s.hw && r.placement === s.placement && r.attentions.includes("sdpa") && Number(s.gpus_per_node) === r.gpus_per_node),
@@ -143,19 +143,19 @@ const config = {
}, },
{ {
id: "nvfp4_dit", label: "NVFP4 DiT", flags: ['--component-paths.transformer "{{NVFP4_DIT_PATH}}"'], id: "nvfp4_dit", label: "NVFP4 DiT", flags: ['--component-paths.transformer "{{NVFP4_DIT_PATH}}"'],
disabled: (s) => !["b200", "rtxpro6000", "rtx5090"].includes(s.hw), disabled: (s) => !["b200", "rtxpro6000", "rtx5090", "dgx-spark"].includes(s.hw),
disableReason: "Native NVFP4 requires a Blackwell GPU (compute capability 10.0 or newer).", disableReason: "Native NVFP4 requires a Blackwell GPU (compute capability 10.0 or newer).",
soft: true, softReason: "A calibrated ModelOpt-format DiT export passed 1024px/40-step generation, editing, and transparent output on B200. Other exports, RTX PRO 6000, and RTX 5090 need validation.", soft: true, softReason: "A calibrated ModelOpt-format DiT export passed 1024px/40-step generation, editing, and transparent output on B200. Other exports, RTX PRO 6000, and RTX 5090 need validation.",
}, },
{ {
id: "nvfp4_encoder", label: "NVFP4 encoder", flags: ['--component-paths.text_encoder "{{NVFP4_ENCODER_PATH}}"'], id: "nvfp4_encoder", label: "NVFP4 encoder", flags: ['--component-paths.text_encoder "{{NVFP4_ENCODER_PATH}}"'],
disabled: (s) => !["b200", "rtxpro6000", "rtx5090"].includes(s.hw), disabled: (s) => !["b200", "rtxpro6000", "rtx5090", "dgx-spark"].includes(s.hw),
disableReason: "Native NVFP4 requires a Blackwell GPU (compute capability 10.0 or newer).", disableReason: "Native NVFP4 requires a Blackwell GPU (compute capability 10.0 or newer).",
soft: true, softReason: "A calibrated language-encoder export passed generation, editing, and transparent output on B200; vision weights retain native precision. Output quality requires validation.", soft: true, softReason: "A calibrated language-encoder export passed generation, editing, and transparent output on B200; vision weights retain native precision. Output quality requires validation.",
}, },
{ {
id: "nvfp4_both", label: "NVFP4 DiT + encoder", flags: ['--component-paths.transformer "{{NVFP4_DIT_PATH}}"', '--component-paths.text_encoder "{{NVFP4_ENCODER_PATH}}"'], id: "nvfp4_both", label: "NVFP4 DiT + encoder", flags: ['--component-paths.transformer "{{NVFP4_DIT_PATH}}"', '--component-paths.text_encoder "{{NVFP4_ENCODER_PATH}}"'],
disabled: (s) => !["b200", "rtxpro6000", "rtx5090"].includes(s.hw), disabled: (s) => !["b200", "rtxpro6000", "rtx5090", "dgx-spark"].includes(s.hw),
disableReason: "Native NVFP4 requires a Blackwell GPU (compute capability 10.0 or newer).", disableReason: "Native NVFP4 requires a Blackwell GPU (compute capability 10.0 or newer).",
soft: true, softReason: "Combined exports passed generation, editing, transparent output, offload, and TP2 on B200. The small max-calibration sample changes image and alpha values; validate your exported checkpoint.", soft: true, softReason: "Combined exports passed generation, editing, transparent output, offload, and TP2 on B200. The small max-calibration sample changes image and alpha values; validate your exported checkpoint.",
}, },
@@ -169,7 +169,7 @@ const config = {
learnMore: "#5-runtime-features", learnMore: "#5-runtime-features",
default: "auto", default: "auto",
options: [ options: [
{ id: "auto", label: "Auto", flags: ["--encoder-parallel auto"], recommended: true }, { id: "auto", label: "Auto", recommended: true },
{ id: "replicate", label: "Replicate", flags: ["--encoder-parallel replicate"], soft: true, softReason: "Explicit replication has not been verified for this server recipe." }, { id: "replicate", label: "Replicate", flags: ["--encoder-parallel replicate"], soft: true, softReason: "Explicit replication has not been verified for this server recipe." },
{ id: "fold", label: "Fold", flags: ["--encoder-parallel fold"], soft: true, softReason: "Native encoder TP and full-checkpoint TP2 × SP2 editing passed on B200. Requires node-local P2P; this HTTP recipe is unverified." }, { id: "fold", label: "Fold", flags: ["--encoder-parallel fold"], soft: true, softReason: "Native encoder TP and full-checkpoint TP2 × SP2 editing passed on B200. Requires node-local P2P; this HTTP recipe is unverified." },
], ],
@@ -224,7 +224,7 @@ const config = {
learnMore: "#batching", learnMore: "#batching",
default: "off", default: "off",
options: [ options: [
{ id: "off", label: "Off", recommended: true, flags: ["--batching-max-size 1"], description: "Recommended for interactive latency. Resident H200, B200, and RTX PRO 6000 batching did not materially improve throughput in the measured workload." }, { id: "off", label: "Off", recommended: true, description: "Recommended for interactive latency. Resident H200, B200, and RTX PRO 6000 batching did not materially improve throughput in the measured workload." },
{ {
id: "2", label: "Up to 2 images", id: "2", label: "Up to 2 images",
flags: ["--batching-max-size 2", "--batching-delay-ms 20"], flags: ["--batching-max-size 2", "--batching-delay-ms 20"],
@@ -289,8 +289,9 @@ const config = {
{ id: "b200-2-ulysses", hw: "b200", nodes: 1, gpus_per_node: 2, placement: "resident", tp_size: 1, ulysses_degree: 2, ring_degree: 1, encoder: "auto", attentions: ["fa"], batchSizes: [1, 2] }, { id: "b200-2-ulysses", hw: "b200", nodes: 1, gpus_per_node: 2, placement: "resident", tp_size: 1, ulysses_degree: 2, ring_degree: 1, encoder: "auto", attentions: ["fa"], batchSizes: [1, 2] },
{ id: "rtxpro6000-1-resident", hw: "rtxpro6000", nodes: 1, gpus_per_node: 1, placement: "resident", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", attentions: ["sdpa"], batchSizes: [1, 2, 4], default: true }, { id: "rtxpro6000-1-resident", hw: "rtxpro6000", nodes: 1, gpus_per_node: 1, placement: "resident", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", attentions: ["sdpa"], batchSizes: [1, 2, 4], default: true },
{ id: "rtxpro6000-1-offload", hw: "rtxpro6000", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", attentions: ["sdpa"] }, { id: "rtxpro6000-1-offload", hw: "rtxpro6000", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", attentions: ["sdpa"] },
{ id: "rtx5090-1-offload", hw: "rtx5090", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", attentions: ["sdpa"], default: true }, { id: "rtx5090-1-offload", hw: "rtx5090", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", attentions: ["sdpa"], default: true, unverified: true },
{ id: "rtx4090-1-offload", hw: "rtx4090", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", attentions: ["fa", "sdpa"], batchSizes: [1, 2], batchAttentions: ["fa"], default: true }, { id: "rtx4090-1-offload", hw: "rtx4090", nodes: 1, gpus_per_node: 1, placement: "offload", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", attentions: ["fa", "sdpa"], batchSizes: [1, 2], batchAttentions: ["fa"], default: true },
{ id: "dgx-spark-1-resident", hw: "dgx-spark", nodes: 1, gpus_per_node: 1, placement: "resident", tp_size: 1, ulysses_degree: 1, ring_degree: 1, encoder: "auto", attentions: ["sdpa"], batchSizes: [1], default: true },
], ],
autoTopology: (s) => ({ tp_size: 1, ulysses_degree: Number(s.gpus_per_node), ring_degree: 1 }), autoTopology: (s) => ({ tp_size: 1, ulysses_degree: Number(s.gpus_per_node), ring_degree: 1 }),
validateTopology: (s, topology) => { validateTopology: (s, topology) => {
@@ -300,11 +301,12 @@ const config = {
const { tp_size: tp, ulysses_degree: ulysses, ring_degree: ring } = topology; const { tp_size: tp, ulysses_degree: ulysses, ring_degree: ring } = topology;
if (nodes !== 1) errors.push("This picker covers single-node deployment only."); if (nodes !== 1) errors.push("This picker covers single-node deployment only.");
if (![1, 2, 4].includes(perNode)) errors.push("Select one, two, or four GPUs per node."); if (![1, 2, 4].includes(perNode)) errors.push("Select one, two, or four GPUs per node.");
if (s.hw === "dgx-spark" && perNode !== 1) errors.push("DGX Spark has one GPU per node. This picker covers one Spark only.");
if (![tp, ulysses, ring].every((n) => [1, 2, 4].includes(n))) errors.push("TP, Ulysses and Ring must each be 1, 2, or 4."); if (![tp, ulysses, ring].every((n) => [1, 2, 4].includes(n))) errors.push("TP, Ulysses and Ring must each be 1, 2, or 4.");
if (nodes * perNode !== tp * ulysses * ring) errors.push(`World size ${nodes * perNode} must equal TP × Ulysses × Ring (${tp * ulysses * ring}).`); if (nodes * perNode !== tp * ulysses * ring) errors.push(`World size ${nodes * perNode} must equal TP × Ulysses × Ring (${tp * ulysses * ring}).`);
if (32 % (tp * ulysses) !== 0) errors.push("32 attention heads must be divisible by TP × Ulysses."); if (32 % (tp * ulysses) !== 0) errors.push("32 attention heads must be divisible by TP × Ulysses.");
if (ring > 1 && effectiveAttention(s) === "sdpa") errors.push("Ring requires FlashAttention or SageAttention; Torch SDPA is unsupported."); if (ring > 1 && effectiveAttention(s) === "sdpa") errors.push("Ring requires FlashAttention or SageAttention; Torch SDPA is unsupported.");
if (s.precision?.startsWith("nvfp4_") && !["b200", "rtxpro6000", "rtx5090"].includes(s.hw)) errors.push("Native NVFP4 requires a Blackwell GPU. Select B200, RTX PRO 6000, or RTX 5090."); if (s.precision?.startsWith("nvfp4_") && !["b200", "rtxpro6000", "rtx5090", "dgx-spark"].includes(s.hw)) errors.push("Native NVFP4 requires a Blackwell GPU. Select B200, RTX PRO 6000, RTX 5090, or DGX Spark.");
if (perNode === 1 && ["rtx5090", "rtx4090"].includes(s.hw) && s.placement === "resident") errors.push("The full resident pipeline exceeds this GPU's memory. Select CPU offload."); if (perNode === 1 && ["rtx5090", "rtx4090"].includes(s.hw) && s.placement === "resident") errors.push("The full resident pipeline exceeds this GPU's memory. Select CPU offload.");
return errors; return errors;
}, },
@@ -319,7 +321,7 @@ const config = {
&& entry.nodes === Number(s.nodes) && entry.gpus_per_node === Number(s.gpus_per_node) && entry.nodes === Number(s.nodes) && entry.gpus_per_node === Number(s.gpus_per_node)
&& entry.placement === s.placement && entry.tp_size === topology.tp_size && entry.placement === s.placement && entry.tp_size === topology.tp_size
&& entry.ulysses_degree === topology.ulysses_degree && entry.ring_degree === topology.ring_degree); && entry.ulysses_degree === topology.ulysses_degree && entry.ring_degree === topology.ring_degree);
const serveVerified = !!recipe && errors.length === 0 && s.encoder === "auto" const serveVerified = !!recipe && !recipe.unverified && errors.length === 0 && s.encoder === "auto"
&& recipe.attentions.includes(effectiveAttention(s)) && s.precision === "native" && recipe.attentions.includes(effectiveAttention(s)) && s.precision === "native"
&& s.execution === "eager" && s.vae === "full" && s.execution === "eager" && s.vae === "full"
&& (!s.batching || s.batching === "off" || ((recipe.batchSizes || [1]).includes(Number(s.batching)) && (!s.batching || s.batching === "off" || ((recipe.batchSizes || [1]).includes(Number(s.batching))
@@ -333,15 +335,18 @@ const config = {
|| (s.hw === "h200" && s.background === "scene" && s.mode === "text" && s.resolution === "512" && Number(s.steps) === 4 && Number(s.outputs) === 2) || (s.hw === "h200" && s.background === "scene" && s.mode === "text" && s.resolution === "512" && Number(s.steps) === 4 && Number(s.outputs) === 2)
|| (s.hw === "h200" && s.background === "scene" && s.mode === "multi" && s.resolution === "512" && Number(s.steps) === 4 && Number(s.outputs) === 1)); || (s.hw === "h200" && s.background === "scene" && s.mode === "multi" && s.resolution === "512" && Number(s.steps) === 4 && Number(s.outputs) === 1));
const world = Number(s.nodes) * Number(s.gpus_per_node); const world = Number(s.nodes) * Number(s.gpus_per_node);
const flags = ['--model-path "{{MODEL_PATH}}"', "--model-id Qwen-Image-2.1", `--num-gpus ${world}`]; const flags = ['--model-path "{{MODEL_PATH}}"'];
if (world > 1) flags.push(`--num-gpus ${world}`);
if (topology.tp_size > 1) flags.push(`--tp-size ${topology.tp_size}`); if (topology.tp_size > 1) flags.push(`--tp-size ${topology.tp_size}`);
flags.push(`--ulysses-degree ${topology.ulysses_degree}`); if (world > 1) flags.push(`--ulysses-degree ${topology.ulysses_degree}`);
if (topology.ring_degree > 1) flags.push(`--ring-degree ${topology.ring_degree}`); if (topology.ring_degree > 1) flags.push(`--ring-degree ${topology.ring_degree}`);
flags.push("--host {{HOST_IP}}", "--port {{PORT}}"); flags.push("--host {{HOST_IP}}", "--port {{PORT}}");
const warnings = []; const warnings = [];
if (s.hw === "rtx4090" && (Number(s.outputs) > 1 || (s.batching && s.batching !== "off"))) warnings.push("This recipe streams DiT layers for batch memory headroom. Restart with the updated Server command when changing output count or request batching."); if (s.hw === "rtx4090" && (Number(s.outputs) > 1 || (s.batching && s.batching !== "off"))) warnings.push("This recipe streams DiT layers for batch memory headroom. Restart with the updated Server command when changing output count or request batching.");
if (s.batching && s.batching !== "off" && s.mode !== "text") warnings.push("Cross-request batching applies to text-to-image requests. Image edits run separately; use Outputs for multiple images in one edit request."); if (s.batching && s.batching !== "off" && s.mode !== "text") warnings.push("Cross-request batching applies to text-to-image requests. Image edits run separately; use Outputs for multiple images in one edit request.");
if (!serveVerified && !errors.length) warnings.push("This server combination has not completed an exact HTTP verification run."); if (!serveVerified && !errors.length) warnings.push(s.hw === "rtx5090"
? "This RTX 5090 recipe has not been retested with the updated checkpoint."
: "This server combination has not completed an exact HTTP verification run.");
if (!requestVerified && !errors.length) warnings.push("This request shape is outside the verified HTTP matrix."); if (!requestVerified && !errors.length) warnings.push("This request shape is outside the verified HTTP matrix.");
return { return {
match: { hw: s.hw }, nnodes: Number(s.nodes), verified: serveVerified, flags, match: { hw: s.hw }, nnodes: Number(s.nodes), verified: serveVerified, flags,
@@ -355,7 +360,7 @@ const config = {
}, },
resolvedSettings: { resolvedSettings: {
attention: s.attention === "platform" ? `${platformAttention(s) === "sdpa" ? "Torch SDPA" : "FlashAttention"} (auto)` attention: s.attention === "platform" ? `${platformAttention(s) === "sdpa" ? "Torch SDPA" : "FlashAttention"} (auto)`
: sm120Hardware.includes(s.hw) && s.attention === "fa" ? "Torch SDPA (FA fallback)" : undefined, : sm12Hardware.includes(s.hw) && s.attention === "fa" ? "Torch SDPA (FA fallback)" : undefined,
encoder: s.encoder === "auto" && world === 1 ? "Single GPU (auto)" : undefined, encoder: s.encoder === "auto" && world === 1 ? "Single GPU (auto)" : undefined,
}, },
}, },
@@ -365,7 +370,7 @@ const config = {
modelNames: { default: "Qwen-Image-2.1" }, modelNames: { default: "Qwen-Image-2.1" },
placeholders: { placeholders: {
MODEL_PATH: { target: "command", label: "Authorized checkpoint directory", default: "/models/qwen-image-2.1" }, MODEL_PATH: { target: "command", label: "Checkpoint repository or directory", default: "Qwen/Qwen-Image-2.1" },
FP8_DIT_PATH: { target: "command", label: "Serialized FP8 DiT directory", default: "/models/qwen-image-2.1-fp8/transformer" }, FP8_DIT_PATH: { target: "command", label: "Serialized FP8 DiT directory", default: "/models/qwen-image-2.1-fp8/transformer" },
FP8_ENCODER_PATH: { target: "command", label: "Serialized FP8 encoder directory", default: "/models/qwen-image-2.1-fp8/text_encoder" }, FP8_ENCODER_PATH: { target: "command", label: "Serialized FP8 encoder directory", default: "/models/qwen-image-2.1-fp8/text_encoder" },
GGUF_DIT_PATH: { target: "command", label: "GGUF DiT file", default: "/models/qwen-image-2.1-gguf/transformer-Q4_0.gguf" }, GGUF_DIT_PATH: { target: "command", label: "GGUF DiT file", default: "/models/qwen-image-2.1-gguf/transformer-Q4_0.gguf" },