# SANA-WM

SANA-WM es un modelo de mundo de imagen a video controlado por cámara, construido sobre SANA. Dada una imagen de primer fotograma, un prompt de texto y una trayectoria de cámara (ya sea una pose `c2w` explícita o una cadena de acciones WASD/IJKL), genera un video cuyo movimiento sigue la trayectoria de cámara solicitada.

La inferencia se ejecuta en dos etapas:

1. **Etapa 1 — SANA-WM DiT.** Un DiT bidireccional de 1.6B parámetros con atención lineal GDN-Triton y una rama de control de cámara UCPE. El muestreo utiliza un planificador Euler de flow-matching estilo LTX con pasos de tiempo por token; el primer marco latente es la ancla de condicionamiento.
2. **Etapa 2 — Refinador LTX-2 (opcional).** Un pipeline de refinamiento Euler bidireccional con sink independiente ([SanaWMLTX2Refiner](/docs/diffusers/main/es/api/pipelines/sana_wm#diffusers.SanaWMLTX2Refiner)) que envuelve [SanaWMLTX2RefinerTransformer3DModel](/docs/diffusers/main/es/api/models/sana_wm_refiner_transformer3d#diffusers.SanaWMLTX2RefinerTransformer3DModel) + `LTX2TextConnectors` y un codificador de texto Gemma-3, ejecutado durante 3 pasos de sigma destilados.

Ambas etapas decodifican a través del VAE [AutoencoderKLLTX2Video](/docs/diffusers/main/es/api/models/autoencoderkl_ltx_2#diffusers.AutoencoderKLLTX2Video).

Modelos disponibles:

| Modelo | dtype recomendado |
|:-----:|:-----------------:|
| [`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 se entrena a una resolución fija de 704×1280. El dtype recomendado para los pesos del transformer es `torch.bfloat16`; mantenga el codificador de texto en `torch.bfloat16` y el VAE en `torch.float32` para obtener la mejor precisión numérica. El pipeline espera las intrínsecas de la cámara `[fx, fy, cx, cy]` en coordenadas de píxeles de la *imagen original*; la transformación de redimensionado y recorte centrado se aplica internamente.

## Inferencia

```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
)
```

Pasa `action=None` y proporciona tus propias poses `c2w` (un array numpy de `(F, 4, 4)`) para controlar la trayectoria de la cámara de forma explícita. Omita `output_type="latent"` para obtener el video directamente de la etapa 1 y saltarse el refinador.

### Etapa 2 — el refinador LTX-2

[SanaWMLTX2Refiner](/docs/diffusers/main/es/api/pipelines/sana_wm#diffusers.SanaWMLTX2Refiner) se distribuye como su propio repositorio, de la misma manera que SDXL separa el modelo base y el refiner. Pasa el VAE del pipeline base para que los pesos se compartan en lugar de cargarse dos veces:

```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)
```

Sin un `vae`, el refiner devuelve latentes refinados en lugar de video, lo cual es útil si deseas decodificarlos tú mismo.

> [!TIP]
> Habilite el offloading en **ambos** pipelines, o libere la etapa 1 antes de la etapa 2 (`pipe.transformer.to("cpu")`). Las dos etapas juntas ocupan unos 45 GB en `torch.bfloat16`, y mantener ambas residentes en una sola tarjeta de 80 GB deja demasiado poco espacio para las activaciones. Tenga en cuenta también que la etapa 2 respeta `torch_dtype`: cargar el refiner en `torch.float32` duplica aproximadamente su memoria y cambia ligeramente la salida.

Si no tienes las intrínsecas de la cámara, un [bloque modular](../../modular_diffusers/overview) alojado puede estimarlas a partir de un solo fotograma. Se encuentra fuera de `diffusers` porque incorpora Pi3X, una dependencia adicional y un segundo checkpoint, por lo que no se descarga nada hasta que lo solicites:

```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")
```

## Convertir el punto de control publicado

Si tienes la versión de origen de SANA-WM (no la instantánea de diffusers preconvertida), ejecuta el script de conversión una vez:

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

Esto escribe dos directorios: el pipeline base en `--dst` y el refiner de la etapa 2 junto a él en `./SANA-WM_bidirectional-diffusers-refiner` (se puede sobrescribir con `--dst-refiner`). Luego, carga cada uno desde su ruta local como de costumbre.

## Componentes

- `tokenizer` — `GemmaTokenizerFast`
- `text_encoder` — Gemma-2 (devuelve los estados ocultos del decodificador)
- `vae` — [AutoencoderKLLTX2Video](/docs/diffusers/main/es/api/models/autoencoderkl_ltx_2#diffusers.AutoencoderKLLTX2Video) (LTX-2, espacial ×32 / temporal ×8)
- `transformer` — [SanaWMTransformer3DModel](/docs/diffusers/main/es/api/models/sana_wm_transformer3d#diffusers.SanaWMTransformer3DModel), DiT bidireccional de 1.600 millones de parámetros
- `scheduler` — [FlowMatchEulerDiscreteScheduler](/docs/diffusers/main/es/api/schedulers/flow_match_euler_discrete#diffusers.FlowMatchEulerDiscreteScheduler)

El refiner de la etapa 2 es un repositorio independiente con su propio `transformer` ([SanaWMLTX2RefinerTransformer3DModel](/docs/diffusers/main/es/api/models/sana_wm_refiner_transformer3d#diffusers.SanaWMLTX2RefinerTransformer3DModel)), `connectors` (`LTX2TextConnectors`), `tokenizer`, `text_encoder` de Gemma-3 y `scheduler`. No tiene un `vae` propio: pase el de la pipeline base.

## SanaWMPipeline[[diffusers.SanaWMPipeline]]

#### diffusers.SanaWMPipeline[[diffusers.SanaWMPipeline]]

```python
diffusers.SanaWMPipeline(tokenizer: GemmaTokenizer | GemmaTokenizerFast, text_encoder: Gemma2PreTrainedModel, vae: AutoencoderKLLTX2Video, transformer: SanaWMTransformer3DModel, scheduler: FlowMatchEulerDiscreteScheduler)
```

[Fuente](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/sana_wm/pipeline_sana_wm.py#L143)

**Parámetros:**

tokenizer ([*GemmaTokenizer*] o [*GemmaTokenizerFast*]) : El tokenizador de Gemma-2.

text_encoder ([*Gemma2PreTrainedModel*]) : El codificador de texto de Gemma-2.

vae ([*AutoencoderKLLTX2Video*]) : El VAE de LTX-2.

transformer ([*SanaWMTransformer3DModel*]) : El DiT bidireccional SANA-WM de 1600M.

scheduler ([*FlowMatchEulerDiscreteScheduler*]) : Programador Euler de flow-matching (pasos de tiempo por token estilo LTX). latentes directamente.

Pipeline de imagen a video controlado por cámara de SANA-WM.

Genera un video a partir de una imagen de primer fotograma, un prompt de texto y una trayectoria de cámara (poses `c2w` explícitas o una cadena de acciones WASD/IJKL). Utiliza el SANA DiT bidireccional de 1600M para el muestreo de la etapa 1 y el refinador Euler sink-bidireccional de LTX-2 para el pulido de la etapa 2; ambos se decodifican a través del VAE de LTX-2.

#### __call__[[diffusers.SanaWMPipeline.__call__]]

```python
__call__(image: PIL.Image.Image | str | Path, prompt: str, c2w: np.ndarray | None = None, action: str | None = None, intrinsics: np.ndarray | list[float] | None = None, height: int = 704, width: int = 1280, num_frames: int = 161, fps: int = 16, num_inference_steps: int = 60, guidance_scale: float = 5.0, negative_prompt: str = '', generator: torch.Generator | list[torch.Generator] | None = None, prompt_embeds: torch.Tensor | None = None, prompt_attention_mask: torch.Tensor | None = None, negative_prompt_embeds: torch.Tensor | None = None, negative_prompt_attention_mask: torch.Tensor | None = None, max_sequence_length: int = 300, chi_prompt: list[str] | None = None, output_type: Literal['np', 'pil', 'pt', 'latent'] = 'np', callback_on_step_end: Callable[[DiffusionPipeline, int, int, dict], dict] | None = None, callback_on_step_end_tensor_inputs: list[str] = ['latents'], return_dict: bool = True)
```

[Fuente](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/sana_wm/pipeline_sana_wm.py#L457)

**Parámetros:**

image (*PIL.Image.Image* o *str*) : Imagen del primer fotograma (PIL o ruta).

prompt (*str*) : Prompt de texto.

c2w (*np.ndarray*, *opcional*) : poses de cámara al mundo `(F, 4, 4)`. Mutuamente excluyente con *action*.

action (*str*, *opcional*) : Cadena de Action-DSL, p. ej. `"w-80,jw-40,w-40"`. Mutuamente excluyente con *c2w*.

intrinsics (*np.ndarray* o *list[float]*) : `[fx, fy, cx, cy]` en coordenadas de píxeles de la **imagen original**. El pipeline aplica la transformación de redimensionado y recorte internamente.

height (*int*, valor predeterminado 704) : Altura del cuadro de salida (fija para el modelo público).

width (*int*, por defecto 1280) : Ancho del cuadro de salida (fijo para el modelo público).

num_frames (*int*, por defecto 161) : Número de fotogramas objetivo; se ajusta a `8k+1` (restricción del VAE de LTX-2).

fps (*int*, por defecto 16) : Tasa de fotogramas de salida (también se envía al refiner).

num_inference_steps (*int*, por defecto 60) : Número de pasos de muestreo de DiT de la etapa 1.

guidance_scale (*float*, por defecto 5.0) : Escala de guía sin clasificador.

negative_prompt (*str*, por defecto "") : Prompt negativo opcional.

generator (*torch.Generator* o *list[torch.Generator]*, *opcional*) : Uno o más generadores de torch para hacer que la muestreo de ruido sea determinista. Si es *None*, la muestreo no es determinista.

prompt_embeds (*torch.Tensor*, *opcional*) : Incrustaciones de texto precalculadas, para omitir el codificador de texto. Debe pasarse junto con *prompt_attention_mask*.

prompt_attention_mask (*torch.Tensor*, *opcional*) : Máscara de atención para *prompt_embeds*.

negative_prompt_embeds (*torch.Tensor*, *opcional*) : Embeddings de texto negativo precalculados. Debe pasarse junto con *negative_prompt_attention_mask*.

negative_prompt_attention_mask (*torch.Tensor*, *opcional*) : Máscara de atención para *negative_prompt_embeds*.

max_sequence_length (*int*, por defecto 300) : Número máximo de tokens del prompt.

chi_prompt (*list[str]*, *opcional*) : Anula el prefijo de chi-prompt (el valor predeterminado refleja la versión pública).

output_type (*"np"*, *"pil"*, *"pt"* o *"latent"*, por defecto *"np"*) : Formato de salida.

callback_on_step_end (*Callable*, *opcional*) : Se llama al final de cada paso de desruido como *callback_on_step_end(self, step, timestep, callback_kwargs)*, donde *callback_kwargs* contiene los tensores nombrados en *callback_on_step_end_tensor_inputs*. Los tensores en el diccionario devuelto reemplazan los de la pipeline.

callback_on_step_end_tensor_inputs (*list[str]*, por defecto *["latents"]*) : Qué tensores pasar a *callback_on_step_end*; debe ser un subconjunto de *._callback_tensor_inputs*.

return_dict (*bool*, por defecto True) : Devuelve [*SanaWMPipelineOutput*] en lugar de una tupla.

**Devuelve:**

[*SanaWMPipelineOutput*] con *.frames* de forma `(T, H, W, 3)`, un `np.ndarray` de tipo float en `[0, 1]` para *output_type="np"*, una lista de `PIL.Image.Image` de longitud `T` para *"pil"*, un tensor `(T, 3, H, W)` para *"pt"*, o el tensor latente en bruto para *"latent"*.

Genera un video controlado por cámara de SANA-WM.

Ejemplos:
```py
>>> import torch
>>> from PIL import Image
>>> from diffusers import SanaWMPipeline

>>> pipe = SanaWMPipeline.from_pretrained(
...     "Efficient-Large-Model/SANA-WM_bidirectional-diffusers", torch_dtype=torch.bfloat16
... ).to("cuda")

>>> output = pipe(
...     image=Image.open("input.png").convert("RGB"),
...     prompt="A car driving across a vast desert plain at golden hour.",
...     action="w-80,jw-40,w-40",
...     intrinsics=[800.0, 800.0, 845.0, 464.0],  # fx, fy, cx, cy in original-image pixels
...     num_inference_steps=60,
... )
>>> # output.frames is (T, H, W, 3) float np.ndarray in [0, 1] (diffusers convention).
```

#### check_inputs[[diffusers.SanaWMPipeline.check_inputs]]

```python
check_inputs(c2w: np.ndarray | None, action: str | None, intrinsics: np.ndarray | list[float] | None, callback_on_step_end_tensor_inputs: list[str] | None = None)
```

[Fuente](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/sana_wm/pipeline_sana_wm.py#L335)

Valida las entradas de `__call__`. Lanza una excepción si la entrada es incorrecta y no devuelve nada.

#### encode_prompt[[diffusers.SanaWMPipeline.encode_prompt]]

```python
encode_prompt(prompt: str, negative_prompt: str = '', device: torch.device | None = None, max_sequence_length: int = 300, chi_prompt: list[str] | None = None, prompt_embeds: torch.Tensor | None = None, prompt_attention_mask: torch.Tensor | None = None, negative_prompt_embeds: torch.Tensor | None = None, negative_prompt_attention_mask: torch.Tensor | None = None)
```

[Fuente](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/sana_wm/pipeline_sana_wm.py#L204)

**Devuelve:**

`(cond, cond_mask, neg, neg_mask)` donde `cond` y `neg` son estados ocultos de Gemma con forma `(1, 1, L, D)` y las máscaras tienen forma `(1, L)`.

Codifica el prompt y el prompt negativo a través de Gemma-2.

Reproduce el truco de prefijo de chi-prompt de SANA: el prompt chi se antepone al prompt del usuario y luego una rebanada `select_index = [0, -L+1, ..., -1]` toma el token BOS junto con los últimos `max_sequence_length - 1` tokens.

#### prepare_camera_trajectory[[diffusers.SanaWMPipeline.prepare_camera_trajectory]]

```python
prepare_camera_trajectory(c2w: np.ndarray | None, action: str | None, intrinsics: np.ndarray | list[float], num_frames: int)
```

[Fuente](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/sana_wm/pipeline_sana_wm.py#L365)

Normaliza las entradas de la cámara a `(c2w_(F, 4, 4), intrinsics_(F, 4))`.

También ajusta `num_frames` al formato `8k+1` compatible con el VAE y recorta ambos arreglos para que coincidan. La imagen recortada y las intrínsecas reescaladas se proporcionan más adelante, una vez que se conoce la resolución objetivo.

#### prepare_latents[[diffusers.SanaWMPipeline.prepare_latents]]

```python
prepare_latents(first_latent: torch.Tensor, num_frames: int, height: int, width: int, dtype: torch.dtype, device: torch.device, generator: torch.Generator)
```

[Fuente](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/sana_wm/pipeline_sana_wm.py#L403)

Muestree los latentes iniciales y fije el primer fotograma como ancla de condicionamiento.

Devuelve `(latents, condition_mask)`, donde `condition_mask` tiene unos en los tokens del primer fotograma (se mantienen limpios durante todo el muestreo) y ceros en el resto.

## SanaWMLTX2Refiner[[diffusers.SanaWMLTX2Refiner]]

El refinador de la etapa 2 de LTX-2 es un [DiffusionPipeline](/docs/diffusers/main/es/api/pipelines/overview#diffusers.DiffusionPipeline) independiente que recibe los latentes de la etapa 1. Si se le proporciona un `vae` (el de la pipeline base, para compartir los pesos), decodificará a video; si no, devuelve los latentes refinados.

#### diffusers.SanaWMLTX2Refiner[[diffusers.SanaWMLTX2Refiner]]

```python
diffusers.SanaWMLTX2Refiner(transformer: SanaWMLTX2RefinerTransformer3DModel, connectors: LTX2TextConnectors, tokenizer: GemmaTokenizer | GemmaTokenizerFast, text_encoder: Gemma3ForConditionalGeneration, scheduler: FlowMatchEulerDiscreteScheduler, vae: AutoencoderKLLTX2Video | None = None, text_max_sequence_length: int = 1024)
```

[Fuente](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/sana_wm/refiner.py#L47)

**Parámetros:**

transformer ([*SanaWMLTX2RefinerTransformer3DModel*]) : El DiT de video LTX-2 con caché KV de ventana deslizante causal por bloques.

connectors ([*LTX2TextConnectors*]) : conectores de texto de LTX-2.

tokenizer : Tokenizador de Gemma-3.

text_encoder : codificador de texto de Gemma-3.

scheduler ([*FlowMatchEulerDiscreteScheduler*]) : Programador Euler de flow-matching. Se construye con `shift=1.0` para que las sigmas destiladas pasen sin modificaciones.

vae ([*AutoencoderKLLTX2Video*], *opcional*) : El mismo VAE utilizado por [*SanaWMPipeline*]; pase *vae=pipe.vae* para compartir los pesos. Si se proporciona, el refiner decodifica a video; de lo contrario, devuelve latentes refinados.

text_max_sequence_length (*int*, valor predeterminado 1024) : Número máximo de tokens pasados al tokenizador de Gemma-3.

LTX-2 sink-bidirectional Euler refiner — SANA-WM etapa 2, como pipeline independiente.

Envuelve los componentes de LTX-2 (transformador refinador + conectores de texto + codificador de texto Gemma-3 + tokenizador) junto con un [*FlowMatchEulerDiscreteScheduler*] que lleva la programación de sigma destilada y realiza los pasos de Euler. Está registrado como un componente opcional de [*SanaWMPipeline*] y también puede usarse de forma independiente para refinar los latentes de la etapa 1.

#### __call__[[diffusers.SanaWMLTX2Refiner.__call__]]

```python
__call__(sana_latent: torch.Tensor, prompt: str, fps: float, sink_size: int = 1, generator: torch.Generator | None = None, block_size: int = 3, kv_max_frames: int = 11, sigmas: tuple[float, ...] = (0.909375, 0.725, 0.421875, 0.0), output_type: str = 'np')
```

[Fuente](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/sana_wm/refiner.py#L107)

**Parámetros:**

sana_latent : `(B, C, F, H, W)` latente de la etapa 1.

prompt : prompt de texto.

fps : tasa de fotogramas del video (controla la escalada temporal de LTX-2 RoPE).

sink_size : cuántos fotogramas crudos `z_sana` iniciales anclar como sumidero de atención (canónico: 1).

generator : torch.Generator para el ruido del extremo FM. Por defecto, se utiliza un generador con semilla 42 para que los resultados sean reproducibles desde el inicio.

block_size : fotogramas latentes por bloque de AR (canónico: 3).

kv_max_frames : número máximo de cuadros de contexto + activos conservados en la ventana deslizante (canónico: 11 = 1 sink + 10 recientes).

sigmas : programa de Euler descendente que termina en 0.0 (destilado canónico de 3 pasos: `(0.909375, 0.725, 0.421875, 0.0)`). Se pasa a `self.scheduler` (sin el 0.0 final, que el programador añade por sí mismo).

output_type : *"latent"* devuelve los latentes refinados. Cualquier otro valor se decodifica a través de *self.vae* y se postprocesa a ese tipo (*"np"*, *"pt"*, *"pil"*); sin un *vae*, se devuelven los latentes de todos modos.

**Devuelve:** `*torch.Tensor*`

Latentes de VAE refinados de forma `(B, C, F, H, W)`: los primeros `sink_size` cuadros conservan los latentes de sink de la etapa 1 sin cambios, y el resto contiene la salida refinada.

Ejecuta el refinador LTX-2 y devuelve los latentes VAE refinados.

Utiliza la receta AR de causalidad por bloques con la que se entrenó el modelo (`block_size=3`, `kv_max_frames=11`): una ventana deslizante de K/V de `[source_sink + recent_history + active_block]` se alimenta al transformer un bloque a la vez, de modo que el cómputo por bloque está acotado y el coste total de refinamiento escala linealmente con la longitud del video.

## SanaWMPipelineOutput[[diffusers.SanaWMPipelineOutput]]

#### diffusers.SanaWMPipelineOutput[[diffusers.SanaWMPipelineOutput]]

```python
diffusers.SanaWMPipelineOutput(frames: typing.Union[torch.Tensor, numpy.ndarray, list[list[PIL.Image.Image]]], c2w: numpy.ndarray | None = None, latent: typing.Optional[torch.Tensor] = None)
```

[Fuente](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/sana_wm/pipeline_output.py#L25)

**Parámetros:**

frames (*torch.Tensor*, *np.ndarray* o *list[PIL.Image.Image]*) : Video generado: un `np.ndarray` de forma `(T, H, W, 3)` en `[0, 1]` para `output_type="np"`, un tensor `(T, 3, H, W)` para `"pt"`, una lista de `T` ``PIL.Image` para `"pil"`, o el tensor latente `(B, C, T_lat, H_lat, W_lat)` para `"latent"`.

c2w (*np.ndarray*) : Posas de cámara al mundo `(T, 4, 4)` alineadas con `frames` (el refiner descarta el marco de ancla de sink; este array se realinea en consecuencia cuando se ejecutó el refiner).

latent (*torch.Tensor*, opcional) : Tensor latente en el espacio VAE de LTX-2, con forma `(B, C, T_lat, H_lat, W_lat)`. Se devuelve cuando `output_type="latent"``.

Clase de salida para el pipeline de imagen a video de SANA-WM.

