Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
66b5a07
feat(sana-wm): add diffusers-style SANA-WM camera-controlled I2V pipe…
HaoyiZhu May 25, 2026
a764dee
feat(sana-wm): align pipeline with merged sana_video style; fix mp4 e…
lawrence-cj Jun 2, 2026
bd08244
feat(sana-wm): port chunk-causal AR refiner mode (RefinerChunkRunner …
lawrence-cj Jun 2, 2026
34f0d81
feat(sana-wm): block-level checkpoint for AR refiner (resume after pr…
lawrence-cj Jun 2, 2026
4d8b2cc
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Jun 9, 2026
44aa5cb
test(sana-wm): add CPU unit tests + slow GPU integration stub
lawrence-cj Jun 9, 2026
bf48b22
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Jun 10, 2026
32ea160
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Jun 15, 2026
1afe995
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Jun 16, 2026
c0712d3
feat(sana-wm): make triton optional + auto-fallback to pure-PyTorch a…
lawrence-cj Jun 16, 2026
e1d13d5
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Jun 18, 2026
efada20
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Jun 24, 2026
0c23442
fix(sana-wm): make optional deps lazy + register transformer in __init__
lawrence-cj Jun 25, 2026
7b7dea1
style(sana-wm): apply make style + fix-copies
lawrence-cj Jun 25, 2026
c3fe5b4
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Jun 25, 2026
eb7b3df
refactor(sana-wm): drop einops dependency, inline with torch ops
lawrence-cj Jun 25, 2026
1b81344
refactor(sana-wm): remove dead code per @dg845's review
lawrence-cj Jun 25, 2026
2723228
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Jun 25, 2026
d352443
fix(sana-wm): defer transformers import + document mask/return_dict
lawrence-cj Jun 26, 2026
aa06c13
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Jun 29, 2026
271d94f
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Jul 1, 2026
08d3bde
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Jul 2, 2026
f259221
refactor(sana-wm): address review feedback on SanaWMPipeline
lawrence-cj Jul 2, 2026
6317ce3
refactor(sana-wm): make SanaWMLTX2Refiner a standalone DiffusionPipeline
lawrence-cj Jul 2, 2026
b66576e
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Jul 3, 2026
63447e5
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Jul 7, 2026
5c5e26a
docs(sana-wm): document the refiner __call__ `device` arg
lawrence-cj Jul 7, 2026
0fcaba0
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Jul 8, 2026
6b19325
refactor(sana-wm): clean up dead code + reuse shared utils in the DiT
lawrence-cj Jul 8, 2026
3af3193
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Jul 8, 2026
29685d9
refactor(sana-wm): merge the 3 DiT classes into one SanaWMTransformer…
lawrence-cj Jul 9, 2026
878836a
refactor(sana-wm): drop the no-op custom grad-checkpoint wrapper
lawrence-cj Jul 9, 2026
da08192
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Jul 10, 2026
0ec7905
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Jul 23, 2026
62e444c
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Jul 30, 2026
2c0f7b4
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Aug 20, 2026
403db13
refactor(sana-wm): drop the fla-core and termcolor dependencies
lawrence-cj Aug 20, 2026
cf38f91
fix(sana-wm): address review nits in the pipeline / refiner / cam utils
lawrence-cj Aug 20, 2026
20b9992
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Aug 21, 2026
e933156
refactor(sana-wm): strip research-repo code from the DiT, fix model c…
lawrence-cj Aug 22, 2026
b9fd857
fix(sana-wm): honour scheduler shift, use randn_tensor, drop cluster …
lawrence-cj Aug 22, 2026
7fac24a
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Aug 24, 2026
64f2450
refactor(sana-wm): drop the timm dependency
lawrence-cj Aug 25, 2026
d435712
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Aug 25, 2026
bfcd030
refactor(sana-wm): drop the Triton kernels and apply review feedback
lawrence-cj Aug 26, 2026
ed3c413
refactor(sana-wm): inline the modulation helper, drop the backward-on…
lawrence-cj Aug 26, 2026
ab724fc
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Aug 26, 2026
cc35384
Update src/diffusers/models/transformers/transformer_sana_wm.py
lawrence-cj Aug 26, 2026
18cacc4
Update src/diffusers/models/transformers/transformer_sana_wm.py
lawrence-cj Aug 26, 2026
33c3b9a
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Aug 27, 2026
d925f45
style(sana-wm): drop the blank line left by removing the camera-branc…
lawrence-cj Aug 27, 2026
d09e4e7
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Aug 27, 2026
ca4b226
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Aug 28, 2026
88e6378
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Sep 1, 2026
99d51eb
refactor(sana-wm): address @dg845's review round
lawrence-cj Sep 2, 2026
82c902c
refactor(sana-wm): rename WanRotaryPosEmbed to SanaWMRotaryPosEmbed
lawrence-cj Sep 2, 2026
d44abcb
test(sana-wm): add transformer model tests, migrate pipeline tests to…
lawrence-cj Sep 2, 2026
8b18fb7
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
lawrence-cj Sep 2, 2026
f563a3e
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Sep 2, 2026
dc45e37
fix(sana-wm): make the transformer layerwise-casting and offload safe
lawrence-cj Sep 3, 2026
1a78e2d
refactor(sana-wm): explicit arguments instead of **kwargs, generator …
lawrence-cj Sep 3, 2026
7cbe9a7
refactor(sana-wm): drop the unreachable chunk-split strategies
lawrence-cj Sep 3, 2026
d689b32
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Sep 3, 2026
0c2d585
refactor(sana-wm): use the shared RMSNorm
lawrence-cj Sep 4, 2026
2fdb1ed
refactor(sana-wm): migrate MultiHeadCrossAttention to the diffusers a…
lawrence-cj Sep 4, 2026
cf7531f
feat(sana-wm): give the refiner its own transformer model
lawrence-cj Sep 4, 2026
92929a2
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Sep 5, 2026
8f9af9a
refactor(sana-wm): run the refiner as a separate pipeline
lawrence-cj Sep 6, 2026
d6d3205
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
dg845 Sep 7, 2026
725e986
Apply style fixes
github-actions[bot] Sep 8, 2026
52dad68
docs(sana-wm): document the refiner's output_type argument
lawrence-cj Sep 8, 2026
6b404dd
refactor(sana-wm): support only what the released checkpoint uses
lawrence-cj Sep 9, 2026
82779e7
refactor(sana-wm): make the softmax attention class standalone
lawrence-cj Sep 9, 2026
3170044
feat(sana-wm): export the refiner weights in bfloat16
lawrence-cj Sep 9, 2026
c636bbd
refactor(sana-wm): pipeline clean-up from @yiyixuxu's review
lawrence-cj Sep 10, 2026
083bdb9
refactor(sana-wm): model-side clean-up from @yiyixuxu's review
lawrence-cj Sep 10, 2026
c405389
test(sana-wm): cover the GDN branch, which was silently dead in these…
lawrence-cj Sep 10, 2026
8504610
Merge branch 'main' into feat/sana-wm-diffusers-cleanup
DN6 Sep 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/source/en/_toctree.yml
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,10 @@
title: SanaTransformer2DModel
- local: api/models/sana_video_transformer3d
title: SanaVideoTransformer3DModel
- local: api/models/sana_wm_refiner_transformer3d
title: SanaWMLTX2RefinerTransformer3DModel
- local: api/models/sana_wm_transformer3d
title: SanaWMTransformer3DModel
- local: api/models/sd3_transformer2d
title: SD3Transformer2DModel
- local: api/models/skyreels_v2_transformer_3d
Expand Down Expand Up @@ -651,6 +655,8 @@
title: Sana Sprint
- local: api/pipelines/sana_video
title: Sana Video
- local: api/pipelines/sana_wm
title: SANA-WM
- local: api/pipelines/shap_e
title: Shap-E
- local: api/pipelines/stable_cascade
Expand Down
55 changes: 55 additions & 0 deletions docs/source/en/api/models/sana_wm_refiner_transformer3d.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!-- Copyright 2025 The HuggingFace Team and SANA-WM Authors. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License. -->

# SanaWMLTX2RefinerTransformer3DModel

The chunk-causal autoregressive refiner DiT used as stage 2 of [`SanaWMPipeline`], driven by
[`SanaWMLTX2Refiner`].

It is architecturally identical to [`LTX2VideoTransformer3DModel`] — same config arguments, same submodules, same
parameter names — so a released LTX-2 checkpoint loads into it unchanged. The forward pass differs:

* only the video stream is run (the audio and audio/video cross-attention branches are skipped),
* self-attention runs against an explicit sliding-window KV cache ([`SanaWMRefinerKVCache`]) holding the attention
sink plus the recent refined history, so per-block compute is bounded and total refinement cost scales linearly
with video length,
* the caller supplies the video RoPE, which lets each autoregressive window keep every frame's absolute index in the
source video (see
[`SanaWMLTX2RefinerTransformer3DModel.build_rotary_emb_for_absolute_positions`]).

The model can be loaded with:

```python
import torch
from diffusers import SanaWMLTX2RefinerTransformer3DModel

transformer = SanaWMLTX2RefinerTransformer3DModel.from_pretrained(
"Efficient-Large-Model/SANA-WM_bidirectional-diffusers",
subfolder="refiner/transformer",
torch_dtype=torch.bfloat16,
)
```

## SanaWMLTX2RefinerTransformer3DModel

[[autodoc]] SanaWMLTX2RefinerTransformer3DModel

## SanaWMRefinerKVCache

[[autodoc]] models.transformers.transformer_sana_wm_refiner.SanaWMRefinerKVCache

## SanaWMRefinerKVLayerCache

[[autodoc]] models.transformers.transformer_sana_wm_refiner.SanaWMRefinerKVLayerCache

## Transformer2DModelOutput

[[autodoc]] models.modeling_outputs.Transformer2DModelOutput
42 changes: 42 additions & 0 deletions docs/source/en/api/models/sana_wm_transformer3d.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!-- Copyright 2025 The HuggingFace Team and SANA-WM Authors. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License. -->

# SanaWMTransformer3DModel

A 3D Diffusion Transformer (1.6B parameters) for camera-controlled image-to-video generation, used as the stage-1
sampler of [`SanaWMPipeline`]. The transformer combines:

* a bidirectional GDN-Triton linear-attention main branch (depth 20, hidden 2240, 20 heads),
* a UCPE (Unified Camera Pose Embedding) camera-control branch that consumes a raymap + Plücker representation of
the requested trajectory, and
* a Wan-style 3D rotary position embedding plus periodic softmax-attention blocks injected every `softmax_every_n`
layers.

The model can be loaded with:

```python
import torch
from diffusers import SanaWMTransformer3DModel

transformer = SanaWMTransformer3DModel.from_pretrained(
"Efficient-Large-Model/SANA-WM_bidirectional-diffusers",
subfolder="transformer",
torch_dtype=torch.bfloat16,
)
```

## SanaWMTransformer3DModel

[[autodoc]] SanaWMTransformer3DModel

## Transformer2DModelOutput

[[autodoc]] models.modeling_outputs.Transformer2DModelOutput
170 changes: 170 additions & 0 deletions docs/source/en/api/pipelines/sana_wm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<!-- Copyright 2025 The HuggingFace Team and SANA-WM Authors. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License. -->

# SANA-WM

SANA-WM is a camera-controlled image-to-video world model built on top of SANA. Given a first-frame image, a text
prompt, and a camera trajectory (either explicit `c2w` poses or a WASD/IJKL action string), it generates a video
whose motion follows the requested camera path.

Inference runs in two stages:

1. **Stage 1 — SANA-WM DiT.** A 1.6B-parameter bidirectional DiT with GDN-Triton linear attention and a UCPE
camera-control branch. Sampling uses an LTX-style flow-matching Euler scheduler with per-token timesteps; the
first latent frame is the conditioning anchor.
2. **Stage 2 — LTX-2 refiner (optional).** A separate sink-bidirectional Euler refiner pipeline
([`SanaWMLTX2Refiner`]) that wraps
[`SanaWMLTX2RefinerTransformer3DModel`] + `LTX2TextConnectors` and a Gemma-3 text encoder, run for 3
distilled sigma steps.

Both stages decode through the [`AutoencoderKLLTX2Video`] VAE.

Available models:

| Model | Recommended dtype |
|:-----:|:-----------------:|
| [`Efficient-Large-Model/SANA-WM_bidirectional-diffusers`](https://huggingface.co/Efficient-Large-Model/SANA-WM_bidirectional-diffusers) | `torch.bfloat16` |
| [`Efficient-Large-Model/SANA-WM_bidirectional-diffusers-refiner`](https://huggingface.co/Efficient-Large-Model/SANA-WM_bidirectional-diffusers-refiner) | `torch.bfloat16` |

> [!TIP]
> SANA-WM is trained at a fixed 704×1280 resolution. The recommended dtype is for the transformer weights — keep
> the text encoder in `torch.bfloat16` and the VAE in `torch.float32` for best numerics. The pipeline expects
> camera intrinsics `[fx, fy, cx, cy]` in *original-image* pixel coordinates; the resize-and-center-crop transform
> is applied internally.

## Inference

```python
import torch
from PIL import Image

from diffusers import SanaWMPipeline
from diffusers.utils import export_to_video

pipe = SanaWMPipeline.from_pretrained(
"Efficient-Large-Model/SANA-WM_bidirectional-diffusers",
torch_dtype=torch.bfloat16,
)
pipe.enable_model_cpu_offload() # ~45 GB of weights — offload between stages

# SANA-WM was trained on the LTX-2 VAE in framewise mode with tiling enabled. Without these
# settings the VAE encodes the whole (B, C, T, H, W) clip in one shot, which gives subtly
# different numerics from the released checkpoint.
pipe.vae.enable_tiling()
pipe.vae.use_framewise_encoding = True
pipe.vae.use_framewise_decoding = True
pipe.vae.tile_sample_stride_num_frames = 64
pipe.vae.tile_sample_min_num_frames = 96

prompt = "A car driving across a vast desert plain at golden hour."
output = pipe(
image=Image.open("input.png").convert("RGB"),
prompt=prompt,
action="w-80,jw-40,w-40", # WASD-style action DSL: forward 80f, jump+forward 40f, forward 40f
intrinsics=[800.0, 800.0, 845.0, 464.0], # fx, fy, cx, cy in original-image pixels
num_frames=161,
num_inference_steps=60,
guidance_scale=5.0,
generator=torch.Generator(device="cuda").manual_seed(42),
output_type="latent", # hand the latents to the refiner below
)
```

Pass `action=None` and supply your own `c2w` poses (`(F, 4, 4)` numpy array) to drive the camera trajectory
explicitly. Drop `output_type="latent"` to get video straight out of stage 1 and skip the refiner.

### Stage 2 — the LTX-2 refiner

[`SanaWMLTX2Refiner`] ships as its own repository, the way SDXL splits base and refiner. Pass the base pipeline's
VAE so the weights are shared rather than loaded twice:

```python
from diffusers import SanaWMLTX2Refiner

refiner = SanaWMLTX2Refiner.from_pretrained(
"Efficient-Large-Model/SANA-WM_bidirectional-diffusers-refiner",
vae=pipe.vae,
torch_dtype=torch.bfloat16,
)
refiner.enable_model_cpu_offload()

frames = refiner(output.latent, prompt, fps=16)
export_to_video(list(frames), "sana_wm.mp4", fps=16)
```

Without a `vae` the refiner returns refined latents instead of video, which is useful if you want to decode
yourself.

> [!TIP]
> Enable offloading on **both** pipelines, or free stage 1 before stage 2 (`pipe.transformer.to("cpu")`). The two
> stages together are around 45 GB in `torch.bfloat16`, and keeping both resident on one 80 GB card leaves too
> little room for activations. Note also that stage 2 honours `torch_dtype`: loading the refiner in
> `torch.float32` roughly doubles its memory and changes the output slightly.

If you don't have camera intrinsics, a hosted [modular block](../../modular_diffusers/overview) can estimate them
from a single frame. It lives outside `diffusers` because it pulls in Pi3X — an extra dependency and a second
checkpoint — so nothing is downloaded until you ask for it:

```python
from diffusers import ModularPipeline

# One-time per image. Requires `pip install pi3-vision`.
estimator = ModularPipeline.from_pretrained(
"Efficient-Large-Model/pi3x-intrinsics-estimator", trust_remote_code=True
)
estimator.load_components(dtype=torch.bfloat16)
intrinsics = estimator(image=Image.open("input.png").convert("RGB"), output="intrinsics")
```

## Converting the released checkpoint

If you have the source SANA-WM release (not the pre-converted diffusers snapshot), run the conversion script once:

```bash
python scripts/convert_sana_wm_to_diffusers.py \
--src Efficient-Large-Model/SANA-WM_bidirectional \
--dst ./SANA-WM_bidirectional-diffusers
```

This writes two directories: the base pipeline at `--dst`, and the stage-2 refiner alongside it at
`./SANA-WM_bidirectional-diffusers-refiner` (override with `--dst-refiner`). Then load each from its local path
as usual.

## Components

- `tokenizer` — [`GemmaTokenizerFast`]
- `text_encoder` — Gemma-2 (returns decoder hidden states)
- `vae` — [`AutoencoderKLLTX2Video`] (LTX-2, spatial ×32 / temporal ×8)
- `transformer` — [`SanaWMTransformer3DModel`], 1.6B-parameter bidirectional DiT
- `scheduler` — [`FlowMatchEulerDiscreteScheduler`]

The stage-2 refiner is a separate repository with its own `transformer`
([`SanaWMLTX2RefinerTransformer3DModel`]), `connectors` (`LTX2TextConnectors`), `tokenizer`, Gemma-3
`text_encoder` and `scheduler`. It has no `vae` of its own — pass the base pipeline's.

## SanaWMPipeline

[[autodoc]] SanaWMPipeline
- all
- __call__

## SanaWMLTX2Refiner

The LTX-2 stage-2 refiner is a standalone [`DiffusionPipeline`] that takes stage-1 latents. Give it a `vae` (the
base pipeline's, so the weights are shared) to have it decode to video; without one it returns refined latents.

[[autodoc]] SanaWMLTX2Refiner
- all
- __call__

## SanaWMPipelineOutput

[[autodoc]] pipelines.sana_wm.pipeline_output.SanaWMPipelineOutput
Loading
Loading