Skip to main content

1. Model Introduction

SANA-WM is NVLabs’ 2.6B text-and-image-to-video world model for 720p, minute-scale generation with explicit per-frame 6-DoF camera control. Its hybrid recurrent/softmax attention keeps long causal histories bounded, while an LTX-2 refiner supplies the detail that the fast Stage-1 world model does not produce on its own. Choose the dense checkpoint for the best bounded-clip quality and the streaming checkpoint for long-running or interactive control. Streaming and realtime trade global bidirectional context for bounded state and lower response latency; the realtime WebSocket path is not bit-identical to offline batch streaming. Architecture & components For architecture and training details, see the SANA-WM paper and model card (Apache-2.0).

2. Installation

SGLang-diffusion offers multiple installation methods depending on your hardware platform. Please refer to the SGLang Diffusion installation guide. SANA-WM adds the SanaWMTransformer3DModel + GDN kernels, the SanaWMTwoStagePipeline (dense bidirectional + chunk-causal streaming), and the SanaWMRealtimePipeline with the /v1/realtime_video WebSocket router. Use sglang serve to launch the diffusion server.

3. Model Setup

Both SANA-WM checkpoints are public (Apache-2.0, no gating, no token) and load directly — there is no manual assembly step. Pass the HuggingFace repo id to --model-path and SGLang downloads, materializes, validates, and loads it: Both repo ids are registered in SGLang’s built-in model-overlay registry, so on first load the overlay transparently materializes the official release into a runnable Diffusers directory — for the streaming checkpoint this converts the DMD self-forcing checkpoint (sana_dit/model.pt) into a Diffusers transformer/ and wires the LTX-2 causal VAE, the LTX-2 refiner, and the Gemma encoders. No environment variable or build_model_dir.sh step is needed. (You may also pass a local, already-materialized Diffusers directory.) The materialized checkpoint is a Diffusers directory whose model_index.json declares the loadable components: How loading works:
  • The server resolves the checkpoint via maybe_download_model(model_path, force_diffusers_model=True) and verifies it contains a model_index.json plus the required component subdirectories (transformer/, vae/).
  • If text_encoder / tokenizer are not provided as component paths, the pipeline falls back to the default Stage-1 text encoder Efficient-Large-Model/gemma-2-2b-it (DEFAULT_SANA_WM_TEXT_ENCODER).
  • Pick the path with --pipeline-class-name. The checkpoint’s model_index.json _class_name selects the default pipeline (SanaWMTwoStagePipeline). Pin it explicitly to choose: --pipeline-class-name SanaWMTwoStagePipeline for the /v1/videos paths (§4–5) or --pipeline-class-name SanaWMRealtimePipeline for live realtime (§6). Pinning is also required if you point --model-path at a bare safetensors file instead of a Diffusers directory.
  • The Stage-2 LTX-2 refiner lives under refiner/ in the checkpoint: refiner/transformer (transformer_2), refiner/connectors (connectors), and refiner/text_encoder (the Gemma-3 encoder for text_encoder_2, whose tokenizer also serves as tokenizer_2). The refiner is optional: it is skipped (Stage-1-only output) when the env flag SGLANG_SANA_WM_SKIP_REFINER (or a skip_refiner request extra) is set, or when no refiner/ is present (transformer_2 unloaded). On the batch path it runs chunk-wise with --refiner-chunked (the official streaming path, default on) or whole-clip without it; on the realtime path the pipeline builds a SanaWMChunkedRefinerChainStage only when a refiner is available, and otherwise streams Stage-1 frames.
Throughout this cookbook, <checkpoint> stands for the appropriate SANA-WM repo id from the table above (or a local materialized Diffusers directory).

4. Dense bidirectional (offline /v1/videos)

The bidirectional checkpoint generates the whole clip in one shot (full bidirectional attention, not chunked) followed by a dense LTX-2 refiner — the highest single-clip quality, matching the NVlabs dense reference. Launch with the two-stage pipeline and no --streaming flag (dense is the default — streaming defaults to False):
Command
Then POST to /v1/videos exactly as in §5, but pass the NVlabs dense sampling defaults for closest parity — the dense path is denser than the distilled streaming few-step schedule:
Command
  • num_inference_steps / guidance_scale — the dense path uses CFG; NVlabs’ reference defaults to 60 steps, guidance 5.0 (the SanaWMSamplingParams defaults are the lighter 20 / 4.5 — pass 60 / 5.0 explicitly for dense parity).
  • The dense refiner drops the leading sink frame, so a num_frames=321 request yields 320 output frames.

5. Batch streaming (offline /v1/videos)

The streaming checkpoint generates a full camera-controlled clip in one request — no websocket. This is SGLang’s offline streaming path: the whole clip is generated chunk-by-chunk internally, refined, decoded, and returned as one video. Launch with the two-stage pipeline + the streaming flags:
Command
  • --streaming — chunk-causal forward_long Stage-1 (vs the dense one-shot path of §4).
  • --refiner-chunked — chunk-wise streaming LTX-2 refiner (on by default). To use the whole-clip dense refiner instead (also valid, higher peak memory), pass --refiner-chunked false — simply omitting the flag keeps the default chunked refiner.
  • --num-frame-per-block N — latent frames per chunk (default 3).
Then POST to /v1/videos (JSON body shown below; multipart/form-data with an uploaded input_reference file also works). Camera control goes in diffusers_kwargs — the action-DSL string (§8) and the intrinsics:
Command
The response is a VideoResponse; fetch the rendered MP4 via the returned reference or GET /v1/videos/{id}/content. The streaming hyperparameters (num_frame_per_block, denoising_step_list, sink_size, num_cached_blocks, streaming_cfg_scale) are pipeline-config defaults on SanaWMPipelineConfig, not request fields — see §9.

6. Launch the Realtime Server

Launch with the realtime pipeline pinned — the checkpoint defaults to SanaWMTwoStagePipeline, so realtime must be selected explicitly (see §3). The /v1/realtime_video router is always mounted and becomes functional once the realtime config is active, because SanaWMRealtimeConfig has a registered realtime adapter (SanaWMRealtimeAdapter).
Command
Common launch variants:
Command
Notes on launch behavior:
  • Default endpoint is 127.0.0.1:30000 (--host / --port override).
  • CPU offload flags are optional. --dit-cpu-offload, --text-encoder-cpu-offload, and --image-encoder-cpu-offload are available; defaults are auto-adjusted from GPU memory (GPUs under 30 GB get more aggressive offloading).
  • Multi-GPU realtime. Prefer explicit sequence parallelism (--sp-degree equal to the number of GPUs for a single session). Do not enable CFG parallel for the realtime profile: the default realtime request uses guidance_scale=1.0, while CFG parallel requires active cond/uncond branches.
  • FSDP. Use --use-fsdp-inference only when you specifically need weight sharding for memory. For the low-latency realtime profile, prefer keeping components resident and using SP first.
  • Warmup. Server warmup is automatically skipped for the realtime pipeline — a synthetic warmup request has no WebSocket session, so the server detects the registered realtime adapter and skips it. No explicit --warmup-mode setting is needed.
Once up, the realtime WebSocket endpoint lives at ws://127.0.0.1:30000/v1/realtime_video/generate (use the Python client in §7 to connect — plain curl does not speak the ws:// upgrade).

7. Realtime WebSocket API

The realtime API is a single WebSocket at /v1/realtime_video/generate. All messages — client → server and server → client — are msgpack (msgspec.msgpack.encode / decode), not JSON. The lifecycle is:
1

Connect & send INIT

The client opens the WebSocket and sends exactly one init message (type: "init"), carrying the prompt, the required first_frame, output/sampling options, and optional camera conditions in condition_inputs.
2

Stream live EVENTs (optional)

While generation runs, the client may push event messages (type: "event") to steer the camera — either kind: "camera_actions" (frame-by-frame lists or state transitions) or kind: "action" (an action-DSL string).
3

Receive frame batches

The server streams frame batches back. Each chunk arrives as one or more frame_batch messages (header fields + payload bytes); is_final_frame_batch: true marks the end of a chunk. The server also emits chunk_stats timing messages.

INIT message

RealtimeVideoGenerationsRequest (type is the literal "init"). Key fields: condition_inputs accepts (all optional; pass only one of action / camera_actions): If you omit both intrinsics_path and intrinsics, SGLang uses a centered heuristic intrinsic matrix derived from the first-frame size. Pass explicit intrinsics when you need closer camera parity with a prepared trajectory.
INIT (msgpack dict) — open-ended (omit num_frames)

Live EVENT messages

RealtimeEvent (type: "event"). Use kind + payload (optional event_id correlates the response back to this event).
EVENT - camera_actions (frame-by-frame list[list[str]])
EVENT - camera_actions (state-based transitions)
EVENT - action (DSL string)

Server frame output

The server streams frame batches. Every batch arrives as a single msgpack message with type: "frame_batch" — the header fields below plus an inline payload bytes field (the wire type is always "frame_batch"; there is no separate header-then-bytes message). Header fields:
Server output - frame_batch (msgpack dict)
Encodings. application/x-raw-rgb is uncompressed RGB24 (3 × uint8, bytes_per_frame = width*height*3). application/x-raw-rgb-delta-gzip is the zlib-compressed per-frame XOR delta against the preceding frame (each frame in the batch is XOR’d against the previous one; sent by default). realtime_output_format: "raw" forces uncompressed RGB; "webp" / "jpeg" send preview-encoded frames.
delta-gzip must be restored frame-by-frame: decompress the payload, then for each frame XOR it against the already-restored previous frame (the first frame of a batch references the last frame of the previous batch). See restore_delta_gzip_raw_rgb_payload in runtime/realtime/video.py. The "raw" format below avoids this.

Minimal client example

Python

8. Camera Action DSL

Camera trajectories are described by a compact string of comma-separated <keys>-<frames> segments, e.g. "w-100,wd-50,d-30,none-10". This is the format accepted by condition_inputs.action at init and by kind: "action" events. Parsing rules (parse_action_string):
  • Each segment is <keys>-<frames>; <frames> must be a positive integer.
  • none means no motion for that span: none-10 = 10 static frames.
  • Keys are case-insensitive; combined keys apply simultaneously (wd = forward + right strafe). Allowed keys are exactly wasdijkl.
Pose generation (action_string_to_c2w):
  • Translation (w/s/a/d) moves at translation_speed (default 0.04 world-units/frame).
  • Rotation (i/k pitch, j/l yaw) turns at rotation_speed_deg (default 1.2°/frame); pitch is clamped to ±85°.
  • Strafe-yaw coupling (coefficient 0.4): a d (right) strafe also nudges yaw right and a (left) nudges yaw left, so wd traces a curving arc rather than a pure sidestep.
  • Produces (F+1, 4, 4) camera-to-world matrices; the realtime stage pads the trajectory to the requested frame count.
Example: "w-100,wd-50,d-30,none-10" = 100 frames forward → 50 frames forward + sweep right → 30 frames right strafe → 10 frames static.

9. Configuration Reference

SANA-WM’s defaults live in three places: request-time sampling params, the pipeline config (streaming/refiner knobs), and the realtime adapter (init-time overrides).

Request-time — SanaWMSamplingParams (configs/sample/sana_wm.py)

generator_device is inherited from the base SamplingParams (default None = use the pipeline/model default). On the /v1/videos HTTP API the camera fields are passed inside diffusers_kwargs (action / intrinsics, as in §4–5).

Pipeline config — SanaWMPipelineConfig (configs/pipeline_configs/sana_wm.py)

These are server-launch knobs (set via the --streaming / --refiner-chunked / --num-frame-per-block CLI flags or a pipeline-config override), not request fields:

Realtime adapter init overrides — SanaWMRealtimeAdapter

At WebSocket init the realtime adapter fills SANA-WM defaults that differ from the request/sampling defaults above:
guidance_scale applies to the dense path (§4) only; the distilled streaming path uses streaming_cfg_scale (default 1.0, i.e. no CFG) so a guidance_scale override never accidentally enables CFG on the streaming stage. denoising_step_list = (1000, 960, 889, 727, 0) is the official 4-step streaming schedule (it must end in 0).