Skip to content

Wan Video

Text-to-video generation from Alibaba's Wan team, based on a flow-matching DiT with a UMT5-XXL text encoder and a causal 3D video VAE that streams decoding one latent frame at a time. mold implements the family natively in Rust.

Note: Video output defaults to MP4. Also supports GIF, WebP, and APNG via --format. Frame count must be 4n+1 (77, 81, 121, ...) due to the VAE's 4x temporal compression. Width and height must be multiples of 16 — except wan22-ti2v-5b, whose 2.2 VAE requires multiples of 32. Wan is performance-qualified on CUDA and Apple Metal; CPU runs are correctness-only. fp8-scaled checkpoints stay CUDA-only — Metal has no fp8 widening kernel; use the bf16 or GGUF tier there.

Example Clip

wan22-t2v-a14b:q4: 832x480, 49 frames, 16 fps, 4 steps, seed 83005

"A red fox walks steadily through fresh powder beneath frost-covered pines. The camera tracks alongside at eye level while loose snow falls from branches and the fox's paws leave a clear trail, cinematic winter light."

Wan's T2V and TI2V models generate silent video. Native synchronized speech is a separate Wan S2V pipeline and is not implied by these model rows.

Variants

ModelStepsApprox total pullNotes
wan21-t2v-1.3b:bf1630~14.5 GB480p text-to-video; smallest, fastest pull
wan21-t2v-1.3b:turbo3~17.6 GBFastVideo DMD 3-step distill of the same 1.3B, no CFG; steps, solver and shift pinned
wan21-t2v-14b:q530~23 GBQ5_K_M 2.1 14B; 480p text-to-video
wan21-t2v-14b:q830~27.5 GBQ8_0 2.1 14B; the 2.1 quality tier
wan22-ti2v-5b:fp1620~22.8 GB720p24 text- and image-to-video
wan22-ti2v-5b:q820~18 GBQ8_0 5B; 8-12 GB cards at reduced settings
wan22-ti2v-5b:turbo4~22.8 GBSelf-Forcing 4-step distill, no CFG
wan22-ti2v-5b:dmd3~22.8 GBFastVideo DMD 3-step distill, text-to-video only, no CFG; steps/solver/shift pinned
wan22-t2v-a14b:q54~36 GB480p16 text-to-video, 4-step Lightning tier
wan22-t2v-a14b:q820~42 GBSame weights at Q8_0, no distill
wan22-t2v-a14b:q44~33 GBQ4_K_M Lightning; 12-16 GB needs reduced use
wan22-i2v-a14b:q54~36 GB480p16 image-to-video, 4-step Lightning tier
wan22-i2v-a14b:q820~42 GBSame weights at Q8_0, no distill
wan22-i2v-a14b:q44~33 GBQ4_K_M Lightning; 12-16 GB needs reduced use
wan22-t2v-a14b:fp820~40.5 GBComfy-Org fp8-scaled experts; ~2.6 GB more VRAM headroom than :q8, refuses LoRAs, CUDA-only
wan22-i2v-a14b:fp820~40.5 GBfp8-scaled image-to-video pair; same recipe and constraints

Totals include the shared UMT5-XXL encoder (~11.4 GB), tokenizer, and the variant's VAE. The encoder is shared across every Wan model under shared/wan/, so a second Wan pull only fetches the checkpoint and VAE.

The encoder is also selectable: set the umt5_variant config key or the MOLD_UMT5_VARIANT env var to q8, q6, or q5 (there is no dedicated CLI flag) to swap the 11.4 GB FP16 encoder for city96's GGUF export at 6.0, 4.7, or 4.1 GB. auto (the default) prefers FP16 on GPU when it fits and otherwise the largest GGUF that does — the encoder is the floor of a wan render's memory estimate, so this is the lever that moves every tier at once. Nothing below the publisher's Q5_K_M floor ships. MOLD_KEEP_TE_RAM=1 additionally parks the FP16 encoder in host RAM between renders instead of re-reading it from disk.

A14B is two models

Wan 2.2 A14B is a mixture of experts along the noise axis: two complete 14B transformers, one trained for the early, structural part of the schedule and one for the late, detail part. mold loads the high-noise expert first, switches once when the schedule crosses the boundary (timestep 875 for T2V, 900 for I2V), and drops each expert before loading its partner — so VRAM is the larger of the two experts, not their sum (~10.8 GB at :q5, ~15.4 GB at :q8). Disk is the sum, which is why the pull totals are large.

Admission prices a wan render from its frame count, not just its resolution. Video memory is dominated by the token count — ((frames − 1) / 4 + 1) latent frames times the patch grid — so the same 832×480 shape costs several times more at 81 frames than at 17. The server predicts that peak from the checkpoint's own header before the UMT5 encode and the expert load, and refuses a shape that cannot fit rather than failing part-way through the denoise. The model is calibrated against measured peaks on an RTX 4090 and validated on a second checkpoint it was not fitted to. A rejection names frames first, because that is the most effective lever, and suggests the next quantized tier down rather than the one that just failed.

The swap itself is a cold read: the outgoing expert is still in the page cache, but the incoming 10.8-15.4 GB file has not been read this run and on a 32-64 GB host has been evicted behind UMT5 and the resident expert. mold warms it in the background while the first expert denoises — host I/O only, so no VRAM is touched and the max-of-pair invariant holds. Measured on an RTX 4090 with a cold cache (wan22-t2v-a14b:q5, 33f): the swap load falls from 8.7-22.2 s, varying with what survived in cache, to a consistent 5.6 s. MOLD_WAN_PREFETCH=0 turns it off.

Community A14B adapters are published the same way: a high-noise file and a low-noise file, distilled together and explicitly not interchangeable. Bind one to its expert with --lora file.safetensors@high (or @low), or the additive per-entry expert field on the API. mold infers the binding from the dominant filename conventions (high_noise, HighNoise, HIGH) when the field is absent and says so in the progress output — an adapter with no expert marker still applies to both experts, which is right for a genuinely unpaired one. A single-expert checkpoint refuses an explicit expert rather than ignoring it.

The :q5 tier additionally pulls lightx2v's 4-step distill — a separate adapter for each expert — and defaults to guidance 1.0. That is not a weak setting: at guidance ≤ 1 mold skips the unconditional pass entirely, so each step is one forward instead of two. Four steps at one forward each is where the tier's speed comes from.

Usage

bash
# 480p, 81 frames @ 16 fps (defaults)
mold run wan21-t2v-1.3b "a red fox trotting through fresh snow, golden hour"

# The same clip on the 3-step DMD distill: 3 forwards instead of 30 x 2.
# Steps, guidance, solver and flow shift are the published schedule's.
mold run wan21-t2v-1.3b:turbo "a red fox trotting through fresh snow, golden hour"

# 720p24, 121 frames — Wan 2.2 5B
mold run wan22-ti2v-5b "aerial view of waves breaking on a black sand beach" \
  --width 1280 --height 704 --frames 121 --fps 24

# The same clip on the 4-step distill: 4 steps at guidance 1.0, so 4 forwards
# instead of 20 x 2. Image-to-video works the same way.
mold run wan22-ti2v-5b:turbo "aerial view of waves breaking on a black sand beach"

# The same clip on the 3-step DMD distill: 3 forwards instead of 20 x 2.
# Steps, solver, and flow shift are pinned to FastVideo's published schedule.
mold run wan22-ti2v-5b:dmd "aerial view of waves breaking on a black sand beach"

# Wan 2.2 A14B, 4-step Lightning tier
mold run wan22-t2v-a14b:q5 "a paper boat drifting down a rain gutter"

# A14B image-to-video from a still
mold run wan22-i2v-a14b:q5 "the balloon lifts off" --image balloon.png

# Single-frame text-to-image — Wan 2.2 as a still-image model
mold run wan22-t2v-a14b:q5 "a lighthouse at dusk, volumetric fog" \
  --frames 1 --output still.png

# First/last-frame interpolation: anchor both endpoints (A14B I2V or TI2V-5B)
mold run wan22-i2v-a14b:q5 "the sapling grows into an oak" \
  --image sapling.png --last-image oak.png

# The fp8-scaled quality tier — the 20-step recipe with ~2.6 GB more VRAM headroom
mold run wan22-t2v-a14b:fp8 "storm waves crash over the lighthouse"

The :fp8 tier is not faster than :q8 — measured on an RTX 4090 at 33f/832x480 under identical settings, both run ~28 s/step (the denoise is compute-bound, not weight-decode-bound) — but its peak VRAM is 17,646 MiB against :q8's 20,278 MiB. The trade-off: fp8-scaled weights refuse LoRA stacks (merging would re-round every targeted weight to three mantissa bits), so adapters — the Lightning distills included — need the GGUF or bf16 tiers.

Where an A14B step actually goes

A kernel-level audit (RTX 4090, 33f/832x480 — 14,040 video tokens) attributes the quality-tier step. Under the device-synced profiler the step measures ~42.5 s, of which dense self-attention SDPA is ~21 s, the quantized matmuls ~7 s — the GGUF fast path (MMQ) engages for every shipped quant mix, and forcing the dequantize-per-forward fallback triples that bucket to ~21 s while leaving attention untouched — and the BF16↔F32 boundary casts are ~1.5 s. Per-phase syncs inflate the many small ops far more than the 80 large attention kernels, so against the real ~28 s step SDPA's share sits between half and roughly three quarters: attention dominates. Weight size barely matters: measured denoise time is 28.2 s/step at :q8 quality, 30.2 at :q5 quality (its Lightning adapter runs as a per-step parallel branch on GGUF), and 15.9 at :q5 fast (guidance 1 skips the uncond forward — exactly half a CFG step). Two diagnostic env knobs ship for re-running the audit: MOLD_WAN_STEP_PROFILE=1 prints a per-phase, device-synced timing line per denoise step, and MOLD_WAN_FORCE_DMMV=1 forces the quantized-matmul fallback for A/B comparison — neither belongs in production use.

81 frames on a 24 GB card

The A14B pair's trained clip length is 81 frames, and the Q5-backed tiers now default to it. What makes it fit a 24 GB card is partial block offload: when a render's activation budget will not fit what is free once the weights land, trailing transformer blocks park in host RAM and each returns for the duration of its own forward, so at most one parked block is resident at a time.

Measured on an RTX 4090, wan22-t2v-a14b:q5 at 832x480:

FramesOffloadWall clockPeak
53none163 s21,354 MiB
81noneOOM
81automatic316.3 s17,322 MiB

The Q4 tier is measured at the same shape rather than inferred from Q5's smaller resident: 317.5 s at a 15,722 MiB peak.

It engages by itself and a render that already fits parks nothing, so shorter clips are unchanged. MOLD_WAN_OFFLOAD_BLOCKS=N pins the block count and 0 disables it; --offload (MOLD_OFFLOAD=1) parks every block, which is the lowest-VRAM execution the family has and the slowest.

Two caveats worth knowing. Parking is only available for GGUF checkpoints — the move is a raw-byte round trip that the plain and fp8 weight sources have no equivalent of — and parked blocks cost wall clock, roughly doubling the render above, because each is rebuilt on the device once per step.

The Q8 and fp8 tiers carry a larger resident expert, so parking does not reach 81 for them. Their envelopes are measured now, on the same card and by the same protocol — the largest 4n+1 count admission accepts on an idle 4090, then a real render:

TierExpertParksDefault framesWall clockPeakFirst refused
:q5~10.8 GByes81316.3 s17,322 MiB
:q4~9.7 GByes81317.5 s15,722 MiB
:q8~15.3 GByes732,235.0 s16,650 MiB77 frames
:fp8~14.3 GBno45996.4 s19,082 MiB49 frames

Both raised defaults sit at the edge of what admission accepts with the card otherwise idle — 77 frames on :q8 is refused at ~25.3 GB against ~24.8 GB usable, and 49 on :fp8 at ~25.2 GB. That is the same posture the pre-offload 53-frame default held (23,975 MiB of 24,564). On a card sharing VRAM with a desktop session, pass a smaller --frames rather than meet a refusal at the tier's own default.

Note the shape of the :q8 number: parking buys the frames and charges wall clock for them, so its 73-frame peak is lower than the 45-frame fp8 tier's while taking more than twice as long. fp8 cannot park at all, so its envelope is simply what fits resident.

The auto-chain routing default follows the tier's own default, so running one of these models with no --frames renders a single clip rather than a stitched sequence. Ask for more than the tier's default and the CLI still chains, as it always has.

720p on a 24 GB card is the 5B tier, not A14B

Block offload returns weight bytes, so it moves an envelope that weights bind. At 720p the binding term is activations, and A14B's demand runs away from what parking can return: wan22-t2v-a14b:q5 at 1280x720 estimates ~28.1 GB at 49 frames and ~39.5 GB at 81, against ~24.8 GB usable, while parking every one of the 40 blocks is worth at most ~3.8 GB. Measured ceiling on an RTX 4090: 33 frames at 1280x720, 311.0 s at an 18,346 MiB peak.

That is not a mold limit. Upstream puts A14B at 1280x720 on "a GPU with at least 80GB VRAM" even with --offload_model True --convert_model_dtype (Wan2.2/README.md), and names the 5B TI2V as the 24 GB path. mold agrees: wan22-ti2v-5b:fp16 renders 81 frames at 1280x704 in 470.7 s at a 19,402 MiB peak, and its own 121-frame default at 160.7 s / 18,986 MiB. The 5B's single expert and 2.2 VAE (a 16x spatial stride against the 2.1 VAE's 8x) make its 720p token grid a fraction of A14B's — that, not the quantization, is why it reaches the resolution.

The LoRA branch runs at the activation dtype

The Q5/Q4 tiers are Lightning distills, and a quantized checkpoint cannot take a LoRA merge — that means dequantize, add, requantize, which is minutes per expert with most of the delta rounded away. Mold hangs the adapter off each projection as a parallel branch instead, ~400 of them per expert.

Where that branch sits decides what it costs. A quantized projection computes in F32 while the model carries BF16 between ops, so each one casts on both sides. Running the branch on the activation the model already holds — rather than on the projection's F32 interior — lets the adapter be staged at BF16 for no extra casts at all.

Measured on an RTX 4090, wan22-t2v-a14b:q5 at 832x480 x 53 frames:

Adapter stagingWall clockPeak
F32, inside the cast163.0 s20,778 MiB
BF16, outside it160.4 s19,050 MiB

The saving is larger than the adapter's own ~0.6 GB because the branch's intermediates are BF16 now too. Renders are deterministic per build but not identical across this change — the same seed returns the same shot, not the same bytes. BF16 keeps 8 mantissa bits against Q5_K's ~5 per 32-value block, so the adapter stays well inside the accuracy of the checkpoint it patches.

The VAE decode is 40-50% of a fast-tier render

On the 4-step Turbo tier the denoise is short enough that the causal 3-D VAE decode, not the transformer, is the largest single phase. Measured on an RTX 4090 with wan22-ti2v-5b:turbo, reading the Decoding video frames [Xs] line:

ShapeFramesDecodeTotalDecode share
480x288816.8 s13.4 s51%
832x4808119.8 s38.0 s52%
1280x7048144.9 s101.8 s44%
1280x70412166.9 s170.0 s39%

That cost is compute, not overhead: decode time tracks pixels x frames almost exactly (2.9x the pixels costs 2.9x the time, 6.5x costs 6.6x), while the number of kernel launches is identical across the first three rows. The decoder's convolutions are doing real work at full output resolution. Batching the per-frame 2-D stages into one launch and widening the decode chunk were both built and measured; neither moved the number, and the wider chunk only added an OOM at 8 latent frames, so neither shipped.

The knob turned out to be one layer down, in the convolution itself — see cuDNN convolutions below.

The decode is also not where this family runs out of memory. Its transient is bounded to one latent frame at a time by construction, so 1280x704 x 121 decodes on a 24 GB card; the denoise is the memory wall.

cuDNN convolutions

candle has two CUDA convolution implementations. The historical one builds an im2col column buffer and runs a single GEMM; a --features cuda,cudnn build adds NVIDIA's own kernels, and that is the default for video families, on the same clips-versus-stills argument as MOLD_ATTN. Image families stay on im2col in every build so an archived still renders the same bytes. MOLD_CONV={cudnn,im2col} overrides either direction.

Measured on an RTX 4090, one steady-state latent frame of the Wan 2.1 VAE decoder at 832x480 in bf16, summing every convolution it issues:

Convolution backendPer latent framevs im2col
im2col845 ms-
cuDNN, as candle shipped it518 ms1.63x
cuDNN, after the fork's fixes192 ms4.38x

The gap between the second and third rows is the interesting part, and it was not cuDNN's doing. candle created every convolution descriptor at CUDNN_DEFAULT_MATH, which declines tensor-core engines for f16/bf16 — a bf16 convolution ran slower than the same convolution in f32 — and silently permits TF32 for f32. Selecting TENSOR_OP for half precision and FMA for the wide dtypes is 2.4x on bf16 at bit-identical output, and on f32 it is both 1.4x faster and 100x more accurate. candle also zeroed the cuDNN workspace on every call, a memset of up to hundreds of MB that cuDNN never reads; dropping it is worth another ~10%.

cuDNN is not always the right answer, so the fork chooses per convolution. A 1x1 kernel is a matmul whose im2col buffer is a free reshape, and a small convolution finishes in less time than cuDNN spends creating descriptors and running its algorithm heuristic — neither of which candle caches. The gate is the size of the column buffer im2col would materialise, which is exactly the work cuDNN avoids; output elements are the wrong measure, as this decoder's own head convolution shows. 96->3 at 480x832 has barely a million output elements, a 690 MB column buffer, and runs 6.9x faster on cuDNN.

Convolutions are ~76% of the decode, and the end-to-end effect was measured on the same card with a full render rather than projected from the table above -- wan22-t2v-a14b:q5, 832x480, 81 frames, 4 steps, seed 1483, both arms warm and parking the same blocks:

MOLD_CONVDecodeRender
im2col23.3 s105.9 s
cudnn11.2 s94.9 s
2.08x1.12x

Decode saves 12.1 s and the render saves 11.0 s, so the whole gain lands in the phase it should. Note the render figure is the honest one to quote: a 4.38x on the convolutions is a 2.08x on the decode, because a quarter of the decode is not convolution and cuDNN pays a per-call setup candle does not cache. Frames match the im2col arm at ~40 dB PSNR -- the same shot, not the same bytes, which is the trade this family opts into.

A cuDNN failure falls back to im2col rather than failing the render. Every Linux CUDA artifact compiles the feature -- the Nix packages, the four release.yml binaries, the Docker image, both AUR packages and the desktop AppImage. macOS and Windows do not: cuDNN is CUDA-only.

When the VAE decode does not fit

The full decode is attempted first. Only an out-of-memory failure falls back to a spatially tiled decode — 256x256 pixel tiles with a quarter-tile overlap and a linearly ramped blend, the same geometry ComfyUI's own decode_tiled_3d uses. Wan's decoder already streams the temporal axis one latent frame at a time, so tiling bounds the one thing that was left unbounded: the spatial working set at full output resolution. A render that fits keeps the untiled path, which is faster and free of any seam approximation.

Apple Metal qualification

Wan is performance-qualified on a 48 GB M4 Max with full denoising and the default automatic Metal memory limit. Each row used 17 frames and includes one cold plus three consecutive warm renders; every output decoded completely and passed frame-by-frame visual inspection.

WorkloadStepsColdWarm rangeMedian denoise stepVAE decode range
1.3B BF16 T2V, 832x48030437.7 s444.5–454.9 s9.8–10.7 s64.5–65.8 s
5B Q8 T2V, 1280x70420549.3 s667.3–762.5 s12.9–21.1 s161.8–182.1 s
5B FP16 I2V, 1280x70420839.7 s920.1–1190.3 s21.8–29.7 s193.8–357.3 s

The FP16 row reached macOS warning pressure during the sustained campaign and used about 709 MiB of additional swap, while retaining at least 3.26 GiB of sampled available memory. The completed, visually stable runs qualify the path, but the warm slowdown is real: Q8 is the practical 5B Metal tier when dense FP16 is not required. Wan's sequential encoder/transformer placement already addresses the relevant unified-memory phase split; the host-side FP8 widening optimization used by LTX-2 does not apply to Wan's BF16 and Q8 compute paths.

FlashAttention on Wan

A --features cuda,flash-attn build routes the Wan DiT's self- and cross-attention through candle-flash-attn v2, and that is now the default for video families — an unset MOLD_ATTN reaches the Wan DiT as flash wherever the kernel is compiled in. MOLD_ATTN=math restores the previous arithmetic. Image families keep math as their default, so the bytes an archived still renders are unchanged (see #736 for why that split exists). Measured on an RTX 4090 with the same binary, only the backend varying (wan22-t2v-a14b:q5, 53 frames at 832x480):

BackendPeak VRAMWall clock
flash21,354 MiB75.3 s
math22,250 MiB158.4 s

So flash is worth 2.1x on speed and only ~900 MiB on peak. The speed is why it is the default; the small memory delta is why it is not, on its own, what unlocks longer clips.

Where it does change memory is the estimate. device::wan_activation_budget_bytes charges a per-token score matrix of 2 x heads x 512 x 2 bytes — 81,920 B/token at A14B, as much as the whole rest of the block — and FlashAttention materializes none of it. That estimate drives both admission and the block-offload policy, so an estimate blind to the backend parks blocks against a shortfall the running render does not have. The two calibrations are therefore fitted separately, and the flash pair must never inherit math's slope: dropping the score term shrinks the derived sum 44% while the residual the slope stands in for does not, so a shared slope under-estimates by gigabytes and OOMs.

Measured on an RTX 4090 with wan22-t2v-a14b:q5 at 832x480, before and after the backend-aware estimate:

Framesneed_mib beforeafterblocks parked beforeafterWall clock
5310,5168,1765 / 400 / 40193.5 s -> 79.5 s
8114,62411,29040 / 4015 / 40-> 111.5 s

Every parked block is a host round-trip on the critical path of every step, so the 81-frame render was paying full weight streaming for a shortfall that was largely an artifact of pricing the wrong backend. That is the opposite of the usual expectation, and it is why longer clips are not unlocked by switching backends: at 81 frames the estimate is ~27.7 GB against ~24.8 GB usable, and flash's measured per-token saving extrapolates to about 1.3 GB — not the ~3 GB that would be needed. Reaching 81 frames on a 24 GB card needs partial block offload, which is now wired for this family (see above). Note also that among release artifacts only the sm89 (h3-cuda) binary compiles flash-attnMOLD_ATTN=flash opts in there; on every other artifact this remains a source-build configuration.

At --frames 1 Wan renders a still: png/jpeg output is admitted (and png is the default there), the image embeds the same mold:parameters provenance as every image family, and the gallery treats it as an upscale-eligible still. Upstream defines its t2i-14B task as the same weights at frame_num=1; any frame count above 1 keeps the video-only output contract.

Wan checkpoints were tuned against a specific long Chinese negative prompt; mold applies it automatically whenever a request carries no negative at all. Every surface now shows it: /api/models advertises the tuned default per model (default_negative_prompt), the web/desktop/iPhone Negative field and the TUI's Advanced → Negative editor prefill it, and editing the text replaces it. Clearing the field (or passing --no-negative on the CLI, or negative_prompt: none on Discord) sends an explicit empty negative, which the engine honors as a real empty uncond instead of re-applying the default. An untouched field stays absent on the wire, so older servers behave exactly as before. Saved gallery metadata records the negative that actually conditioned the render, so Library details and "Reuse settings" are truthful.

Source-image contracts

Wan checkpoints split three ways, and /api/models advertises which through the additive per-model source_image field so every surface offers exactly what the checkpoint accepts:

  • unsupportedwan21-t2v-1.3b:*, wan21-t2v-14b:*, wan22-t2v-a14b:*, and wan22-ti2v-5b:dmd: a supplied image is rejected at admission. The DMD tier is the one that refuses by policy rather than by architecture — its weights would take the frame, but the distilled student does not follow it.
  • optionalwan22-ti2v-5b:fp16, :q8, :turbo: text-to-video, or the source pinned as frame 0 through latent inpainting.
  • requiredwan22-i2v-a14b:*: the image is half the model input; admission rejects a request without one.

Installed cv:/hf: wan checkpoints classify from their own tensor shapes — the same read the engine performs — never from their names.

First/last-frame interpolation

--image + --last-image (wire: a two-entry keyframes list anchoring pixel frames 0 and F-1) renders motion between two stills — upstream's FLF2V task, ComfyUI's WanFirstLastFrameToVideo. A14B I2V drives the 36-channel mask contract with the endpoint flag in mask channel 3; TI2V-5B pins both endpoint latent frames through the same inpaint path diffusers' last_image uses. Any other keyframe layout is refused at admission — the family has no mid-clip keyframe path.

Sequences

Wan renders multi-clip sequences, and mold run --frames N past the per-clip envelope auto-chains rather than failing. What crosses a clip boundary depends on the checkpoint, never the family, because wan has no latent motion tail: its smooth handoff is last-frame image conditioning, which only an image-conditioned checkpoint accepts.

CheckpointSeamWhy
wan22-ti2v-5b:fp16/q8/turboContinuesThe latent inpaint pins the seeded frame
wan22-i2v-a14b:*ContinuesThe 36-channel mask+latent concat takes the frame
wan21-t2v-*, wan22-t2v-a14b:*Join / Cut / FadeNo conditioning channel at all
wan22-ti2v-5b:dmdJoin / Cut / FadeRefuses the source frame, so nothing to continue

That classification is exactly the source_image contract /api/models already advertises, so a script can never ask for a seam the checkpoint would reject. A text-to-video checkpoint is still sequence-capable — it simply concatenates independent clips, the same honest behaviour LTX-Video has.

The continuation is seeded with the previous clip's final frame, so it re-renders exactly that one frame and the stitch trims exactly one. This is deliberately not LTX-2's 17-frame motion tail, which is the pixel window its VAE turns into three latent slots of carryover — wan has no equivalent, and reusing the number would discard sixteen good frames at every seam.

Clip lengths sit on wan's 4k+1 grid. The auto-chaining default is a VRAM envelope rather than a ceiling, and it is the selected tier's own default frame count raised to a family floor — 81 on the Q5/Q4 A14B tiers, 73 on :q8, 121 on the single-expert 5B — where the floor is 53 for A14B (which is what :fp8, whose 45-frame manifest default sits below it, actually routes at) and 121 for everything else, including an opaque cv: / hf: ID with no manifest. --clip-frames overrides it, clamped to the real 257-frame request cap.

bash
# Auto-chains into three 49-frame clips and stitches one MP4
mold run wan22-ti2v-5b:q8 "a paper boat drifting down a rain gutter" \
  --frames 100 --clip-frames 49

A Wan engine caches the prompt encoding it produced — the ~4 MB [1, 512, 4096] tensor, not the 11.37 GB encoder — so a stage repeating a prompt skips the encoder load and the forward entirely. That covers an auto-chained long video, a re-roll, and a batch child; a scripted sequence gives every stage its own prompt and still pays one encoder load per stage.

Retaining the encoder itself across a sequence was built and measured, and then removed: the load is 15.3 s cold but only ~5 s once the file is in the page cache, against a ~90 s stage, and holding it costs 11.37 GB of host RAM for the length of the sequence. Five percent of a stage is not worth that much headroom on the axis where video renders are already tight. MOLD_KEEP_TE_RAM=1 remains for anyone who wants the trade, and installing a quantized encoder is the better lever — the Q8 GGUF loads in 3.2 s on the GPU.

Measured: a 3-stage 159-frame wan22-t2v-a14b:q5 sequence at 832x480 renders in 257 s on an RTX 4090.

Scripted sequences work through the same mold.chain.v1 script the LTX families use — per-stage prompts, frames, and transitions — with mold chain validate shot.toml reporting the normalized stage list and stitched length before anything is submitted:

toml
schema = "mold.chain.v1"

[chain]
model = "wan22-ti2v-5b:q8"
width = 704
height = 384
fps = 24
steps = 20
guidance = 5.0
strength = 1.0
motion_tail_frames = 1
output_format = "mp4"

[[stage]]
prompt = "a paper boat drifting down a rain gutter"
frames = 49

[[stage]]
prompt = "the boat passes a storm drain"
frames = 49
transition = "smooth"

motion_tail_frames is normalized to what the checkpoint can carry, so a value carried over from an LTX script does not silently trim frames.

Measured on an RTX 4090: 145 frames at 704x384, three stages, 141 s. The boat, gutter, and railing persist across both seams.

Extending a clip

--extend continues an existing video in one request. It is the same seam as a sequence boundary with the carryover coming from a file: the source clip's final frame becomes the continuation's conditioning, so --extend-overlap is always 1 on wan — the multi-frame overlap LTX-2 accepts is a latent motion tail wan does not have, and a larger value is refused rather than silently trimming good frames. That is also the default: /api/models advertises extend_default_overlap_frames: 1 for wan checkpoints, so the flag can be omitted.

Resolution and fps are locked to the source clip; a mismatch is refused rather than rescaled, because the stitched result is one video. /api/models advertises supports_extend per checkpoint, from the same source_image contract the seam reads — a text-to-video checkpoint cannot extend, and its continuation is refused at admission rather than after the model load. The resolved overlap is written into the request before the job is queued, so the saved metadata records the 1 that rendered even for an installed cv: / hf: checkpoint the manifest cannot classify.

The web, desktop, and iPhone Continue a video control reads that same advertised field, so it appears for any wan checkpoint the host says can continue and offers the single 1-frame overlap wan accepts. A continuation on an I2V checkpoint needs no attached source image: the clip you are continuing supplies the first frame, and both the server and every Studio surface count it as satisfying the checkpoint's image requirement. Attaching an image alongside the clip is still refused — the first frames are pinned by the source tail.

bash
mold run wan22-ti2v-5b:q8 "the paper boat drifts on past a storm drain" \
  --extend clip.mp4 --frames 49 \
  --width 704 --height 384 --fps 24

Measured on an RTX 4090: a 48-frame source plus a 49-frame continuation minus the 1-frame overlap = 96 frames, in 53 s, with the boat and gutter continuous across the join.

Defaults and limits

Propertywan21-t2v-1.3bwan21-t2v-14b:*wan22-ti2v-5bwan22-*-a14b:q5wan22-*-a14b:q8
Resolution832x480 / 480x832832x480 / 480x8321280x704 / 704x1280832x480832x480
Frames81 @ 16 fps81 @ 16 fps121 @ 24 fps81 @ 16 fps73 @ 16 fps²
Steps303020420
Guidance6.06.05.01.0 (no CFG pass)per-expert¹
Flow shift8.08.08.05.05.0
SamplerFlowUniPC (bh2)FlowUniPC (bh2)FlowUniPC (bh2)FlowUniPC (bh2)FlowUniPC (bh2)

¹ The :q8 quality tier advertises guidance 3.5, but by default mold applies upstream's per-expert scales, switching at the same boundary as the expert swap: T2V runs 4.0 while the high-noise expert is resident and 3.0 after the boundary; I2V runs 3.5 throughout (wan_{t2v,i2v}_A14B.pysample_guide_scale). Passing an explicit --guidance pins that one scale for the whole schedule — except an explicit 3.5 on the quality tier, which is indistinguishable from the default on the wire and selects the per-expert pair. The Lightning tiers (default 1.0) treat every value, 3.5 included, as an explicit uniform choice.

² Each A14B frame default is that tier's measured 24 GB envelope: Q5/Q4 reach the checkpoint's trained 81 through block offload, :q8 reaches 73, and :fp8 — which cannot park — reaches 45. See 81 frames on a 24 GB card.

The Turbo tier on 24 GB

Measured on an RTX 4090 at the tier's own defaults (1280x704, 24 fps, 4 steps, guidance 1.0):

RunTimePeak VRAM
Text-to-video, 121f160.7 s18,986 MiB
Image-to-video, 81f92.0 sfits
Image-to-video, 121frefused~24.9 GB estimated against ~24.8 GB usable

Image-to-video at the full 121-frame default does not fit a 24 GB card at this weight class — wan22-ti2v-5b:fp16 is refused at the identical estimate, so this is the fp16-weight envelope rather than anything the distill changes. Use --frames 81, or the :q8 tier, which carries ~4.5 GB less of transformer.

The A14B frame defaults are each a measured 24 GB envelope: 81 frames on the Q5/Q4 tiers, which partial block offload reaches, 73 on :q8, and 45 on :fp8, which cannot park. See 81 frames on a 24 GB card for the measurements and the refusal points either side of them; larger cards simply pass a bigger --frames.

The sampler schedule matches the one lightx2v's Lightning distills were trained against (diffusers' flow-UniPC grid), so the 4-step tier reproduces its published timesteps exactly.

Turbo 1.3B

wan21-t2v-1.3b:turbo is FastVideo's DMD distill of the same 2.1 1.3B transformer (FastVideo/FastWan2.1-T2V-1.3B-Diffusers, Apache-2.0). It walks exactly three rungs on the family's shift-8 flow-match table:

RungTimestep
11000
2757
3522

Steps, guidance, sample solver and flow shift are all fixed, and mold refuses a request that sets any of them. That is not conservatism: a DMD student predicts the clean latent x0 at each rung and is re-noised to the next, so the rungs are the schedule the network was trained to answer. Asking for four steps leaves a rung with nothing to walk, guidance above 1.0 asks for an unconditional branch the distill removed, and running UniPC or Euler over it produces a different — and worse — render, not a slower one. Everything else is the base checkpoint's: 832x480, 81 frames at 16 fps, the same tuned negative prompt (unused at guidance 1.0), and text-to-video only.

Against the base tier's 30 steps with CFG, that is (30 x 2) / 3 = 20x fewer transformer forwards per clip.

Measured on an NVIDIA L40S (CUDA, math attention, no flash-attn), 832x480 x 81 frames at 16 fps, one prompt and seed, the base tier run with MOLD_WAN_STEP_CACHE=off (see #1559 for why):

TierForwardsPipeline totalOf which denoise
wan21-t2v-1.3b:bf1660196.0 s~170 s
wan21-t2v-1.3b:turbo340.8 s~14 s

That is 4.8x on the whole run and about 12x on the denoise loop; the rest is UMT5 encoding, VAE decode and MP4 encoding, which the distill cannot shrink. The first run in a fresh process pays kernel warm-up (56.8 s), and a server with the text encoder already resident answers the same clip in 33.2 s. 121 frames rendered in 54.4 s and stayed coherent end to end, past the checkpoint's 61-frame training length; treat longer clips as an experiment. Per-rung noise is drawn from the request seed, so a seed reproduces its clip byte-for-byte on the same hardware, but not FastVideo's own generator stream.

The checkpoint ships F32 — a 5.96 GB transformer file against the base bf16's 2.84 GB — and mold casts to bf16 lazily at load, so it costs about 3 GB in VRAM. VRAM admission prices a transformer by its file length, which over-reserves here; at 1.3B scale that is harmless.

FastVideo trained at 61 frames and 448x832, and its model card notes the checkpoint "supports any resolution, quality may degrade" away from that. The 81-frame 832x480 default is the card's own inference shape rather than its training shape, so it is well-travelled; longer clips and larger canvases are an experiment, not a promise.

Per-rung noise is derived from the request seed, so a render is deterministic for a given seed on a given machine — but it is not bit-identical to FastVideo's own output, which draws from PyTorch's generator.

DMD 5B

wan22-ti2v-5b:dmd is FastVideo's DMD distill of the same 2.2 TI2V-5B transformer (FastVideo/FastWan2.2-TI2V-5B-FullAttn-Diffusers, Apache-2.0). It walks the same three integer rungs as the 1.3B distill, but on its own shift-5 flow-match table, not the family's shift-8:

RungTimestep
11000
2757
3522

FastVideo's own inference code hardcodes shift-8 for every DMD tier it ships, which contradicts this checkpoint's own config (flow_shift = 5.0) and the shift its distillation recipe actually trained against (--flow_shift 5). mold follows the training shift, not the inference-stage bug, so --sample-shift is refused by name against 5, not 8 — do not read this as the same table the 1.3B distill uses.

Steps, guidance, sample solver and flow shift are all fixed, and mold refuses a request that sets any of them, for the same reason as the 1.3B distill: a DMD student predicts the clean latent x0 at each rung and is re-noised to the next, so the rungs are the schedule the network was trained to answer. Everything else is the base checkpoint's: 1280x704, 121 frames at 24 fps, and the shared UMT5 encoder and 2.2 VAE. The checkpoint itself is a single 9.3 GiB bf16 transformer file carrying the base checkpoint's exact architecture and key set — distilled weights only, no image embedder added.

Against the base tier's 20 steps with CFG, that is (20 x 2) / 313.3x fewer transformer forwards per clip.

Image-to-video is refused on this tier, where the other three 5B tiers accept it. Upstream ships no image branch for this checkpoint: the DMD stage has none, the distillation was data-free on text-to-video trajectories with a scalar timestep, and the model card says text-to-video. mold's TI2V latent-inpaint path does run on it — latent frame 0 is pinned and re-imposed after every rung, and it comes back bit-exact — but the student never saw a per-token t=0 and does not treat that pinned frame as where the clip starts. Measured against :turbo from the same stills, prompts, and seeds on an L40S, it lurches to a tighter, more saturated framing within about four frames, and on a person it changes clothing and build while doing so, leaving the pinned frame an orphan; :turbo holds the same sources across the whole clip. Use :turbo, :fp16, or :q8 for image-to-video on this checkpoint.

Measured on an NVIDIA L40S (CUDA, math attention, no flash-attn), 1280x704 x 121 frames at 24 fps, one prompt and seed, the base tier run with MOLD_WAN_STEP_CACHE=off (a 20-step 5B run engages the same auto cache that collapsed the 1.3B base when auto is selected explicitly, see #1559):

TierForwardsPipeline totalOf which denoise
wan22-ti2v-5b:fp1640258.2 s~194 s
wan22-ti2v-5b:turbo480.9 s~25 s
wan22-ti2v-5b:dmd385.0 s~21 s

That is 3.0x on the whole run and ~9x on the denoise loop against the base tier. The denoise column is derived by subtracting the reported load, encode, and decode phases from the total, so read it as approximate. The pipeline totals are much closer than the loop times because a 121-frame 720p clip spends ~38 s in the VAE decode and ~11 s loading and running the text encoder whatever the tier does. The DMD run above was also the cold one, paying 13.5 s to load the transformer where the warm :turbo run beside it paid 4.4 s; the same DMD render repeated warm in 75.6 s. At 832x480 x 81 frames the same three tiers come in at 78.4 s, 39.0 s, and 36.8 s.

Same seed, same machine, the tier renders byte-identical output on a rerun.

Per-rung noise is derived from the request seed, so a render is deterministic for a given seed on a given machine, but it is not bit-identical to FastVideo's own output, which draws from PyTorch's generator.

Recipe controls

Three request-level knobs reproduce published Wan recipes; each stays absent by default so the tier defaults above remain authoritative. A fourth, env-scoped knob trades denoise steps for time on the quality tiers.

Flow shift (--sample-shift, env fallback MOLD_WAN_SHIFT) is the family's primary quality/character knob — upstream ships per-task values from 3.0 to 16, diffusers documents 2.0–5.0 for low resolutions and 7.0–12.0 for high, Lightning wants 5, upstream's 720p quality A14B T2V wants 12, and ComfyUI templates ship 8. Precedence is request > env > per-tier default, so two queued jobs can run different shifts on one server.

Sample solver (--sample-solver unipc|euler|dpm++, env fallback MOLD_WAN_SOLVER, wire slot scheduler) selects the denoise algorithm:

  • unipc (default) — FlowUniPC order-2 predictor-corrector, the UAT'd recipe every existing seed reproduces.
  • euler — plain flow Euler over the same grid; the solver the lightx2v 4-step Lightning distills were tuned for. At 4 steps, order 2 vs order 1 is a real output difference.
  • dpm++ — upstream's FlowDPMSolverMultistepScheduler (order 2, dpmsolver++ midpoint) over its own sigma grid, which starts at exactly 1.0 (first DiT timestep 1000) — useful for A/B-ing quality-tier renders against upstream, golden-pinned to fm_solvers.py.

Distill strength (--distill-strength high=X,low=Y, or one number for both experts) scales the manifest-shipped Lightning adapters per expert. The fast tier's documented failure mode is reduced motion / grayish output; the community mitigation (lightx2v-acknowledged) is high-noise strength 1.5–2.0 with low at 1.0, and/or 5–6 steps at guidance 1. A strength on a tier that ships no distill in that slot (the :q8 quality tier) is refused, not ignored.

Step cache (MOLD_WAN_STEP_CACHE=off|auto|<threshold>, env-scoped) applies the FBCache formulation to the quality tiers: block 0 runs every step, and when its residual moves less than the relative-L1 threshold, the remaining blocks are skipped and their previous contribution replayed. auto uses threshold 0.10, measured at 1.85× on wan22-t2v-a14b:q8 (605.6 s → 327.4 s at 33f/832x480) with no visible artifacting. A cached run is a different sample of comparable quality, not the same frames faster — skipping steps changes the trajectory. The conditional and unconditional passes keep independent caches and the cache resets at the A14B expert swap. It refuses (with a message rather than a silent no-op) on the 4-step Lightning distill tiers and on schedules under 12 steps — neither has redundant steps to skip. Wan 1.3B and dense Wan 2.1 14B also refuse both auto and explicit positive thresholds: their cached default renders can collapse into noise or blur, so they always run every transformer block. Wan 2.2 A14B remains qualified because its two-expert graph was measured independently. off is bit-identical to the uncached engine.

The 0.10 threshold was tuned on A14B at 33f/832x480, and it holds there: an A/B on wan22-t2v-a14b:q8 renders comparable detail at 1.46x on an L40S. It is a per-tier judgement, not a universal one — on TI2V-5B at 121f/1280x704 the same threshold buys 1.68x but visibly softens fine detail, so a shape far from where it was tuned is worth an A/B against off before trusting it.

off is the default. Approximate reuse must be selected explicitly with auto or a numeric threshold: a controlled Metal 1.3B qualification produced a coherent scene with full denoising and saturated fields with the cache enabled. Wan 1.3B refuses reuse even when explicitly requested. The memory engaging the cache holds — the retained residuals per trajectory, and the transients around the distance check — is charged against the same activation estimate that admission and the block-offload policy both read.

Long clips and --frames

--frames past a tier's single-pass budget auto-chains into stages. That only produces a longer video on a checkpoint that can hand motion to the next stage: an I2V or TI2V tier seeds each continuation with the previous clip's final frame. A text-to-video checkpoint has no image conditioning at all, so it carries nothing across the boundary and every stage re-derives the scene from the same prompt and seed — the result is the same clip repeated, not a longer one.

Every door refuses that up front rather than spending the renders: mold run, the apps' Create rail, and POST /api/chain-jobs, which answers 422 for an ephemeral (one-shot) chain on such a tier. All three render one sentence, from mold_core::chain::text_only_auto_chain_refusal. Ask for --frames within the tier's budget, or use an image-conditioned tier — wan22-i2v-a14b:* or wan22-ti2v-5b:fp16 / :q8 / :turbo. Note the tag: wan22-ti2v-5b:dmd refuses the source frame, so it falls under this same rule rather than being the way around it.

A scripted sequence is untouched. Repeating a text-to-video clip is a legitimate thing to compose deliberately — mold run --script and POST /api/chain-jobs still build one and still concatenate independent clips with Join / Cut / Crossfade seams. The refusal is only for the split mold performs on your behalf when you asked for one long video.

Quantized checkpoints and adapters

A14B ships as GGUF. Quantized weights stay quantized in memory and dequantize inside the matmul, which is what keeps a 14B expert at ~10.8 GB rather than ~28 GB. A LoRA cannot be merged into a weight in that state without requantizing it, so on GGUF mold applies adapters as a parallel branch instead — low-rank for A/B pairs, dense for full-weight .diff deltas — the same arithmetic, applied at full precision, with no load cost. On bf16 safetensors the adapter is merged as the weights are read; fp8-scaled checkpoints refuse adapter stacks rather than re-round their weights.

Community adapters that carry .diff (full weight delta) and .diff_b (bias delta) tensors alongside or instead of their low-rank pairs — Kijai's Wan 2.1 lightx2v extractions, lightx2v's distill pairs, musubi-tuner-trained Civitai LoRAs — load fully: W' = W + strength·diff, b' = b + strength·diff_b, matching ComfyUI. The kohya alpha never rescales a full-weight delta (a delta has no rank), and a delta naming a tensor the checkpoint does not have still refuses the whole adapter rather than applying the part that matches.

*_fp8_e4m3fn_scaled safetensors also load: the weights stay 1 byte per parameter and dequantize per call against their per-module scale. The e5m2 variants some repositories publish beside them are refused by name — mold reads the e4m3 flavour only.

Checkpoint key layouts

Three safetensors key layouts load, and mold picks between them by reading the file's own header — never its filename:

  • Upstream / ComfyUI names at the file root (blocks.0.self_attn.q.*).
  • Comfy-Org repacks, the same names under a model.diffusion_model. prefix.
  • diffusers WanTransformer3DModel exports (blocks.0.attn1.to_q.*, condition_embedder.*, ffn.net.0.proj), translated at load through the same rename table the golden parity test uses — including diffusers' norm2/norm3 swap.

patch_embedding.weight is spelled identically in all three, so the self-attention query projection is what actually distinguishes them. A checkpoint matching none of the layouts is refused by name at load rather than constructing a transformer from default weights and rendering noise.

A layout that loads is not a promise that every checkpoint in it runs. Wan 2.1 image-to-video is refused in either spelling — original cross_attn.k_img or diffusers attn2.add_k_proj / condition_embedder.image_embedder.* — since its CLIP-vision cross-attention branch is not implemented; use wan22-i2v-a14b or wan22-ti2v-5b for image conditioning. A diffusers-layout file is additionally scanned whole and refused by name when it carries tensors the rename table does not cover, such as VACE control blocks (vace_blocks.*), rather than loading the covered subset. *_fp8_e4m3fn_scaled weights read in the original layout only: the per-tensor <module>.scale_weight is not a module path, so there is nothing for the rename table to translate, and a diffusers-keyed fp8 file is refused rather than half-loaded.

Discovery

The models in the table above install by name. Community Wan fine-tunes are additionally discoverable in the catalog — open Models → Discover in Mold Studio and search for wan, then install the row, or pull a Civitai version id directly:

bash
mold pull wan22-t2v-a14b:q5   # manifest name — the A14B fast tier
mold pull wan21-t2v-1.3b      # bare names resolve their default tag
mold pull cv:<version-id>     # a catalog row

mold pull takes manifest names and catalog ids (cv:…, hf:…). A catalog id has to name a row the catalog actually supports, which is not the same as any Hugging Face repository — hf:Wan-AI/Wan2.2-T2V-A14B, for instance, is the upstream aggregate repository, not a single runnable checkpoint; the A14B manifest tiers above are its supported route. When in doubt, install from Models → Discover, which only lists rows this build can run.

Every Wan checkpoint in the wild ships the transformer alone, so a catalog install also pulls the shared UMT5-XXL encoder and the matching VAE. Those are the same files the manifest models use, under shared/wan/, so a second Wan install reuses them.

A14B fine-tunes install as a pair. Civitai publishes the two A14B experts as separate model versions of one model (… HighNoise / … LowNoise, HIGH Q8 / LOW Q8, and similar). mold pairs the high-noise version with its low-noise sibling into one install: the catalog shows one row per pair, either expert's cv: id resolves to the same two-file download, and the installed model denoises with both experts exactly like the manifest tiers (switching at timestep 875 for T2V, 900 for I2V). A version whose counterpart cannot be identified with confidence — merged "all-in-one" republications, a high-noise-only upload, ambiguous naming — stays visible but is refused with the reason rather than installed as a single expert, which would render silently wrong. If one half of an installed pair goes missing, the row reports not-installed and re-running the install resumes just the missing half.

What the catalog deliberately does not offer:

  • Wan 2.1 image-to-video conditions through a CLIP-vision cross-attention branch mold's transformer does not implement, so the download would install and then fail to generate.
  • Wan 2.5 and 2.7 are later architectures with no mold engine.
  • GGUF Civitai rows — the Civitai path is safetensors-only; the GGUF A14B tiers ship through the manifest names above.
  • 4-bit (NF4/NVFP4) safetensors — the Wan loader reads dense, scaled-FP8, and GGUF weights only, so these versions are dropped rather than offered as multi-gigabyte downloads that fail at load.

Wan 2.1 text-to-video at either size, TI2V-5B, and paired A14B fine-tunes install from the catalog normally.

Roadmap

Remaining Wan work is tracked in the Wan Video milestone.

The Wan ecosystem is much wider than text-to-video and image-to-video — VACE, Fun-Control, camera, Phantom, track, audio-driven (S2V, HuMo, InfiniteTalk, WanDancer), and character animation (Animate, SCAIL) all exist upstream. Every one of them carries an explicit decision in the ComfyUI parity ledger: supported, earning an engine, deferred with the blocker named, or dropped with the reason. If a Wan variant you use is not in this page's model table, that ledger says why and what would change it.

One naming trap worth stating here: ComfyUI's Fun Inpaint is the first/last-frame contract under a different brand name. In mold it is --image plus --last-image (the keyframes pair), not a separate model.