--- title: SANA-WM metatags: description: "Deploy SANA-WM with SGLang - a camera-controlled text+image-to-video world model with WASD/IJKL 6-DoF camera control, served three ways: dense bidirectional and chunk-causal batch streaming over /v1/videos (SanaWMTwoStagePipeline), and live over a realtime WebSocket API (SanaWMRealtimePipeline, /v1/realtime_video/generate)." --- import { DiffusionModelTags } from '/src/snippets/diffusion/model-tags.jsx'; ## 1. Model Introduction [SANA-WM](https://huggingface.co/Efficient-Large-Model/SANA-WM_bidirectional) 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. | Serving mode | Checkpoint and API | Best fit | Tradeoff | | --- | --- | --- | --- | | Dense bidirectional | `SANA-WM_bidirectional` via `/v1/videos` | Highest single-clip quality | Bounded one-shot generation; full attention cost | | Batch streaming | `SANA-WM_streaming` via `/v1/videos` | Long clips returned as one job | Chunk-causal context rather than global bidirectional context | | Live realtime | `SANA-WM_streaming` via WebSocket | Interactive camera events and incremental output | Stateful session; not bit-identical to batch streaming | **Architecture & components** | Component | Value | |---|---| | Stage-1 DiT | 2.6B; 20 layers, hidden 2240, 20 heads (head_dim 112); ~10 GB | | Attention | frame-wise Gated DeltaNet + softmax every 4th block (hybrid linear) | | Camera | dual-branch, UCPE + PRoPE (raymap + Plücker), 6-DoF | | VAE | LTX-2 causal, strides (T, H, W) = (8, 32, 32); ~2 GB | | Refiner | LTX-2 Stage-2 distilled; ~41 GB | | Output | up to 720p (704×1280) @ 16 fps, minute-scale | For architecture and training details, see the [SANA-WM paper](https://arxiv.org/abs/2605.15178) and [model card](https://huggingface.co/Efficient-Large-Model/SANA-WM_bidirectional) (Apache-2.0). ## 2. Installation SGLang-diffusion offers multiple installation methods depending on your hardware platform. Please refer to the [SGLang Diffusion installation guide](../../../docs/sglang-diffusion/installation). 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: | Mode | `--model-path` | |---|---| | Dense bidirectional (§4) | `Efficient-Large-Model/SANA-WM_bidirectional` | | Batch streaming (§5) / realtime (§6) | `Efficient-Large-Model/SANA-WM_streaming` | 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: | Component (`model_index.json`) | Class | |---|---| | `transformer` (Stage-1 DiT) | `diffusers.SanaWMTransformer3DModel` | | `vae` | `diffusers.AutoencoderKLCausalLTX2Video` | | `text_encoder` | `transformers.Gemma2Model` | | `tokenizer` | `transformers.GemmaTokenizer` | | `scheduler` | `diffusers.FlowMatchEulerDiscreteScheduler` | 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, `` 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`): ```bash Command sglang serve \ --model-path Efficient-Large-Model/SANA-WM_bidirectional \ --pipeline-class-name SanaWMTwoStagePipeline \ --host 127.0.0.1 --port 30000 ``` 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: ```bash Command curl -s http://127.0.0.1:30000/v1/videos \ -H 'content-type: application/json' -d '{ "prompt": "a camera moving forward and turning left", "input_reference": "/path/to/first_frame.png", "num_frames": 321, "seed": 42, "fps": 16, "num_inference_steps": 60, "guidance_scale": 5.0, "diffusers_kwargs": { "action": "w-80,wl-80,l-80,wj-80", "intrinsics": "/path/to/intrinsics.npy" } }' ``` - `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: ```bash Command sglang serve \ --model-path Efficient-Large-Model/SANA-WM_streaming \ --pipeline-class-name SanaWMTwoStagePipeline \ --streaming --refiner-chunked \ --host 127.0.0.1 --port 30000 ``` - `--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: ```bash Command curl -s http://127.0.0.1:30000/v1/videos \ -H 'content-type: application/json' -d '{ "prompt": "a camera moving forward and turning left", "input_reference": "/path/to/first_frame.png", "num_frames": 321, "seed": 42, "fps": 16, "diffusers_kwargs": { "action": "w-80,wl-80,l-80,wj-80", "intrinsics": "/path/to/intrinsics.npy" } }' ``` | Field | Notes | |---|---| | `prompt` | text prompt | | `input_reference` | first-frame image — a server-side path, or (multipart) an uploaded file. For an `http(s)://` URL in a JSON body, use the separate `reference_url` field (the server downloads it and assigns it to `input_reference`) | | `num_frames` | total pixel frames (e.g. `321` → 41 latent frames, 13 chunks; output 704×1280) | | `seed` | RNG seed (default `42`) | | `fps` | output frame rate — **pass `16`** (SANA-WM's native rate). The generic `/v1/videos` default is `24`, which would encode the same frames at 24 fps and make the clip play ~33% shorter (16/24 of the duration) | | `diffusers_kwargs.action` | camera action-DSL string (§8) | | `diffusers_kwargs.intrinsics` | path to a camera-intrinsics `.npy` (per-frame `(T,3,3)`) or an inline 3×3 / `(T,3,3)` list | 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`). ```bash Command sglang serve \ --model-path Efficient-Large-Model/SANA-WM_streaming \ --pipeline-class-name SanaWMRealtimePipeline \ --host 127.0.0.1 --port 30000 ``` Common launch variants: ```bash Command # recommended multi-GPU realtime profile sglang serve \ --model-path Efficient-Large-Model/SANA-WM_streaming \ --pipeline-class-name SanaWMRealtimePipeline \ --num-gpus 8 --sp-degree 8 \ --host 127.0.0.1 --port 30000 # single GPU sglang serve \ --model-path Efficient-Large-Model/SANA-WM_streaming \ --pipeline-class-name SanaWMRealtimePipeline \ --num-gpus 1 --host 127.0.0.1 --port 30000 # offload DiT + text encoder to CPU (tight VRAM) sglang serve \ --model-path Efficient-Large-Model/SANA-WM_streaming \ --pipeline-class-name SanaWMRealtimePipeline \ --host 127.0.0.1 --port 30000 \ --dit-cpu-offload --text-encoder-cpu-offload ``` 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 request uses `guidance_scale=1.0`, so the extra CFG ranks only recompute the same branch. - **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: 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`. 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). 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: | Field | Type | Notes | |---|---|---| | `type` | `"init"` | Required literal | | `prompt` | str | Text prompt | | `first_frame` | bytes \| str | **Required by the SANA-WM adapter** (`on_init` raises if absent), though the generic request schema defines it as optional. Raw image bytes, a server-side path, or an `http(s)://` URL (downloaded & cached) | | `condition_inputs` | dict | Camera/conditioning inputs (see below) | | `num_frames` | int | Total frames to generate. **Omit it for an open-ended, continuous session** — the adapter leaves `num_frames` unset and flags an open-ended run (`condition_inputs["sana_wm_open_ended"] = True`), generating uniform chunks indefinitely (until `max_chunks` or the client disconnects). Provide an integer for a fixed-length clip | | `seed` | int | RNG seed (default `42`) | | `size` | str | `"WIDTHxHEIGHT"`; realtime requests default to `"832x480"` for latency. Pass `"1280x704"` for the native landscape resolution | | `max_chunks` | int | Optional cap on total chunks generated | | `num_inference_steps` | int | Default `4` for SANA-WM (realtime adapter) | | `guidance_scale` | float | Default `1.0` | | `realtime_output_format` | `"raw"` \| `"webp"` \| `"jpeg"` | Frame encoding for output (see below) | | `realtime_causal_sink_size` | int | Optional override | | `realtime_causal_kv_cache_num_frames` | int | Optional override | `condition_inputs` accepts (all optional; pass **only one** of `action` / `camera_actions`): | Key | Type | Meaning | |---|---|---| | `camera_actions` | `list[list[str]]` or `{mode: "state", transitions: [...]}` | Frame-by-frame camera actions, or state-based transitions | | `action` | str | Action-DSL string, e.g. `"w-10,none-5,a-8"` (see §8) | | `intrinsics_path` | str | Server-side path to a camera-intrinsics **`.npy`** file (loaded via `np.load`; shapes `(4,)`, `(3,3)`, or `(F,3,3)`) | | `intrinsics` | list | Inline intrinsics with shape `(4,)`, `(3,3)`, `(F,4)`, or `(F,3,3)` | 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. ```json INIT (msgpack dict) — open-ended (omit num_frames) { "type": "init", "prompt": "beautiful landscape video", "first_frame": "", "size": "832x480", "seed": 42, "max_chunks": 10, "realtime_output_format": "raw", "num_inference_steps": 4, "guidance_scale": 1.0, "condition_inputs": { "camera_actions": [["w"], [], ["a", "s"]], "intrinsics_path": "/path/to/intrinsics.npy" } } ``` ### Live EVENT messages `RealtimeEvent` (`type: "event"`). Use `kind` + `payload` (optional `event_id` correlates the response back to this event). ```json EVENT - camera_actions (frame-by-frame list[list[str]]) { "type": "event", "kind": "camera_actions", "event_id": 1, "payload": [["w"], ["w"], ["a"], []] } ``` ```json EVENT - camera_actions (state-based transitions) { "type": "event", "kind": "camera_actions", "event_id": 2, "payload": { "mode": "state", "transitions": [ {"actions": ["w"], "client_ts_ms": 1000}, {"actions": ["a", "w"], "client_ts_ms": 1500} ] } } ``` ```json EVENT - action (DSL string) { "type": "event", "kind": "action", "event_id": 3, "payload": "w-10,none-5,a-8,d-10" } ``` ### 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: | Field | Meaning | |---|---| | `type` | `"frame_batch"` (always) | | `request_id` | Generation id | | `chunk_index` | Chunk index | | `content_type` | `application/x-raw-rgb`, `application/x-raw-rgb-delta-gzip`, `image/webp`, or `image/jpeg` | | `num_frames` | Frames in this batch | | `total_size` | Payload size in bytes (`len(payload)` — the compressed size for delta-gzip) | | `width`, `height`, `channels` | Frame geometry (`channels: 3`) | | `bytes_per_frame` | Bytes per uncompressed frame (`width*height*3`) | | `format` | `rgb24` for raw | | `encoding` | `raw`, `delta-gzip`, `webp`, or `jpeg` | | `delta_reference` | `previous-frame` (present for delta-gzip) | | `event_id` | Echoes the steering event id; **omitted** from the header for INIT-only chunks | | `frame_batch_index`, `num_frame_batches` | Sequence multiple batches within a chunk | | `is_final_frame_batch` | `true` ends the chunk | ```json Server output - frame_batch (msgpack dict) { "type": "frame_batch", "request_id": "uuid-string", "chunk_index": 0, "content_type": "application/x-raw-rgb-delta-gzip", "num_frames": 3, "total_size": 1048576, "width": 1280, "height": 704, "channels": 3, "bytes_per_frame": 2703360, "format": "rgb24", "encoding": "delta-gzip", "delta_reference": "previous-frame", "event_id": 1, "frame_batch_index": 0, "num_frame_batches": 1, "is_final_frame_batch": true, "payload": "" } ``` **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 Python import msgspec import numpy as np import websockets # pip install websockets WS_URL = "ws://127.0.0.1:30000/v1/realtime_video/generate" async def run(): async with websockets.connect(WS_URL, max_size=None) as ws: # 1) INIT — omit num_frames for an open-ended session; "raw" = uncompressed RGB24 with open("first_frame.png", "rb") as f: first_frame = f.read() await ws.send(msgspec.msgpack.encode({ "type": "init", "prompt": "a camera moving forward and turning right", "first_frame": first_frame, "size": "832x480", "seed": 42, "max_chunks": 10, "realtime_output_format": "raw", "num_inference_steps": 4, "guidance_scale": 1.0, "condition_inputs": { "action": "w-100,wd-50,d-30", "intrinsics_path": "/path/to/intrinsics.npy", # optional; centered heuristic if omitted }, })) # 2) optional: steer mid-stream await ws.send(msgspec.msgpack.encode({ "type": "event", "kind": "camera_actions", "event_id": 1, "payload": [["w"], ["w"], ["a"], []], })) # 3) receive frame batches (raw RGB24) async for message in ws: msg = msgspec.msgpack.decode(message) if msg.get("type") != "frame_batch": continue # skip chunk_stats etc. n, h, w, c = msg["num_frames"], msg["height"], msg["width"], msg["channels"] frames = np.frombuffer(msg["payload"], dtype=np.uint8).reshape(n, h, w, c) # ... display/save frames ... if msg.get("is_final_frame_batch") and msg.get("chunk_index", 0) >= 9: break # asyncio.run(run()) ``` ## 8. Camera Action DSL Camera trajectories are described by a compact string of comma-separated `-` 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 `-`; `` 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`. | Key | Motion | |---|---| | `w` / `s` | move forward / backward | | `a` / `d` | strafe left / right | | `i` / `k` | look (pitch) up / down | | `j` / `l` | look (yaw) left / right | 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`) | Field | Default | Purpose | |---|---|---| | `height` | `704` | Output height | | `width` | `1280` | Output width | | `num_frames` | `49` | Total pixel frames (must satisfy `(num_frames - 1) % 8 == 0`) | | `fps` | `16` | Output frame rate (overrides the base default of 24) | | `num_inference_steps` | `20` | Stage-1 step count | | `guidance_scale` | `4.5` | Dense-path CFG scale | | `negative_prompt` | `""` | Negative prompt | | `camera_to_world` | `None` | In-memory `(T,4,4)` c2w extrinsics (mutually exclusive with `action`) | | `intrinsics` | `None` | In-memory `(T,3,3)` pinhole intrinsics | | `action` | `None` | Action-DSL string (see §8) | | `translation_speed` | `0.04` | World-units/frame for W/S/A/D | | `rotation_speed_deg` | `1.2` | Degrees/frame for I/K/J/L | | `pitch_limit_deg` | `85.0` | Pitch clamp | `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: | Field | Default | Purpose | |---|---|---| | `streaming` | `False` | Chunk-causal `forward_long` (§5) vs dense one-shot (§4) | | `refiner_chunked` | `True` | Chunk-wise streaming refiner vs whole-clip dense refiner | | `num_frame_per_block` | `3` | Latent frames per Stage-1 / refiner chunk | | `num_cached_blocks` | `2` | Rolling KV-cache history window | | `denoising_step_list` | `(1000, 960, 889, 727, 0)` | 4-step streaming self-forcing timesteps (must end in 0) | | `streaming_cfg_scale` | `1.0` | CFG scale for the distilled streaming path (1.0 = off) | | `sink_size` | `1` | Sink (unrefined context) frames | | `refiner_block_size` | `3` | Refiner block size | | `refiner_kv_max_frames` | `11` | Refiner sliding KV window | ### Realtime adapter init overrides — `SanaWMRealtimeAdapter` At WebSocket `init` the realtime adapter fills SANA-WM defaults that differ from the request/sampling defaults above: | Field | Realtime default | Note | |---|---|---| | `size` | `832x480` | Realtime request default; pass `1280x704` for native landscape output | | `num_frames` | *(unset)* | Omitting → open-ended continuous session (§7) | | `num_inference_steps` | `4` | Distilled few-step | | `guidance_scale` | `1.0` | CFG off | | `fps` | `16` | Native rate | `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). ## 10. Run in ComfyUI import { ComfyUISupport } from '/src/snippets/diffusion/comfyui-support.jsx';