SANA-WM

SANA-WM

SANA-WM은 NVLabs의 2.6B 텍스트·이미지-투-비디오 월드 모델로, 720p·분 단위(minute-scale) 생성과 명시적 프레임별 6-DoF 카메라 제어를 지원합니다. 하이브리드 recurrent/softmax 어텐션은 긴 인과 기록을 제한된 상태로 유지하고, LTX-2 리파이너가 빠른 Stage-1 월드 모델이 자체적으로 만들지 못하는 디테일을 공급합니다.

출처: 문서

본문

<DiffusionModelTags tags={["world model", "720p video", "minute-scale", "6-DoF camera control", "realtime"]} />

1. 모델 소개 (Model Introduction)

SANA-WM은 NVLabs의 2.6B 텍스트·이미지-투-비디오 월드 모델로, 720p·분 단위 생성과 명시적 프레임별 6-DoF 카메라 제어를 지원합니다. 하이브리드 recurrent/softmax 어텐션은 긴 인과 기록을 제한된 상태로 유지하고, LTX-2 리파이너가 빠른 Stage-1 월드 모델이 자체적으로 만들지 못하는 디테일을 공급합니다.

최상의 제한-클립(bounded-clip) 품질에는 dense 체크포인트를, 장기 실행·대화형 제어에는 streaming 체크포인트를 선택하세요. Streaming과 realtime은 전역 양방향 컨텍스트를 제한된 상태와 더 낮은 응답 지연으로 맞바꿉니다. realtime WebSocket 경로는 오프라인 배치 스트리밍과 비트 단위 동일하지 않습니다.

서빙 모드 (Serving mode) 체크포인트와 API (Checkpoint and API) 가장 좋은 적합 (Best fit) 트레이드오프 (Tradeoff)
Dense bidirectional /v1/videos를 통한 SANA-WM_bidirectional 최고 단일-클립 품질 제한된 one-shot 생성; 전체 어텐션 비용
Batch streaming /v1/videos를 통한 SANA-WM_streaming 하나의 작업으로 반환되는 긴 클립 전역 양방향 컨텍스트가 아닌 chunk-causal 컨텍스트
Live realtime WebSocket을 통한 SANA-WM_streaming 대화형 카메라 이벤트와 증분 출력 상태 저장 세션; 배치 스트리밍과 비트 단위 동일하지 않음

아키텍처 & 구성 요소

구성 요소 (Component) 값 (Value)
Stage-1 DiT 2.6B; 20 레이어, hidden 2240, 20 헤드(head_dim 112); ~10 GB
어텐션 (Attention) 프레임별 Gated DeltaNet + 4블록마다 softmax (하이브리드 선형)
카메라 (Camera) 이중 분기, 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) 최대 720p (704×1280) @ 16 fps, 분 단위

아키텍처와 훈련 세부사항은 SANA-WM 논문모델 카드(Apache-2.0)를 참조하세요.

2. 설치 (Installation)

SGLang-diffusion은 하드웨어 플랫폼에 따라 여러 설치 방법을 제공합니다. SGLang Diffusion 설치 가이드를 참조하세요.

SANA-WM은 SanaWMTransformer3DModel + GDN 커널, SanaWMTwoStagePipeline(dense bidirectional + chunk-causal streaming), 그리고 /v1/realtime_video WebSocket 라우터가 있는 SanaWMRealtimePipeline을 추가합니다. 디퓨전 서버를 시작하려면 sglang serve를 사용하세요.

3. 모델 설정 (Model Setup)

두 SANA-WM 체크포인트는 모두 공개(Apache-2.0, 게이팅 없음, 토큰 없음)이며 직접 로드됩니다. 수동 조립 단계가 없습니다. --model-path에 HuggingFace repo id를 전달하면 SGLang이 다운로드·구체화·검증·로드합니다:

모드 (Mode) --model-path
Dense bidirectional (§4) Efficient-Large-Model/SANA-WM_bidirectional
Batch streaming (§5) / realtime (§6) Efficient-Large-Model/SANA-WM_streaming

두 repo id 모두 SGLang의 내장 model-overlay 레지스트리에 등록되어 있어, 첫 로드 시 overlay가 공식 릴리스를 실행 가능한 Diffusers 디렉터리로 투명하게 구체화합니다. streaming 체크포인트의 경우 DMD self-forcing 체크포인트(sana_dit/model.pt)를 Diffusers transformer/로 변환하고 LTX-2 causal VAE, LTX-2 리파이너, Gemma 인코더를 연결합니다. 환경 변수나 build_model_dir.sh 단계가 필요 없습니다. (로컬의 이미 구체화된 Diffusers 디렉터리를 전달할 수도 있습니다.)

구체화된 체크포인트는 model_index.json이 로드 가능한 컴포넌트를 선언하는 Diffusers 디렉터리입니다:

컴포넌트 (model_index.json) 클래스 (Class)
transformer (Stage-1 DiT) diffusers.SanaWMTransformer3DModel
vae diffusers.AutoencoderKLCausalLTX2Video
text_encoder transformers.Gemma2Model
tokenizer transformers.GemmaTokenizer
scheduler diffusers.FlowMatchEulerDiscreteScheduler

로딩 방식:

  • 서버는 maybe_download_model(model_path, force_diffusers_model=True)로 체크포인트를 해석하고 model_index.json과 필수 컴포넌트 하위 디렉터리(transformer/, vae/)를 포함하는지 검증합니다.
  • text_encoder / tokenizer가 컴포넌트 경로로 제공되지 않으면 파이프라인은 기본 Stage-1 텍스트 인코더 Efficient-Large-Model/gemma-2-2b-it(DEFAULT_SANA_WM_TEXT_ENCODER)로 폴백합니다.
  • --pipeline-class-name으로 경로를 선택하세요. 체크포인트의 model_index.json _class_name이 기본 파이프라인(SanaWMTwoStagePipeline)을 선택합니다. 명시적으로 고정하려면: /v1/videos 경로(§4–5)에는 --pipeline-class-name SanaWMTwoStagePipeline, 라이브 realtime(§6)에는 --pipeline-class-name SanaWMRealtimePipeline. Diffusers 디렉터리 대신 bare safetensors 파일을 --model-path로 가리켜도 고정이 필요합니다.
  • Stage-2 LTX-2 리파이너는 체크포인트의 refiner/ 아래에 있습니다: refiner/transformer(transformer_2), refiner/connectors(connectors), refiner/text_encoder(text_encoder_2용 Gemma-3 인코더로, 그 토크나이저가 tokenizer_2도 제공). 리파이너는 선택 사항입니다. 환경 플래그 SGLANG_SANA_WM_SKIP_REFINER(또는 skip_refiner 요청 extra)가 설정되거나 refiner/가 없으면(transformer_2 언로드) 건너뜁니다(Stage-1 전용 출력). 배치 경로에서는 --refiner-chunked(공식 스트리밍 경로, 기본 켜짐)로 청크별 실행하거나 없으면 전체-클립 실행합니다. realtime 경로에서 파이프라인은 리파이너가 있을 때만 SanaWMChunkedRefinerChainStage를 만들고, 없으면 Stage-1 프레임을 스트리밍합니다.
이 쿡북 전반에서 ``는 위 표의 해당 SANA-WM repo id(또는 로컬 구체화 Diffusers 디렉터리)를 나타냅니다.

4. Dense bidirectional (오프라인 /v1/videos)

bidirectional 체크포인트는 전체 클립을 one-shot으로 생성합니다(전체 양방향 어텐션, 청크 아님). 이어서 dense LTX-2 리파이너가 따릅니다. NVlabs dense 레퍼런스와 일치하는 최고 단일-클립 품질입니다.

2단계 파이프라인과 없는 --streaming 플래그로 시작합니다(dense가 기본 — streaming 기본값은 False):

sglang serve \
  --model-path Efficient-Large-Model/SANA-WM_bidirectional \
  --pipeline-class-name SanaWMTwoStagePipeline \
  --host 127.0.0.1 --port 30000

그런 다음 §5에서와 정확히 같이 /v1/videos 에 POST하되, 가장 가까운 패리티를 위해 NVlabs dense 샘플링 기본값을 전달하세요. dense 경로는 증류된 streaming few-step 스케줄보다 더 밀도 있습니다:

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 — dense 경로는 CFG를 사용합니다. NVlabs 레퍼런스 기본값은 60 스텝, guidance 5.0(SanaWMSamplingParams 기본값은 더 가벼운 20 / 4.5 — dense 패리티를 위해 60 / 5.0을 명시적으로 전달).
  • Dense 리파이너는 선두 sink 프레임을 버리므로 num_frames=321 요청은 320 출력 프레임을 생성합니다.

5. Batch streaming (오프라인 /v1/videos)

streaming 체크포인트는 하나의 요청으로 전체 카메라 제어 클립을 생성합니다 — websocket 없음. 이것이 SGLang의 오프라인 스트리밍 경로입니다. 전체 클립은 내부적으로 청크별 생성, 리파인, 디코딩되어 하나의 비디오로 반환됩니다.

2단계 파이프라인 + 스트리밍 플래그로 시작:

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(§4의 dense one-shot 경로 대비).
  • --refiner-chunked — 청크별 스트리밍 LTX-2 리파이너(기본 켜짐). 대신 전체-클립 dense 리파이너를 사용하려면(유효하며 최고 메모리가 더 높음) --refiner-chunked false를 전달하세요. 플래그를 그냥 생략하면 기본 chunked 리파이너가 유지됩니다.
  • --num-frame-per-block N — 청크당 잠재 프레임(기본 3).

그런 다음 /v1/videos 에 POST합니다(아래 JSON 본문. input_reference 파일을 업로드한 multipart/form-data도 동작). 카메라 제어는 diffusers_kwargs — action-DSL 문자열(§8)과 intrinsics — 에 들어갑니다:

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 텍스트 프롬프트
input_reference 첫 프레임 이미지 — 서버 측 경로 또는 (multipart) 업로드 파일. JSON 본문의 http(s):// URL에는 별도의 reference_url 필드를 사용(서버가 다운로드해 input_reference에 할당)
num_frames 총 픽셀 프레임(예: 321 → 41 잠재 프레임, 13 청크; 출력 704×1280)
seed RNG 시드(기본 42)
fps 출력 프레임 레이트 — 16(SANA-WM의 네이티브 레이트)을 전달하세요. 일반 /v1/videos 기본값은 24이며, 같은 프레임을 24 fps로 인코딩해 클립이 ~33% 더 짧게 재생됩니다(16/24 기간)
diffusers_kwargs.action 카메라 action-DSL 문자열(§8)
diffusers_kwargs.intrinsics 카메라 intrinsics .npy 경로(프레임별 (T,3,3)) 또는 인라인 3×3 / (T,3,3) 리스트

응답은 VideoResponse입니다. 반환된 reference 또는 GET /v1/videos/{id}/content로 렌더링된 MP4를 가져옵니다. 스트리밍 하이퍼파라미터(num_frame_per_block, denoising_step_list, sink_size, num_cached_blocks, streaming_cfg_scale)는 파이프라인 구성SanaWMPipelineConfig 기본값이지 요청 필드가 아닙니다 — §9 참조.

6. 실시간 서버 시작 (Launch the Realtime Server)

realtime 파이프라인을 고정해서 시작합니다 — 체크포인트 기본값은 SanaWMTwoStagePipeline이므로 realtime을 명시적으로 선택해야 합니다(§3 참조). /v1/realtime_video 라우터는 항상 마운트되며 SanaWMRealtimeConfig에 등록된 realtime 어댑터(SanaWMRealtimeAdapter)가 있어 realtime 구성이 활성화되면 기능합니다.

sglang serve \
  --model-path Efficient-Large-Model/SANA-WM_streaming \
  --pipeline-class-name SanaWMRealtimePipeline \
  --host 127.0.0.1 --port 30000

일반적인 시작 변형:

# 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

시작 동작 참고:

  • 기본 엔드포인트127.0.0.1:30000(--host / --port가 오버라이드).
  • CPU 오프로드 플래그는 선택 사항입니다. --dit-cpu-offload, --text-encoder-cpu-offload, --image-encoder-cpu-offload를 사용할 수 있습니다. 기본값은 GPU 메모리에서 자동 조정됩니다(30 GB 미만 GPU는 더 공격적 오프로드).
  • 멀티 GPU realtime. 단일 세션에는 명시적 시퀀스 병렬(--sp-degree를 GPU 수로)을 선호하세요. realtime 프로필에는 CFG 병렬을 활성화하지 마세요. 기본 요청은 guidance_scale=1.0을 사용해 추가 CFG 랭크가 같은 분지만 재계산하기 때문입니다.
  • FSDP. 메모리를 위한 가중치 샤딩이 구체적으로 필요할 때만 --use-fsdp-inference를 사용하세요. 저지연 realtime 프로필에는 컴포넌트를 상주시키고 SP를 먼저 사용하는 것을 선호하세요.
  • 워밍업. realtime 파이프라인에는 서버 워밍업이 자동으로 건너뜁니다. 합성 워밍업 요청에는 WebSocket 세션이 없으므로 서버가 등록된 realtime 어댑터를 감지하고 건너뜁니다. 명시적 --warmup-mode 설정이 필요 없습니다.

올라오면 realtime WebSocket 엔드포인트는 ws://127.0.0.1:30000/v1/realtime_video/generate에 있습니다(연결하려면 §7의 Python 클라이언트를 사용 — 일반 curlws:// 업그레이드를 말하지 않습니다).

7. Realtime WebSocket API

realtime API는 /v1/realtime_video/generate 의 단일 WebSocket입니다. 모든 메시지 — 클라이언트 → 서버 및 서버 → 클라이언트 — 는 msgpack(msgspec.msgpack.encode / decode)이며 JSON이 아닙니다.

수명주기:

클라이언트는 WebSocket을 열고 정확히 하나의 **init** 메시지(`type: "init"`)를 보냅니다. 프롬프트, 필수 `first_frame`, 출력/샘플링 옵션, 선택적 카메라 조건(`condition_inputs`에)을 담습니다. 생성이 실행되는 동안 클라이언트는 **event** 메시지(`type: "event"`)를 푸시해 카메라를 조종할 수 있습니다 — `kind: "camera_actions"`(프레임별 리스트 또는 상태 전환) 또는 `kind: "action"`(action-DSL 문자열). 서버는 **프레임 배치**를 스트리밍합니다. 각 청크는 하나 이상의 `frame_batch` 메시지(헤더 필드 + 페이로드 바이트)로 도착합니다. `is_final_frame_batch: true`가 청크의 끝을 표시합니다. 서버는 `chunk_stats` 타이밍 메시지도 보냅니다.

INIT 메시지

RealtimeVideoGenerationsRequest(type은 리터럴 "init"). 핵심 필드:

필드 (Field) 타입 (Type) 참고 (Notes)
type "init" 필수 리터럴
prompt str 텍스트 프롬프트
first_frame bytes | str SANA-WM 어댑터에 필수(on_init은 없으면 raise. 일반 요청 스키마는 선택으로 정의). 원시 이미지 바이트, 서버 측 경로, 또는 http(s):// URL(다운로드·캐시)
condition_inputs dict 카메라/컨디셔닝 입력(아래)
num_frames int 생성할 총 프레임. 개방형 연속 세션에는 생략 — 어댑터는 num_frames를 설정하지 않고 개방형 실행을 표시(condition_inputs["sana_wm_open_ended"] = True)하며 max_chunks나 클라이언트 연결 해제까지 무기한 균등 청크를 생성. 고정 길이 클립에는 정수 제공
seed int RNG 시드(기본 42)
size str "WIDTHxHEIGHT"; realtime 요청은 지연을 위해 기본값 "832x480". 네이티브 가로 해상도는 "1280x704" 전달
max_chunks int 생성할 총 청크의 선택적 상한
num_inference_steps int SANA-WM(realtime 어댑터)의 기본값 4
guidance_scale float 기본값 1.0
realtime_output_format "raw" | "webp" | "jpeg" 출력용 프레임 인코딩(아래)
realtime_causal_sink_size int 선택적 오버라이드
realtime_causal_kv_cache_num_frames int 선택적 오버라이드

condition_inputs는 다음을 받습니다(모두 선택. action / camera_actions하나만 전달):

키 (Key) 타입 (Type) 의미 (Meaning)
camera_actions list[list[str]] 또는 {mode: "state", transitions: [...]} 프레임별 카메라 동작, 또는 상태 기반 전환
action str Action-DSL 문자열, 예: "w-10,none-5,a-8"(§8 참조)
intrinsics_path str 카메라 intrinsics .npy 파일의 서버 측 경로(np.load로 로드; 형태 (4,), (3,3), 또는 (F,3,3))
intrinsics list 형태 (4,), (3,3), (F,4), 또는 (F,3,3)의 인라인 intrinsics

intrinsics_pathintrinsics를 둘 다 생략하면 SGLang은 첫 프레임 크기에서 유도된 중심 heuristic intrinsics 행렬을 사용합니다. 준비된 궤적과 더 가까운 카메라 패리티가 필요하면 명시적 intrinsics를 전달하세요.

{
  "type": "init",
  "prompt": "beautiful landscape video",
  "first_frame": "<bytes or url>",
  "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"
  }
}

라이브 EVENT 메시지

RealtimeEvent(type: "event"). kind + payload를 사용합니다(선택적 event_id가 응답을 이 이벤트에 연관).

{
  "type": "event",
  "kind": "camera_actions",
  "event_id": 1,
  "payload": [["w"], ["w"], ["a"], []]
}
{
  "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}
    ]
  }
}
{
  "type": "event",
  "kind": "action",
  "event_id": 3,
  "payload": "w-10,none-5,a-8,d-10"
}

서버 프레임 출력

서버는 프레임 배치를 스트리밍합니다. 모든 배치는 type: "frame_batch"단일 msgpack 메시지로 도착합니다 — 아래 헤더 필드 + 인라인 payload 바이트 필드(와이어 type은 항상 "frame_batch". 별도의 header-then-bytes 메시지는 없습니다).

헤더 필드:

필드 (Field) 의미 (Meaning)
type "frame_batch"(항상)
request_id 생성 id
chunk_index 청크 인덱스
content_type application/x-raw-rgb, application/x-raw-rgb-delta-gzip, image/webp, 또는 image/jpeg
num_frames 이 배치의 프레임 수
total_size 페이로드 크기(바이트)(len(payload) — delta-gzip의 경우 압축 크기)
width, height, channels 프레임 지오메트리(channels: 3)
bytes_per_frame 압축하지 않은 프레임당 바이트(width*height*3)
format raw의 경우 rgb24
encoding raw, delta-gzip, webp, 또는 jpeg
delta_reference previous-frame(delta-gzip에 존재)
event_id 조종 event id 반향; INIT 전용 청크의 헤더에서는 생략됨
frame_batch_index, num_frame_batches 청크 내 여러 배치 시퀀스
is_final_frame_batch true가 청크를 종료
{
  "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": "<gzip-compressed bytes>"
}

인코딩. application/x-raw-rgb는 압축하지 않은 RGB24(3 × uint8, bytes_per_frame = width*height*3)입니다. application/x-raw-rgb-delta-gzip은 앞 프레임에 대한 zlib-압축 프레임별 XOR 델타입니다(배치의 각 프레임은 이전 프레임에 대해 XOR됨; 기본 전송). realtime_output_format: "raw"는 압축하지 않은 RGB를 강제하고, "webp" / "jpeg"는 미리보기 인코딩된 프레임을 보냅니다.

delta-gzip은 **프레임별로** 복원해야 합니다. 페이로드를 압축 해제한 다음 각 프레임을 이미 복원된 이전 프레임에 대해 XOR하세요(배치의 첫 프레임은 이전 배치의 마지막 프레임을 참조). `runtime/realtime/video.py`의 `restore_delta_gzip_raw_rgb_payload`를 참조하세요. 아래 `"raw"` 형식은 이를 피합니다.

최소 클라이언트 예시

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. 카메라 Action DSL (Camera Action DSL)

카메라 궤적은 쉼표로 구분된 <keys>-<frames> 세그먼트의 컴팩트 문자열로 설명됩니다. 예: "w-100,wd-50,d-30,none-10". 이것이 init 시 condition_inputs.actionkind: "action" 이벤트가 받는 형식입니다.

파싱 규칙(parse_action_string):

  • 각 세그먼트는 <keys>-<frames>이며 <frames>는 양의 정수여야 합니다.
  • none은 그 구간에 움직임이 없음을 뜻합니다: none-10 = 정적 10 프레임.
  • 키는 대소문자 구분 없음. 결합 키는 동시 적용(wd = 전진 + 우측 스트레이프). 허용 키는 정확히 wasdijkl.
키 (Key) 동작 (Motion)
w / s 전진 / 후진
a / d 좌 / 우 스트레이프
i / k 위 / 아래 보기(pitch)
j / l 좌 / 우 보기(yaw)

포즈 생성(action_string_to_c2w):

  • 이동(w/s/a/d)은 translation_speed(기본 0.04 world-units/frame)로 움직입니다.
  • 회전(i/k pitch, j/l yaw)은 rotation_speed_deg(기본 1.2°/frame)로 돌며 pitch는 ±85°로 클램프됩니다.
  • 스트레이프-요 결합(계수 0.4): d(우) 스트레이프는 요를 오른쪽으로, a(좌)는 왼쪽으로 살짝 돌리므로 wd는 순수 횡보가 아니라 곡선 호를 그립니다.
  • (F+1, 4, 4) camera-to-world 행렬을 만듭니다. realtime 단계는 궤적을 요청 프레임 수로 패딩합니다.

예시: "w-100,wd-50,d-30,none-10" = 전진 100 프레임 → 전진 + 우측 스윕 50 프레임 → 우측 스트레이프 30 프레임 → 정적 10 프레임.

9. 구성 참조 (Configuration Reference)

SANA-WM의 기본값은 세 곳에 있습니다: 요청 시점 샘플링 파라미터, 파이프라인 구성(streaming/refiner 노브), realtime 어댑터(init 시점 오버라이드).

요청 시점 — SanaWMSamplingParams (configs/sample/sana_wm.py)

필드 (Field) 기본값 (Default) 목적 (Purpose)
height 704 출력 높이
width 1280 출력 너비
num_frames 49 총 픽셀 프레임((num_frames - 1) % 8 == 0 충족 필요)
fps 16 출력 프레임 레이트(기본 24 오버라이드)
num_inference_steps 20 Stage-1 스텝 수
guidance_scale 4.5 Dense 경로 CFG 스케일
negative_prompt "" 네거티브 프롬프트
camera_to_world None 인메모리 (T,4,4) c2w extrinsics(action과 상호 배타)
intrinsics None 인메모리 (T,3,3) pinhole intrinsics
action None Action-DSL 문자열(§8 참조)
translation_speed 0.04 W/S/A/D용 world-units/frame
rotation_speed_deg 1.2 I/K/J/L용 degrees/frame
pitch_limit_deg 85.0 Pitch 클램프

generator_device는 기본 SamplingParams에서 상속됩니다(기본 None = 파이프라인/모델 기본 사용). /v1/videos HTTP API에서 카메라 필드는 diffusers_kwargs 안으로 전달됩니다(action / intrinsics, §4–5처럼).

파이프라인 구성 — SanaWMPipelineConfig (configs/pipeline_configs/sana_wm.py)

이들은 서버 시작 노브(--streaming / --refiner-chunked / --num-frame-per-block CLI 플래그 또는 파이프라인 구성 오버라이드로 설정)이며 요청 필드가 아닙니다:

필드 (Field) 기본값 (Default) 목적 (Purpose)
streaming False Chunk-causal forward_long(§5) vs dense one-shot(§4)
refiner_chunked True 청크별 스트리밍 리파이너 vs 전체-클립 dense 리파이너
num_frame_per_block 3 Stage-1 / refiner 청크당 잠재 프레임
num_cached_blocks 2 롤링 KV-캐시 기록 창
denoising_step_list (1000, 960, 889, 727, 0) 4-스텝 스트리밍 self-forcing 타임스텝(0으로 끝나야 함)
streaming_cfg_scale 1.0 증류 streaming 경로의 CFG 스케일(1.0 = off)
sink_size 1 Sink(리파인되지 않은 컨텍스트) 프레임
refiner_block_size 3 리파이너 블록 크기
refiner_kv_max_frames 11 리파이너 슬라이딩 KV 창

Realtime 어댑터 init 오버라이드 — SanaWMRealtimeAdapter

WebSocket init 시 realtime 어댑터는 위의 요청/샘플링 기본값과 다른 SANA-WM 기본값을 채웁니다:

필드 (Field) Realtime 기본값 (Realtime default) 참고 (Note)
size 832x480 Realtime 요청 기본값; 네이티브 가로 출력은 1280x704 전달
num_frames (unset) 생략 → 개방형 연속 세션(§7)
num_inference_steps 4 증류 few-step
guidance_scale 1.0 CFG off
fps 16 네이티브 레이트
`guidance_scale`은 dense 경로(§4)에만 적용됩니다. 증류 streaming 경로는 `streaming_cfg_scale`(기본 `1.0`, 즉 CFG 없음)을 사용하므로 `guidance_scale` 오버라이드가 streaming 단계에서 실수로 CFG를 켜지 않습니다. `denoising_step_list = (1000, 960, 889, 727, 0)`은 공식 4-스텝 스트리밍 스케줄입니다(0으로 끝나야 함).

10. ComfyUI에서 실행 (Run in ComfyUI)

더 알아보기 (Learn more)