[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:
@@ -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`,
|
||||
then install this integration from its source checkout with
|
||||
`uv pip install -e "python[diffusion]"`. Use an authorized checkpoint directory in
|
||||
place of `/models/qwen-image-2.1`. The recipes below target NVIDIA CUDA on Linux;
|
||||
the hardware picker selects a tested single-GPU recipe for the full checkpoint.
|
||||
`uv pip install -e "python[diffusion]"`. The picker uses `Qwen/Qwen-Image-2.1`;
|
||||
you can also set a local checkpoint directory under **Variables**. The recipes
|
||||
target NVIDIA CUDA on Linux; the picker marks which single-GPU workloads have
|
||||
been verified with the full checkpoint.
|
||||
|
||||
<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.
|
||||
|
||||
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.
|
||||
Untested topologies and feature combinations remain selectable and are labeled
|
||||
**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 |
|
||||
| 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 |
|
||||
| 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
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
directories using `--component-paths.transformer` and/or
|
||||
`--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
|
||||
[NVFP4 guide](/docs/sglang-diffusion/quantization#modelopt-nvfp4).
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
export const config = (() => {
|
||||
const sm120Hardware = ["rtx5090", "rtxpro6000"];
|
||||
const platformAttention = (s) => sm120Hardware.includes(s.hw) ? "sdpa" : "fa";
|
||||
const effectiveAttention = (s) => s.attention === "platform" || (sm120Hardware.includes(s.hw) && s.attention === "fa") ? platformAttention(s) : s.attention;
|
||||
const sm12Hardware = ["rtx5090", "rtxpro6000", "dgx-spark"];
|
||||
const platformAttention = (s) => sm12Hardware.includes(s.hw) ? "sdpa" : "fa";
|
||||
const effectiveAttention = (s) => s.attention === "platform" || (sm12Hardware.includes(s.hw) && s.attention === "fa") ? platformAttention(s) : s.attention;
|
||||
|
||||
const config = {
|
||||
modelName: "Qwen-Image 2.1",
|
||||
supportedHardware: ["h200", "b200", "rtxpro6000", "rtx5090", "rtx4090"],
|
||||
supportedHardware: ["h200", "b200", "rtxpro6000", "rtx5090", "rtx4090", "dgx-spark"],
|
||||
hardware: [
|
||||
{ id: "rtxpro6000", label: "RTX PRO 6000", vram: "96GB", vendor: "consumer" },
|
||||
{ id: "rtx5090", label: "RTX 5090", vram: "32GB", vendor: "consumer" },
|
||||
@@ -19,7 +19,7 @@ const config = {
|
||||
id: "weights",
|
||||
title: "Checkpoint weights",
|
||||
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",
|
||||
options: [{ id: "default", label: "Qwen-Image 2.1", flags: [] }],
|
||||
},
|
||||
@@ -45,11 +45,11 @@ const config = {
|
||||
options: [
|
||||
{
|
||||
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,
|
||||
disableReason: "The full resident pipeline exceeds one consumer GPU's memory. Select CPU offload.",
|
||||
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",
|
||||
@@ -80,10 +80,10 @@ const config = {
|
||||
options: [
|
||||
{
|
||||
id: "platform", label: "Automatic", recommended: true,
|
||||
flags: (s) => [`--attention-backend ${platformAttention(s) === "sdpa" ? "torch_sdpa" : "fa"}`],
|
||||
description: "Uses SDPA on RTX PRO 6000 and RTX 5090, and FlashAttention on the other listed GPUs.",
|
||||
flags: (s) => s.hw === "b200" ? ["--attention-backend fa"] : [],
|
||||
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"],
|
||||
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}}"'],
|
||||
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).",
|
||||
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}}"'],
|
||||
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).",
|
||||
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}}"'],
|
||||
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).",
|
||||
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",
|
||||
default: "auto",
|
||||
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: "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",
|
||||
default: "off",
|
||||
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",
|
||||
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: "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: "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: "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 }),
|
||||
validateTopology: (s, topology) => {
|
||||
@@ -300,11 +301,12 @@ const config = {
|
||||
const { tp_size: tp, ulysses_degree: ulysses, ring_degree: ring } = topology;
|
||||
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 (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 (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 (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.");
|
||||
return errors;
|
||||
},
|
||||
@@ -319,7 +321,7 @@ const config = {
|
||||
&& 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.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"
|
||||
&& s.execution === "eager" && s.vae === "full"
|
||||
&& (!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 === "multi" && s.resolution === "512" && Number(s.steps) === 4 && Number(s.outputs) === 1));
|
||||
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}`);
|
||||
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}`);
|
||||
flags.push("--host {{HOST_IP}}", "--port {{PORT}}");
|
||||
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.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.");
|
||||
return {
|
||||
match: { hw: s.hw }, nnodes: Number(s.nodes), verified: serveVerified, flags,
|
||||
@@ -355,7 +360,7 @@ const config = {
|
||||
},
|
||||
resolvedSettings: {
|
||||
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,
|
||||
},
|
||||
},
|
||||
@@ -365,7 +370,7 @@ const config = {
|
||||
|
||||
modelNames: { default: "Qwen-Image-2.1" },
|
||||
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_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" },
|
||||
|
||||
Reference in New Issue
Block a user