~/satyajit

Few-step Qwen-Image-2.1: two DMD LoRAs, and a speedup that is steps alone

mdjsonmcp

2026-09-26 · 31 min · image-generation · diffusion · flow-matching · distillation · lora · qwen · open-weights · inference-optimization · benchmarks · explainer

Qwen-Image-2.1 samples an image in 40 denoising steps. The piece on the release covers the architecture, a 7B single-stream DiT with an exact prefix KV cache, and I will not repeat it here. This one is about what arrived in the week after: two few-step students.

The tidy framing is two routes: a full distilled checkpoint against LoRA adapters. That was true for about a day. I read the repositories: safetensors headers over HTTP range requests, scheduler configs, both cards, and the cases.json behind Viggle's comparison Space, which holds 32 pairs of end-to-end timings. Measured means I computed it from a file. Reported means it is the publisher's number and I could not re-run it. Reasoned is my arithmetic on the other two. The first version of this piece ran no weights. The section on four CPU cores, added the same day, runs Pruna's adapters and generates images, and its timings are labelled by how busy the box was.

Viggle/Qwen-Image-2.1-viggle-turbo@bb26a0f · snapshot 2026-09-26
announced
6 steps, no CFG, about 5x faster than the 40-step base
measured
7,115,124,736
parameters
7.12B
repo size
25.84 GB
task
text-to-image
library
diffusers
license
other
safetensors
9 shards
largest file
14.23 GB
files
24
downloads
101.5K
likes
282
parameters by dtype
BF167.12B
diffusersloratext-to-imageimage-to-imageimage-editingdistillationdmdturbo

Measured from the headers: v0.2.1 is a rank-256 LoRA of 679,542,784 parameters, 9.55% of the denoiser. The 7.1B the Hub reports is the superseded v0.1 full fine-tune. The 5.02x sums 32 end-to-end pairs on one B200. Qwen Research License, non-commercial.

repo last modified 2026-09-25

PrunaAI/Pruna-Qwen-Image-2.1@113e63b · snapshot 2026-09-26
repo size
2.40 GB
task
text-to-image
library
diffusers
license
other
safetensors
2 shards
largest file
335.6 MB
files
13
downloads
5.1K
likes
99
qwenimage-generationimage-editingrgbaloradistilledfew-step

Measured from the headers: two rank-64, alpha-128 LoRAs of 167,772,160 parameters, 2.36% of the denoiser, on attention and image MLP only. The 6.3x is the 5-step adapter at 2048²; the recommended 8-step one at 1024² is 3.8x. No quality metric. Qwen Research License, non-commercial.

repo last modified 2026-09-24

Why the base model takes 40 steps

Qwen-Image-2.1 is a flow-matching model. Training mixes an image x0x_0 with Gaussian noise ϵ\epsilon as xσ=(1−σ) x0+σ ϵx_\sigma = (1-\sigma)\,x_0 + \sigma\,\epsilon and teaches the denoiser vθv_\theta to predict the velocity ϵ−x0\epsilon - x_0. Sampling runs it backwards: start at σ=1\sigma = 1, pure noise, and take Euler steps down a list of noise levels to σ=0\sigma = 0. That is what FlowMatchEulerDiscreteScheduler.step does:

xσi+1=xσi+(σi+1−σi) vθ(xσi,σi)x_{\sigma_{i+1}} = x_{\sigma_i} + (\sigma_{i+1} - \sigma_i)\, v_\theta(x_{\sigma_i}, \sigma_i)

Each step is one forward pass of the 7B denoiser, and the step count is set by how curved the path is. The ideal velocity at any point is an average, v⋆(x,σ)=E[ϵ−x0∣xσ=x]v^\star(x, \sigma) = \mathbb{E}[\epsilon - x_0 \mid x_\sigma = x]. At pure noise every image the prompt allows is still possible, so the velocity points at their average, and one Euler step from there lands on the average image: a blur. As σ\sigma falls the posterior narrows, the direction swings toward one image, and the path bends. Euler holds the direction fixed across a step, so it overshoots every bend by an amount that grows with the step. Forty small steps keep that error small. Six large ones do not, unless the network is retrained to take them.

starting noise
picture kind 1picture kind 2picture kind 3averagenoise
teacher, 6 Euler steps (solid)lands 0.21 from the 400-step endpointstudent, 6 steps (dashed)on the line by construction
teacher forward passes40 x 1 = 40student forward passes6 x 1 = 6ceiling on the speedup6.67x

Qwen-Image-2.1's pipeline default: true_cfg_scale = 1.0, one pass per step. The only thing a student can remove from this baseline is steps.

A toy with an exact velocity field, so every error shown is the sampler's, not a model's. Steps follow the shift-2 schedule the Qwen-Image-2.1 pipeline computes at 1024 x 1024. At N = 1 the teacher lands on the average of the three modes: a blur. The dashed straight path is what trajectory methods (reflow, consistency) train toward; a DMD student matches the distribution instead, and may land on a different mode from the same noise.

The toy's velocity field is exact, so its error is the sampler's alone. At N = 1 the teacher lands on the average of the three clusters. From 4 to 8 steps it heads for the right cluster and misses the 400-step endpoint; at 40 it is close. A few-step student is trained to close that gap.

The second cost, which this base does not pay

Classifier-free guidance runs the denoiser twice per step, with and without the prompt, and extrapolates between the two. It doubles the passes. Qwen-Image 1.0 and Qwen-Image-2512 both call their pipelines with a negative prompt, num_inference_steps=50 and true_cfg_scale=4.0: 100 forward passes an image. Qwen-Image-2.1's card calls its pipeline with 40 steps and no guidance argument. The diffusers pipeline defaults to true_cfg_scale=1.0, and switches guidance on only when the scale exceeds 1 and a negative prompt is passed. The denoiser has no guidance embedder either: its only conditioning on the step is time_text_embed.timestep_embedder (measured, from the tensor names). So the 40-step baseline is 40 passes, not 80.

This shapes every speed claim below. Distillation usually removes two costs at once, because the student is trained to reproduce the teacher's guided output in one pass per step. Here the guidance half was already gone before either student arrived, and the budget is the step ratio alone: 40/6 = 6.67x for Viggle's default, 40/8 = 5x and 40/5 = 8x for Pruna's adapters. Those are ceilings, before the text encoder and the VAE, which no step count touches.

The distillation families, and where these two sit

Every few-step method trains a student to do in N big steps what the teacher does in many small ones. They differ in what "the same" means.

FamilyThe student is trained toExamplesUsed here
Consistencymap any point on the teacher's trajectory to that trajectory's endpointCM, LCM, LCM-LoRAno
Continuous-time consistencythe same in the limit of tiny steps, through a Jacobian-vector-product losssCM; rCM adds score distillation as a regulariserno
Rectified-flow reflowregenerate noise-image pairs with the teacher and retrain on straight lines between themRectified flow, InstaFlowno
Adversarialfool a discriminator on its samplesADD (DINOv2 features), LADD (the teacher's latent features; SD3-Turbo in four unguided steps)not stated
Distribution matchingproduce samples the teacher's score cannot tell from its ownDMD, DMD2, SenseFlowboth
Guidance distillationreproduce the two-pass guided output in one pass, with the scale as an inputMeng et al.moot: no CFG to remove

The first three are trajectory methods. They care which image a given noise becomes, which is the dashed straight path in the toy. Distribution matching does not. DMD keeps two score estimates: the teacher's, and a "fake" one trained online on the student's own outputs. It re-noises a student sample, asks both where it should go, and pushes the student along the difference, which is the gradient of the reverse KL divergence KL(pstudent ∥ pteacher)\mathrm{KL}(p_\text{student} \,\|\, p_\text{teacher}). DMD2 dropped the first paper's regression loss on teacher-generated pairs, added a two-time-scale update for the critic and a GAN term on real images, and made the student multi-step by simulating its own inference during training. The failure this family invites is mode collapse, because reverse KL is mode-seeking; the rCM paper reports matching DMD2's quality "while mitigating mode collapse".

Viggle says what it did (reported): "DMD2 (Yin et al., 2024) / SenseFlow-style distribution matching", plus SenseFlow's intra-segment guidance, in which "the student's velocity over each of its segments is regressed on the teacher's", with teacher targets made from rewritten prompts. The weights are the step-700 EMA student of a run named v6_isg, trained on a 4-step schedule. Whether a GAN term survived, the card does not say.

It also measured the collapse. Its "sample diversity" is the mean DINOv2 patch distance between 8 seeds of one prompt, over 32 prompts, as a ratio to the base model. The v0.1 students scored 0.75 (LoRA) and 0.72 (full fine-tune): seeds landing on near-identical layouts. v0.2.1 scores 0.98. These are Viggle's numbers on its own prompts, and the most useful on either card: they test for the failure the method is known for.

Pruna says one sentence: "Training is based on DMD." No loss terms, no teacher settings, no code. The sentence after it, "Improved using Qwen", reads like a method and is a licence obligation: clause 4(b) of the Qwen Research License says anyone who uses the model or its outputs to train or improve a model they distribute "shall prominently display 'Built with Qwen' or 'Improved using Qwen'". Viggle's card uses the other phrase.

What is actually in each repository

Measured from the safetensors headers:

FileRankAlphaTensorsParametersShare of denoiserTargetsSize
Viggle v0.2.1, 6-step256256454679,542,7849.55%q, k, v, out, 3 MLP, shared modulation, timestep embedder1.36 GB
Viggle v0.2.1, rank cut128128454339,771,3924.78%same0.68 GB
Viggle v0.1 LoRA, 4-step6464454169,885,6962.39%same0.34 GB
Viggle v0.1 transformer/fulln/a2977,115,124,736100%every weight14.23 GB
Pruna v0.1, 8-step and 5-step64128448167,772,1602.36%q, k, v, out, 3 MLP0.34 GB each

There is no full-checkpoint route any more. Viggle shipped one as v0.1 on 22 September, a fine-tune of all 7,115,124,736 weights beside a rank-64 LoRA, moved to a rank-256 LoRA a day later, and now says of the old files "there is no reason to prefer them". I compared the first 64 KB of seven of the full fine-tune's tensors with the base's, and every one had changed. The share of bf16 values that differ is 31% in block 0's query projection, 56% in block 31's MLP output, 75% in the shared modulation and 84% in the timestep embedder (measured). The step-conditioning path moved most. That is the part a sampler that only ever sees four noise levels has most reason to relearn (reasoned).

The two adapters sit on the same 32 blocks and differ in two ways.

Pruna's alpha is twice its rank, so "keep the LoRA strength at 1.0" applies the update at scale 2.

Why not merge the update into the weights and pay nothing per step? Viggle measured that too: round-to-nearest into bf16 keeps about 70% of its update on average, and about 40% in some early MLP layers, because the update is small against a bf16 weight's rounding step (reported). Both cards therefore run the adapter unmerged, as a side branch Wx+BAxWx + BAx on every call. That is exact, and it is not free; the speed section finds the bill.

Both students run the same clock

The schedules are where the two releases turn out closest. Viggle passes raw nodes [1.0, 0.9375, 0.875, 0.75, 0.5, 0.25] through the pipeline's own resolution-dependent shift. Pruna switches the shift off and passes nodes already shifted, 2t/(1+t)2t/(1+t) on evenly spaced tt: a fixed shift of 2. At 1024 x 1024 the pipeline's own shift is eμ=2.0008e^{\mu} = 2.0008, so the two land on one grid.

output, squarepipeline shift emu = 2.0008
Qwen-Image-2.140 stepsViggle v0.2.16 stepsViggle v0.2.18 steps, textPruna v0.18 stepsPruna v0.15 stepssigma 1, pure noise0, image

At 1024px the pipeline's own shift is 2.0008, within 0.001 of Pruna's hard-coded 2. 7 of Viggle's 8 dense-text nodes, counting the start at 1, sit on Pruna's 8-step nodes; the dashed guides are the three that every student schedule here shares: 6/7, 2/3 and 2/5.

Computed from each repo's scheduler config and card, not measured. Filled dots are where the denoiser runs; the hollow dot at 0 is the image. The base row includes the shift_terminal stretch that makes its last non-zero node 0.02; both students switch it off.

Three things follow, reasoned from the configs.

Checking the speed claims

Both claims are end-to-end, and they are not on the same GPU.

  • text-to-image, 1024 · 8 steps6.10 s → 1.61 s = 3.79x of 5.00x (76%)
  • text-to-image, 1024 · 5 steps6.10 s → 1.06 s = 5.75x of 8.00x (72%)
  • text-to-image, 2048 · 8 steps31.44 s → 7.60 s = 4.14x of 5.00x (83%)
  • text-to-image, 2048 · 5 steps31.44 s → 4.98 s = 6.31x of 8.00x (79%)
  • edit, 1024 · 8 steps7.05 s → 2.01 s = 3.51x of 5.00x (70%)
  • edit, 1024 · 5 steps7.05 s → 1.43 s = 4.93x of 8.00x (62%)
  • edit, 2048 · 8 steps34.40 s → 8.54 s = 4.03x of 5.00x (81%)
  • edit, 2048 · 5 steps34.40 s → 5.73 s = 6.00x of 8.00x (75%)
Bars are the measured speedup as a share of 40 / N, the forward-pass ratio. Timings are the publishers', not mine. Base with its KV cache on, adapters with it off and unmerged. The 6.3x headline is the 5-step adapter at 2048, a resolution the card says is outside its training.
A dark bar chart titled Text-to-Image Generation Latency at 1024 by 1024 on one H100 80GB, BF16, batch size 1. Qwen-Image-2.1 base at 40 steps takes 6.10 seconds; the 8-step LoRA takes 1.61 seconds, labelled 3.8 times faster; the 5-step LoRA takes 1.06 seconds, labelled 5.7 times faster.
At the training resolution: 3.8x for the recommended 8-step adapter (Pruna-Qwen-Image-2.1 model card, Figure 1).
The same chart at 2048 by 2048. The base at 40 steps takes 31.44 seconds; the 8-step LoRA 7.60 seconds, 4.1 times faster; the 5-step LoRA 4.98 seconds, 6.3 times faster.
Where the 6.3x comes from: the 5-step adapter at 2K, outside its training coverage (Pruna-Qwen-Image-2.1 model card, Figure 2).

Viggle's "about 5x" holds. Summing all 32 pairs gives 633.7 s against 126.3 s, 5.02x (measured, from cases.json). By task it is 5.55x for text-to-image (median 26.1 s against 4.7 s), 83% of the 6.67x ceiling, and 4.7x for single-reference edits (median 14.2 s against 3.0 s).

Pruna's "up to 6.3x" is one cell of eight. It is the 5-step adapter at 2048², 31.44 s against 4.98 s: the adapter its card calls "visibly worse", at a resolution its card says it did not train at. The recommended default, the 8-step adapter at 1K, is 3.8x for text-to-image and 3.5x for editing (reported). Every ratio on the four charts reproduces from its bar labels, with one rounding slip: 6.10/1.06 is 5.75, charted as 5.7x.

The adapters' steps cost more than the base's. Pruna's 5- and 8-step runs share their fixed work, so their difference is three steps. That gives the adapter's per-step cost, the fixed encode-and-decode cost, and from those the base's per-step cost (reasoned; tick "per-step fit" above). At 1K an adapter step is 0.183 s against the base's 0.149 s, 23% dearer; at 2K, 0.873 s against 0.771 s, 13%. The arms differ in the unmerged side branch and the KV cache. The branch adds only 2.4% to the multiply-accumulates of the seven projections it wraps, and in text-to-image the cached prefix is just the prompt, so most of the gap is not FLOPs. My guess is the fourteen small matmuls per block, each rereading its input; nothing published separates the causes. At the base's per-step cost, the 5-step run at 1K would take about 0.89 s: 6.9x rather than 5.75x.

Editing gains less, and the KV cache is why. The base caches the keys and values of the prompt and reference images after its first step (the mechanism, and how it measured). Its first step is a full pass over references and target; the other 39 run the target only. A 6-step student gets five cheap steps, so the full step and the reference encoding are a far larger share of its time. Viggle's worst case shows it: an edit with ten references, about 41,000 condition tokens against 9,216 target tokens, is 36.6 s against 9.9 s, 3.7x. Solving those two timings for a fixed cost and a per-step cost puts 5 to 6 s of the student's 9.9 s outside the loop (reasoned; the range is whether a student step costs the same as a base step or 15% more).

Pruna's edit rows hold a small puzzle. The fitted edit step at 1K is only 5.5% dearer than the text-to-image step, yet an uncached edit with a megapixel reference would process twice the tokens. Either those runs had the cache on, as the card's examples do, or the reference was small. The card does not say (reasoned).

What the quality claims measure

Viggle's card says the student is "very competitive with it in quality: on the official Qwen examples the two are hard to tell apart on most prompts". The evidence is of two kinds. The first is the comparison set: 32 pairs, one sample each, no seed picking. I looked at two of the five dense-text examples, where the card says the student is weakest.

Two nearly identical architecture presentation boards for Olavinlinna Castle side by side: an aerial view of a stone castle on a rocky island, an elevation with dimension lines, detail panels and Chinese labels. Layout, castle and panels match; the right one is slightly softer.
Same prompt, same seed, same layout. Left: Qwen-Image-2.1 at 40 steps, 27.6 s. Right: viggle-turbo v0.2.1 at 6 steps, 4.9 s. Both on one B200 at 2048 x 2048; the side-by-side layout is mine (Viggle demo Space, Comparison tab, architecture presentation board).
Three crops of the same three-line figure caption of generated small print. Top, clean and fully legible. Middle, the first two lines are doubled and smeared like a double exposure, with several words unreadable. Bottom, the first line is mostly clean again and the second line is still partly garbled.
Small Latin text at native resolution. Top: base, 40 steps. Middle: v0.2.1 at 6 steps, the double exposure Viggle's notes describe. Bottom: v0.2.1 at 8 steps. Crops are mine (Viggle demo Space, Comparison tab, academic infographic).
Three enlarged crops of a numbered Chinese label reading 第一庭院 next to a castle tower. Top and bottom, the four characters are clean black strokes. Middle, the last character is covered by a green blotch.
Small Chinese strokes. Top: base, 40 steps. Middle: 6 steps, a colour blotch on the last character. Bottom: 8 steps, clean. Crops enlarged 2x by me (Viggle demo Space, Comparison tab, architecture presentation board).

Both failures are the ones Viggle's own notes in the Space describe, to its credit: at 6 steps "small Latin text can print twice, like a double exposure (decided in the 0.75 → 0.5 step), and small Chinese strokes can get colour blotches (the last 0.25 → 0 step)". Its OCR check puts numbers on them. Word recall on the infographic's caption rises from 0.71 at 6 steps to 0.83 at 8 over 16 seeds, and on the board's Chinese labels from 0.76 to 0.90 over 4 seeds, against 0.95 for the base on both (reported).

The second kind is the metrics table: sample diversity, centroid drift and the share of prompts whose layout differs from the base's, over 96 held-out user requests and 32 prompts, plus Laplacian sharpness (0.0199 for v0.2.1, 0.0187 for v0.2). Every one measures distance from the teacher. None measures whether the picture does what the prompt asked. The card is plain about it: "no standard benchmark is claimed", and "2K output is compared with the base model only by eye". Its numbers were also taken with Qwen's prompt rewriters on, which the piece on the pocket rewriter found made rendered images less faithful to the request, not more.

Pruna publishes no quality number at all. Its card says v0.1 "does not yet match the visual quality of the base model", that the 5-step adapter's images are "visibly worse", and that short prompts do worse. The pictures it ships have no base-model counterpart and no stated settings.

A grid of image edits joined by purple arrows and plus signs: a knitted dragon turned to gold and to ice, a runner's silhouette combined with a traffic policeman, a figure moved into a night scene with a helicopter, a skier turned into a surfer, a flame-haired portrait turned to frost, a man in a suit made broader, a felt puppet given a smile, sunglasses and a hat, and a woman's helmet and suit restyled in knitted dragon wool.
Pruna's editing samples, including two-reference compositions. The card does not say which adapter, step count or resolution produced them, and shows no base-model output beside them (Pruna-Qwen-Image-2.1 repository, edit_grid.jpg).

The rest of the checklist. Both edit: Viggle trained with 1 to 3 references and says 5, 6 and 10 references, RGBA output and mask-guided edits "work … but are not measured"; Pruna trained single- and multi-image editing at 1K with up to three. Both run at true_cfg_scale=1.0 with no negative prompt, and Viggle's card adds that CFG does not help. Both LICENSE files are byte-identical to Qwen's (measured): the Qwen Research License, non-commercial, "for research or evaluation purposes only".

Pruna's adapter on four CPU cores

Every timing above is someone else's, on a B200 or an H100. The release piece ran the base model on the four-core Xeon that builds this site, with no GPU at all: a 512² image at the vendor's 40 steps took about an hour. A few-step adapter is the obvious thing to try there. I ran Pruna's, because its pair gives a 5-step and an 8-step point on one base; at 90 seconds a step, every image here is ten minutes or more of the box.

The setup is the release piece's: stable-diffusion.cpp at commit 2bb7294, leejet's Q4_K GGUF of the denoiser, Qwen's Q4_K_M encoder, 512 x 512, Euler, guidance off, seed 42, four threads. The sigmas are Pruna's own lists, passed verbatim with no shift on top: 1.0, 0.9333333, 0.8571429, 0.7692308, 0.6666667, 0.5454545, 0.4, 0.2222222, 0 for 8 steps and 1.0, 0.94, 0.8571429, 0.6666667, 0.4, 0 for 5. LoRA strength 1.0, as the card says.

The adapter does not load as shipped

Loaded as published, sd.cpp applied 320 of the file's 448 tensors, and logged as much. Two things do not line up.

The fix is a rewrite of the file. Multiply every B by 2, a power of two and so exact in bf16. For each block, stack the gate and up A matrices into one rank-128 A, and put the two Bs on the diagonal of one B, so the product is the gate update over the up update. The rewritten adapter has 384 tensors, sd.cpp applies all 384, and the largest difference between its updates and PEFT's, over all 32 blocks, is 0.0 (measured). It is 436 MB against 0.34 GB, because the block-diagonal B stores its zeros. Half of this is already fixed upstream: a stable-diffusion.cpp change merged on 25 September, #2057, maps the two MLP names onto the fused weight. The scale is still read only from an .alpha or .scale tensor, so a PEFT file still needs its B matrices doubled by hand (the stable-diffusion.cpp piece has the code path).

What it costs

RunBoxPer stepSamplingVAE decodeTotalPeak RSS
Base, 40 steps (release piece)shared87.09 s median46.06 s3,863 s
Pruna, 8 stepsquiet89.89–91.64 s725.60 s39.56 s771.68 s11.59 GiB
Pruna, 5 stepsshared98.42–124.30 s532.66 s76.11 s615.85 s10.23 GiB
Base, 8 steps on Pruna's sigmasshared90.39–127.90 s829.02 s66.00 s900.86 s10.36 GiB

"Quiet" means the load average stayed near the job's own four threads; "shared" means other work, mostly other agents building this site, was on the box. The quiet run is the one to read.

What it draws

A classroom chalkboard in a wooden frame, lit from a window on the left. Four lines of white chalk read '4 CPU', 'cores,', 'no', 'GPU', every word and the comma correct. A small potted plant sits on the ledge at the lower right.
Pruna's 8-step adapter, rewritten for sd.cpp: Q4_K, 512², guidance off, seed 42. Generated by me on four CPU cores, 771.68 s. Every word right.
The same chalkboard composition, slightly flatter and darker: four lines reading '4 CPU', 'cores,', 'no', 'GPU', a plant on the ledge at the lower left and a desk in the foreground.
The control: the base model with no adapter, the same 8 sigmas, seed and settings. Generated on CPU, 900.86 s on a shared box. Every word right too.
A chalkboard seen at an angle, with a window on the left, a plant on the sill and a wooden desk in the foreground. Four lines read '4 CPU', 'cores,', 'no', 'GPU', correct, with the lettering slightly tilted.
Pruna's 5-step adapter, the same settings on its 5 sigmas. Generated on CPU, 615.85 s on a shared box. Correct, in a different composition.
A framed chalkboard by a window, four lines reading '4 CPU', 'cores,', 'no', 'GPU', a plant on the sill and desks in the foreground.
The reference: the base at 40 steps on the vendor's schedule, from the release piece. Generated on CPU, 3,863 s.

All four get every word and the comma. The chalkboard is easy enough that the base at 8 steps on Pruna's schedule gets it too, so on this prompt the adapter does not buy legibility. The release piece's 8-step runs were unreadable, but those were at Q2_K on the vendor's schedule; two things changed at once, so the pair says nothing about which one mattered.

What the adapter buys is finish. On the grey-level images, its 8-step picture has the 40-step base's contrast, a standard deviation of 60.6 against 60.7, and nearly its sharpness, a Laplacian variance of 512 against 524. The base at 8 steps is flatter and softer, at 56.6 and 467 (measured). That is one prompt, one seed and two crude statistics: evidence that the distillation did its job here, and not a quality benchmark.

A minimalist cream poster. Large bold black capitals read 'EDGE AI SUMMIT', a line below reads 'Bengaluru, 14 November 2026', and three words along the bottom read 'Talks', 'Workshops', 'Demos'. All text is correct.
A layout prompt with the 8-step adapter: 512², guidance off, seed 42. Generated on CPU, 1,802.70 s on a busy box. Every word right.
The same poster with smaller, lighter type: 'EDGE AI SUMMIT', 'Bengaluru, 14 November 2026', and 'Talks', 'Workshops', 'Demos' along the bottom, all correct.
The base at the same 8 sigmas, no adapter. Generated on CPU, 1,738.58 s on a busier box. Every word right, in thinner type.

The poster repeats the pattern. Both spell everything, the date included. The adapter sets it larger and heavier, and its grey-level standard deviation is 40.8 against 28.6. Both leave the middle empty, which is what "minimalist" asked for. Their wall-clocks are twice the quiet run's because the box was running other jobs; they measure the box, not the model.

At the adapter's own resolution

512² is a quarter of the pixels Pruna trained at, so the last four images are at 1024 x 1024 with the same rewritten 8-step adapter, guidance off, seed 42, and --vae-tiling, which decodes the latent in tiles instead of all at once. The prompts are longer on purpose: a scene with signage, a dense illustration with a line of text, a labelled diagram, and a photograph with fine texture.

Only the hummingbird had the box nearly to itself, so it is the one to time. Its eight steps took 370.47 to 377.67 s, 4.1 times a 512² step for 4 times the pixels, and the whole image 3,303.93 s, 55 minutes, with a peak RSS of 11.88 GiB. The other three shared the box and took 4,551.41 to 8,581.52 s; those times measure the neighbours as much as the model. At the same step cost, 40 base steps at 1024² would take about 15,240 s, over four hours, so the adapter's 4.6x holds at this size too (reasoned: the base was not run at 1024² here).

A rain-wet alley at night. Above a small shop a neon sign reads 'CHAI & CODE' in orange tubes, with 'open till 2 am' in blue beneath it. Steam rises from a brass kettle on a stall, fairy lights zigzag overhead, pink and cyan light reflects off the paving, and a ginger cat sleeps on a stack of wooden crates.
'CHAI & CODE' and 'open till 2 am', both exactly as asked, with the kettle, the steam, the fairy lights and the cat on the crates. Pruna's 8-step adapter at 1024², generated on four CPU cores, 4,551.41 s on a shared box.
A cream poster titled 'HOW A DIFFUSION MODEL PAINTS'. Four tall panels in a row joined by arrows, labelled '1. Pure noise', '2. Rough shapes', '3. Colours settle' and '4. Fine detail', each showing a snowy mountain above a lake with pine trees. The first panel is grainy; the other three are finished paintings with different skies.
Every word right: the title and all four labels. The pictures do not show what the labels say. Panel 1 is the finished scene under grain, and panels 2 to 4 are the same finished painting with different skies. 1024², 8 steps, 4,671.67 s on a shared box.
An isometric cutaway of a small bookshop cafe. On the ground floor a barista stands behind a counter with glass pastry cases under a chalkboard reading 'TODAY: CARDAMON BUN'. Above, a balcony with bookshelves and ladders, and a rooftop with potted lemon trees, string lights and a green armchair where a dog sleeps.
One letter wrong: the prompt asked for 'TODAY: CARDAMOM BUN' and the board says CARDAMON. The small menu boards are not words, and the reading nook with the sleeping dog ended up on the roof beside the lemon trees. 1024², 8 steps, 8,581.52 s on a busy box.
A hummingbird hovering beside a single red hibiscus flower, wings spread, a green back and a ruby throat, with a small drop of nectar on the tip of its beak, against a plain olive-green background.
Sharp feathers, the ruby throat and the drop on the beak; one hibiscus rather than a cluster, and a flat olive ground rather than the creamy bokeh asked for. 1024², 8 steps, 3,303.93 s on a nearly quiet box.

Across the four, text holds up at 8 steps: the two strings on the sign, the poster's title and four labels, and all but one letter of the chalkboard. What slips is composition. A prompt with more parts than the picture makes room for loses some of them: the poster's four stages collapse into one finished painting, the café's reading nook moves to the roof, and the hummingbird gets one flower instead of a cluster. One seed each, so this is a look at the adapter, not a measurement of it.

The take

The step count is the whole story here. Qwen-Image-2.1 had already dropped classifier-free guidance by default, so there was no second factor for distillation to remove. A 6-step student can be at most 6.67x faster, and an unmerged adapter gives some of that back on every step. Viggle's 5.02x on a B200 is three quarters of that ceiling, measured on Qwen's own examples, and its card names the price: small text, complicated edits and seed diversity, the last of which DMD is known to spend.

Pruna's release is a v0.1 and says so; the headline does not. "Up to 6.3x" is the adapter the card calls visibly worse, at a resolution it did not train at, and the recommended configuration is 3.8x. The card is precise about its timing method, down to which arm had the KV cache on, and publishes nothing about image quality.

The finding I would carry away is the schedule. Two teams, distilling the same model separately, put their students on the same shift-2 grid and spent their extra steps at the high-noise end, where the composition gets decided. A few-step release for this model that lands its nodes elsewhere will be worth reading closely.

What would change my mind

4 claims above, and what would falsify each

  1. The 40-step Qwen-Image-2.1 baseline runs one forward pass per step, so a few-step student's speedup is bounded by 40 / N.

    Rests on the pipeline default true_cfg_scale=1.0, the card's examples and the tensor names. A Qwen-recommended setting with guidance on, or either baseline timed with CFG, would double the baseline and change every ratio here.

  2. Both releases are distribution-matching (DMD-family) LoRAs, not full checkpoints.

    Rests on both cards and every adapter header. A new recommended full-weight file, or training code whose main loss is a consistency or adversarial objective, would change it.

  3. Unmerged adapter steps cost 13-23% more than base steps in Pruna's runs, mostly not because of the adapter's FLOPs.

    A two-point fit on medians of three, assuming both arms share one fixed cost. A merged or cached 5-step timing at 1024² near 6.9x would confirm it; one still near 5.75x would put the overhead somewhere else.

  4. Viggle's quality metrics measure distance from the teacher, not prompt adherence.

    The card's own words and metric definitions. A GenEval, DPG or Qwen-Image-Bench run on either student against the 40-step base would replace this with an actual number.


Sources, read directly: Viggle/Qwen-Image-2.1-viggle-turbo (card, NOTICE, LICENSE, scheduler and adapter configs, comfyui/viggle_turbo.py, and all nine safetensors headers by range request) and its demo Space (compare.py, compare/cases.json, the renders); PrunaAI/Pruna-Qwen-Image-2.1 (card, LICENSE, both adapter headers, the latency charts); Qwen/Qwen-Image-2.1 (card, LICENSE, configs, shard headers); the diffusers QwenImage21Pipeline at the commit Viggle pins; and the Qwen-Image and Qwen-Image-2512 cards. Figures 3 to 5 are Pruna's and 1, 2 and 6 Viggle's, served locally; the crops and layouts of 1, 2 and 6 are mine.

Cite this article

For attribution, please use the following reference or BibTeX:

Satyajit Ghana, "Few-step Qwen-Image-2.1: two DMD LoRAs, and a speedup that is steps alone", ai.thesatyajit.com, September 2026.

bibtex
@misc{ghana2026qwenimage21fewstep,
  author = {Satyajit Ghana},
  title  = {Few-step Qwen-Image-2.1: two DMD LoRAs, and a speedup that is steps alone},
  url    = {https://ai.thesatyajit.com/articles/qwen-image-2-1-few-step},
  year   = {2026}
}
share