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 theSanaWMTransformer3DModel + 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 amodel_index.jsonplus the required component subdirectories (transformer/,vae/). - If
text_encoder/tokenizerare not provided as component paths, the pipeline falls back to the default Stage-1 text encoderEfficient-Large-Model/gemma-2-2b-it(DEFAULT_SANA_WM_TEXT_ENCODER). - Pick the path with
--pipeline-class-name. The checkpoint’smodel_index.json_class_nameselects the default pipeline (SanaWMTwoStagePipeline). Pin it explicitly to choose:--pipeline-class-name SanaWMTwoStagePipelinefor the/v1/videospaths (§4–5) or--pipeline-class-name SanaWMRealtimePipelinefor live realtime (§6). Pinning is also required if you point--model-pathat 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), andrefiner/text_encoder(the Gemma-3 encoder fortext_encoder_2, whose tokenizer also serves astokenizer_2). The refiner is optional: it is skipped (Stage-1-only output) when the env flagSGLANG_SANA_WM_SKIP_REFINER(or askip_refinerrequest extra) is set, or when norefiner/is present (transformer_2unloaded). 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 aSanaWMChunkedRefinerChainStageonly 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
/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 (theSanaWMSamplingParamsdefaults 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=321request 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-causalforward_longStage-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 (default3).
/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 toSanaWMTwoStagePipeline, 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
Command
- Default endpoint is
127.0.0.1:30000(--host/--portoverride). - CPU offload flags are optional.
--dit-cpu-offload,--text-encoder-cpu-offload, and--image-encoder-cpu-offloadare available; defaults are auto-adjusted from GPU memory (GPUs under 30 GB get more aggressive offloading). - Multi-GPU realtime. Prefer explicit sequence parallelism (
--sp-degreeequal to the number of GPUs for a single session). Do not enable CFG parallel for the realtime profile: the default realtime request usesguidance_scale=1.0, while CFG parallel requires active cond/uncond branches. - FSDP. Use
--use-fsdp-inferenceonly 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-modesetting is needed.
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 withtype: "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)
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. nonemeans 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 exactlywasdijkl.
Pose generation (
action_string_to_c2w):
- Translation (
w/s/a/d) moves attranslation_speed(default0.04world-units/frame). - Rotation (
i/kpitch,j/lyaw) turns atrotation_speed_deg(default1.2°/frame); pitch is clamped to ±85°. - Strafe-yaw coupling (coefficient
0.4): ad(right) strafe also nudges yaw right anda(left) nudges yaw left, sowdtraces 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.
"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).