[Diffusion] DreamZero world model integration with CFG parallel + OpenPI serving (#2162)

Signed-off-by: Meng <meng_chen99@163.com>
Signed-off-by: Yangshen Deng <yangshen.d@outlook.com>
Signed-off-by: Suli Wang <50828270+wsl2000@users.noreply.github.com>
Co-authored-by: Meng <meng_chen99@163.com>
Co-authored-by: Suli Wang <50828270+wsl2000@users.noreply.github.com>
Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
This commit is contained in:
authored and GitHub committed 2026-06-02 19:50:20 +00:00
1 parent e35cd213d7
commit bdfc77147d
42 files changed
+6610 -19

No files matched your search

@@ -0,0 +1,2 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
@@ -0,0 +1,100 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Action encoder/decoder for DreamZero."""
from __future__ import annotations
import torch
import torch.nn as nn
import torch.nn.functional as F
def swish(x: torch.Tensor) -> torch.Tensor:
"""swish activation: x * sigmoid(x)"""
return x * torch.sigmoid(x)
class SinusoidalPositionalEncoding(nn.Module):
"""Sinusoidal encoding: (B, T) timesteps → (B, T, dim)"""
def __init__(self, embedding_dim: int) -> None:
super().__init__()
self.embedding_dim = embedding_dim
def forward(self, timesteps: torch.Tensor) -> torch.Tensor:
timesteps = timesteps.float()
half_dim = self.embedding_dim // 2
exponent = -torch.arange(half_dim, dtype=torch.float, device=timesteps.device) * (
torch.log(torch.tensor(10000.0)) / half_dim
)
freqs = timesteps.unsqueeze(-1) * exponent.exp()
return torch.cat([torch.sin(freqs), torch.cos(freqs)], dim=-1)
class CategorySpecificLinear(nn.Module):
"""Per-category linear: W[cat_id] @ x + b[cat_id]
Attributes:
W: (num_categories, input_dim, hidden_dim) — note: 0.02 * randn init
b: (num_categories, hidden_dim) — zero init
"""
def __init__(self, num_categories: int, input_dim: int, hidden_dim: int) -> None:
super().__init__()
self.W = nn.Parameter(0.02 * torch.randn(num_categories, input_dim, hidden_dim))
self.b = nn.Parameter(torch.zeros(num_categories, hidden_dim))
def forward(self, x: torch.Tensor, cat_ids: torch.Tensor) -> torch.Tensor:
selected_W = self.W[cat_ids]
selected_b = self.b[cat_ids]
return torch.bmm(x, selected_W) + selected_b.unsqueeze(1)
class CategorySpecificMLP(nn.Module):
"""Two-layer MLP: layer1 (relu) → layer2"""
def __init__(self, num_categories: int, input_dim: int, hidden_dim: int, output_dim: int) -> None:
super().__init__()
self.layer1 = CategorySpecificLinear(num_categories, input_dim, hidden_dim)
self.layer2 = CategorySpecificLinear(num_categories, hidden_dim, output_dim)
def forward(self, x: torch.Tensor, cat_ids: torch.Tensor) -> torch.Tensor:
hidden = F.relu(self.layer1(x, cat_ids))
return self.layer2(hidden, cat_ids)
class MultiEmbodimentActionEncoder(nn.Module):
"""Encode actions with embodiment-specific weights + sinusoidal timestep.
Flow: actions → W1 → concat(a_emb, pos_enc(timesteps)) → W2 (swish) → W3
Args:
action_dim: action vector dimension (e.g. 32)
hidden_size: output/hidden dimension (e.g. 5120 = model dim)
num_embodiments: number of robot types (e.g. 32)
"""
def __init__(self, action_dim: int, hidden_size: int, num_embodiments: int) -> None:
super().__init__()
self.hidden_size = hidden_size
self.W1 = CategorySpecificLinear(num_embodiments, action_dim, hidden_size)
self.W2 = CategorySpecificLinear(num_embodiments, 2 * hidden_size, hidden_size)
self.W3 = CategorySpecificLinear(num_embodiments, hidden_size, hidden_size)
self.pos_encoding = SinusoidalPositionalEncoding(hidden_size)
def forward(self, actions: torch.Tensor, timesteps: torch.Tensor, cat_ids: torch.Tensor) -> torch.Tensor:
"""
Args:
actions: (B, T, action_dim)
timesteps: (B, T) — per-token timestep
cat_ids: (B,) — embodiment id per sample
Returns:
(B, T, hidden_size)
"""
a_emb = self.W1(actions, cat_ids)
tau_emb = self.pos_encoding(timesteps).to(dtype=a_emb.dtype)
x = torch.cat([a_emb, tau_emb], dim=-1)
x = swish(self.W2(x, cat_ids))
x = self.W3(x, cat_ids)
return x
@@ -0,0 +1,954 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""CausalWanModel — 40-layer DiT with causal attention and KV cache.
Key differences from WanTransformer3DModel:
- Causal self-attention (new frames only see history)
- KV cache for streaming inference
- Action/state token support (appended after video tokens)
- Extended RoPE with action/state-specific frequencies
- Inference-only forward with KV cache
"""
from __future__ import annotations
import math
from typing import Any
import torch
import torch.nn as nn
from vllm.distributed import (
get_tensor_model_parallel_rank,
get_tensor_model_parallel_world_size,
get_tp_group,
)
from vllm.model_executor.layers.conv import Conv3dLayer
from vllm.model_executor.layers.linear import (
ColumnParallelLinear,
RowParallelLinear,
)
from vllm.model_executor.utils import set_weight_attrs
from vllm_omni.diffusion.attention.layer import Attention
from vllm_omni.diffusion.models.dreamzero.action_encoder import (
CategorySpecificMLP,
MultiEmbodimentActionEncoder,
)
# ── RoPE utilities ──────────────────────────────────────────────────
def sinusoidal_embedding_1d(dim: int, position: torch.Tensor) -> torch.Tensor:
"""Sinusoidal positional embedding for timesteps."""
if dim % 2 != 0:
raise ValueError(f"dim must be even, got {dim}.")
half = dim // 2
position = position.type(torch.float64)
sinusoid = torch.outer(
position,
torch.pow(10000, -torch.arange(half, dtype=position.dtype, device=position.device).div(half)),
)
x = torch.cat([torch.cos(sinusoid), torch.sin(sinusoid)], dim=1)
return x
def rope_params(max_seq_len: int, dim: int) -> torch.Tensor:
"""Precompute complex-valued RoPE frequencies (polar form).
Returns: complex tensor [max_seq_len, dim // 2]
"""
if dim % 2 != 0:
raise ValueError(f"dim must be even, got {dim}.")
freqs = torch.outer(
torch.arange(max_seq_len),
1.0 / torch.pow(10000, torch.arange(0, dim, 2).to(torch.float64).div(dim)),
)
freqs = torch.polar(torch.ones_like(freqs), freqs)
return freqs
def rope_apply(x: torch.Tensor, freqs: torch.Tensor) -> torch.Tensor:
"""Apply RoPE to x using precomputed complex freqs."""
B, seq_len, n, _ = x.shape
x = torch.view_as_complex(x.to(torch.float64).reshape(B, seq_len, n, -1, 2))
freqs = freqs.unsqueeze(0)
x = torch.view_as_real(x * freqs).flatten(3)
return x
def rope_action_apply(
x: torch.Tensor,
freqs: torch.Tensor,
freqs_action: torch.Tensor,
freqs_state: torch.Tensor,
action_register_length: int | None,
num_action_per_block: int = 32,
num_state_per_block: int = 1,
) -> torch.Tensor:
"""RoPE with action/state frequency tables for multi-step sequences."""
B, seq_len, n, _ = x.shape
x = torch.view_as_complex(x.to(torch.float64).reshape(B, seq_len, n, -1, 2))
if action_register_length is not None:
if num_action_per_block is None:
raise ValueError("num_action_per_block is required when action_register_length is set.")
if num_state_per_block is None:
raise ValueError("num_state_per_block is required when action_register_length is set.")
chunk_size = action_register_length // (num_action_per_block + num_state_per_block)
freqs_1d_action = freqs_action[: chunk_size * num_action_per_block].view(
chunk_size * num_action_per_block, 1, -1
)
freqs_1d_state = freqs_state[: chunk_size * num_state_per_block].view(chunk_size * num_state_per_block, 1, -1)
freqs = torch.cat([freqs, freqs_1d_action, freqs_1d_state], dim=0)
freqs = freqs.unsqueeze(0)
x = torch.view_as_real(x * freqs).flatten(3)
return x
def causal_rope_action_apply(
x: torch.Tensor,
freqs: torch.Tensor,
freqs_action: torch.Tensor,
freqs_state: torch.Tensor,
action_register_length: int | None,
num_action_per_block: int,
num_state_per_block: int,
action_state_index: int,
) -> torch.Tensor:
"""RoPE for single inference step (causal / KV-cache mode)."""
B, seq_len, n, _ = x.shape
x = torch.view_as_complex(x.to(torch.float64).reshape(B, seq_len, n, -1, 2))
if action_register_length is not None:
expected_length = num_action_per_block + num_state_per_block
if action_register_length != expected_length:
raise ValueError(
f"action_register_length must equal num_action_per_block + num_state_per_block "
f"({expected_length}), got {action_register_length}."
)
freqs_action = freqs_action[
action_state_index * num_action_per_block : (action_state_index + 1) * num_action_per_block
]
freqs_state = freqs_state[
action_state_index * num_state_per_block : (action_state_index + 1) * num_state_per_block
]
freqs_1d = torch.cat([freqs_action, freqs_state], dim=0).view(action_register_length, 1, -1)
freqs = torch.cat([freqs, freqs_1d], dim=0)
freqs = freqs.unsqueeze(0)
x = torch.view_as_real(x * freqs).flatten(3)
return x
# ── Normalization ───────────────────────────────────────────────────
class WanLayerNorm(nn.LayerNorm):
"""LayerNorm wrapper used by DreamZero blocks."""
def __init__(self, dim: int, eps: float = 1e-6, elementwise_affine: bool = False) -> None:
super().__init__(dim, eps=eps, elementwise_affine=elementwise_affine)
class DistributedRMSNorm(nn.Module):
"""RMSNorm that computes global RMS across tensor parallel ranks."""
def __init__(self, hidden_size: int, eps: float = 1e-6):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.ones(hidden_size))
set_weight_attrs(self.weight, {"weight_loader": self.weight_loader})
def weight_loader(self, param: torch.Tensor, loaded_weight: torch.Tensor) -> None:
if param.shape == loaded_weight.shape:
param.data.copy_(loaded_weight)
return
tp_size = get_tensor_model_parallel_world_size()
if loaded_weight.shape[0] % tp_size != 0:
raise ValueError(
f"Cannot shard RMSNorm weight of shape {tuple(loaded_weight.shape)} across tp_size={tp_size}."
)
shard_size = loaded_weight.shape[0] // tp_size
start_idx = get_tensor_model_parallel_rank() * shard_size
shard = loaded_weight.narrow(0, start_idx, shard_size)
if param.shape != shard.shape:
raise ValueError(f"RMSNorm shard shape mismatch: param={tuple(param.shape)}, shard={tuple(shard.shape)}.")
param.data.copy_(shard)
def forward(self, x: torch.Tensor) -> torch.Tensor:
tp_size = get_tensor_model_parallel_world_size()
x_float = x.float()
local_sum_sq = x_float.pow(2).sum(dim=-1, keepdim=True)
local_count = x.shape[-1]
if tp_size > 1:
global_sum_sq = local_sum_sq.clone()
torch.distributed.all_reduce(global_sum_sq, group=get_tp_group().device_group)
global_count = local_count * tp_size
else:
global_sum_sq = local_sum_sq
global_count = local_count
mean_sq = global_sum_sq / global_count
return (x_float * torch.rsqrt(mean_sq + self.eps)).type_as(x) * self.weight
# ── Projections ─────────────────────────────────────────────────────
class MLPProj(nn.Module):
"""CLIP feature projection for i2v.
Uses ColumnParallelLinear + RowParallelLinear (Qwen3_VisionMLP pattern).
"""
def __init__(self, in_dim: int, out_dim: int) -> None:
super().__init__()
self.norm1 = nn.LayerNorm(in_dim)
self.fc1 = ColumnParallelLinear(
in_dim,
in_dim,
bias=True,
return_bias=False,
)
self.act = nn.GELU()
self.fc2 = RowParallelLinear(
in_dim,
out_dim,
bias=True,
return_bias=False,
)
self.norm2 = nn.LayerNorm(out_dim)
def forward(self, image_embeds: torch.Tensor) -> torch.Tensor:
x = self.norm1(image_embeds)
x = self.fc1(x)
x = self.act(x)
x = self.fc2(x)
x = self.norm2(x)
return x
# ── Cross-Attention ─────────────────────────────────────────────────
# T2V and I2V cross-attention variants
class WanT2VCrossAttention(nn.Module):
"""Text-to-video cross-attention.
Uses vllm-omni Attention for FlashAttn backend.
"""
def __init__(self, dim: int, num_heads: int, window_size=(-1, -1), qk_norm: bool = True, eps: float = 1e-6) -> None:
super().__init__()
if dim % num_heads != 0:
raise ValueError(f"dim={dim} must be divisible by num_heads={num_heads}.")
self.dim = dim
self.num_heads = num_heads
self.head_dim = dim // num_heads
tp_size = get_tensor_model_parallel_world_size()
if num_heads % tp_size != 0:
raise ValueError(f"num_heads={num_heads} must be divisible by tp_size={tp_size}.")
self.tp_num_heads = num_heads // tp_size
self.tp_inner_dim = self.tp_num_heads * self.head_dim
self.q = ColumnParallelLinear(dim, dim, bias=True, gather_output=False, return_bias=False)
self.k = ColumnParallelLinear(dim, dim, bias=True, gather_output=False, return_bias=False)
self.v = ColumnParallelLinear(dim, dim, bias=True, gather_output=False, return_bias=False)
self.o = RowParallelLinear(dim, dim, bias=True, input_is_parallel=True, return_bias=False)
self.norm_q = DistributedRMSNorm(self.tp_inner_dim, eps=eps) if qk_norm else nn.Identity()
self.norm_k = DistributedRMSNorm(self.tp_inner_dim, eps=eps) if qk_norm else nn.Identity()
self.attn = Attention(
self.tp_num_heads,
self.head_dim,
causal=False,
softmax_scale=self.head_dim**-0.5,
skip_sequence_parallel=True,
)
def forward(
self,
x: torch.Tensor,
context: torch.Tensor,
context_lens: torch.Tensor | None = None,
crossattn_cache: dict | None = None,
) -> torch.Tensor:
del context_lens
n, d = self.tp_num_heads, self.head_dim
q = self.norm_q(self.q(x)).unflatten(2, (n, d))
if crossattn_cache is not None:
if not crossattn_cache["is_init"]:
crossattn_cache["is_init"] = True
k = self.norm_k(self.k(context)).unflatten(2, (n, d))
v = self.v(context).unflatten(2, (n, d))
crossattn_cache["k"] = k
crossattn_cache["v"] = v
else:
k = crossattn_cache["k"]
v = crossattn_cache["v"]
else:
k = self.norm_k(self.k(context)).unflatten(2, (n, d))
v = self.v(context).unflatten(2, (n, d))
x = self.attn(q, k, v)
x = x.flatten(2)
x = self.o(x)
return x
class WanI2VCrossAttention(nn.Module):
"""Image-to-video cross-attention (splits first 257 image tokens).
Uses vllm-omni Attention for FlashAttn backend.
"""
def __init__(self, dim: int, num_heads: int, window_size=(-1, -1), qk_norm: bool = True, eps: float = 1e-6) -> None:
super().__init__()
if dim % num_heads != 0:
raise ValueError(f"dim={dim} must be divisible by num_heads={num_heads}.")
self.dim = dim
self.num_heads = num_heads
self.head_dim = dim // num_heads
tp_size = get_tensor_model_parallel_world_size()
if num_heads % tp_size != 0:
raise ValueError(f"num_heads={num_heads} must be divisible by tp_size={tp_size}.")
self.tp_num_heads = num_heads // tp_size
self.tp_inner_dim = self.tp_num_heads * self.head_dim
self.q = ColumnParallelLinear(dim, dim, bias=True, gather_output=False, return_bias=False)
self.k = ColumnParallelLinear(dim, dim, bias=True, gather_output=False, return_bias=False)
self.v = ColumnParallelLinear(dim, dim, bias=True, gather_output=False, return_bias=False)
self.o = RowParallelLinear(dim, dim, bias=True, input_is_parallel=True, return_bias=False)
self.norm_q = DistributedRMSNorm(self.tp_inner_dim, eps=eps) if qk_norm else nn.Identity()
self.norm_k = DistributedRMSNorm(self.tp_inner_dim, eps=eps) if qk_norm else nn.Identity()
self.k_img = ColumnParallelLinear(dim, dim, bias=True, gather_output=False, return_bias=False)
self.v_img = ColumnParallelLinear(dim, dim, bias=True, gather_output=False, return_bias=False)
self.norm_k_img = DistributedRMSNorm(self.tp_inner_dim, eps=eps) if qk_norm else nn.Identity()
self.attn = Attention(
self.tp_num_heads,
self.head_dim,
causal=False,
softmax_scale=self.head_dim**-0.5,
skip_sequence_parallel=True,
)
def forward(
self,
x: torch.Tensor,
context: torch.Tensor,
context_lens: torch.Tensor | None = None,
crossattn_cache: dict | None = None,
) -> torch.Tensor:
del context_lens
context_img = context[:, :257]
context = context[:, 257:]
n, d = self.tp_num_heads, self.head_dim
q = self.norm_q(self.q(x)).unflatten(2, (n, d))
if crossattn_cache is not None:
if not crossattn_cache["is_init"]:
crossattn_cache["is_init"] = True
k = self.norm_k(self.k(context)).unflatten(2, (n, d))
v = self.v(context).unflatten(2, (n, d))
crossattn_cache["k"] = k
crossattn_cache["v"] = v
else:
k = crossattn_cache["k"]
v = crossattn_cache["v"]
else:
k = self.norm_k(self.k(context)).unflatten(2, (n, d))
v = self.v(context).unflatten(2, (n, d))
x = self.attn(q, k, v)
k_img = self.norm_k_img(self.k_img(context_img)).unflatten(2, (n, d))
v_img = self.v_img(context_img).unflatten(2, (n, d))
img_x = self.attn(q, k_img, v_img)
x = x.flatten(2)
img_x = img_x.flatten(2)
x = x + img_x
x = self.o(x)
return x
WAN_CROSSATTENTION_CLASSES = {
"t2v_cross_attn": WanT2VCrossAttention,
"i2v_cross_attn": WanI2VCrossAttention,
}
# ── Self-Attention with causal masking + KV cache ───────────────────
class CausalWanSelfAttention(nn.Module):
"""Causal self-attention with KV cache + action/state tokens."""
def __init__(
self,
dim: int,
num_heads: int,
frame_seqlen: int,
local_attn_size: int = -1,
sink_size: int = 0,
num_frame_per_block: int = 1,
qk_norm: bool = True,
eps: float = 1e-6,
num_action_per_block: int = 32,
num_state_per_block: int = 1,
) -> None:
super().__init__()
if dim % num_heads != 0:
raise ValueError(f"dim={dim} must be divisible by num_heads={num_heads}.")
self.dim = dim
self.num_heads = num_heads
self.head_dim = dim // num_heads
tp_size = get_tensor_model_parallel_world_size()
if num_heads % tp_size != 0:
raise ValueError(f"num_heads={num_heads} must be divisible by tp_size={tp_size}.")
self.tp_num_heads = num_heads // tp_size
self.tp_inner_dim = self.tp_num_heads * self.head_dim
self.local_attn_size = local_attn_size
self.num_frame_per_block = num_frame_per_block
self.frame_seqlen = frame_seqlen
self.num_action_per_block = num_action_per_block
self.num_state_per_block = num_state_per_block
self.max_attention_size = 21 * frame_seqlen if local_attn_size == -1 else local_attn_size * frame_seqlen
self.q = ColumnParallelLinear(dim, dim, bias=True, gather_output=False, return_bias=False)
self.k = ColumnParallelLinear(dim, dim, bias=True, gather_output=False, return_bias=False)
self.v = ColumnParallelLinear(dim, dim, bias=True, gather_output=False, return_bias=False)
self.o = RowParallelLinear(dim, dim, bias=True, input_is_parallel=True, return_bias=False)
self.norm_q = DistributedRMSNorm(self.tp_inner_dim, eps=eps) if qk_norm else nn.Identity()
self.norm_k = DistributedRMSNorm(self.tp_inner_dim, eps=eps) if qk_norm else nn.Identity()
self.attn = Attention(
self.tp_num_heads,
self.head_dim,
causal=False,
softmax_scale=self.head_dim**-0.5,
skip_sequence_parallel=True,
)
def forward(
self,
x: torch.Tensor,
freqs: torch.Tensor,
freqs_action: torch.Tensor,
freqs_state: torch.Tensor,
action_register_length: int | None,
kv_cache: torch.Tensor | None = None,
current_start_frame: int = 0,
is_tf: bool = True,
) -> tuple[torch.Tensor, torch.Tensor | None]:
"""Inference-only forward (KV cache path)."""
n, d = self.tp_num_heads, self.head_dim
q = self.norm_q(self.q(x)).unflatten(2, (n, d))
k = self.norm_k(self.k(x)).unflatten(2, (n, d))
v = self.v(x).unflatten(2, (n, d))
updated_kv_cache: torch.Tensor | None = None
if kv_cache is None:
raise RuntimeError("Inference only: kv_cache is required.")
action_state_index = max(0, (current_start_frame - 1) // self.num_frame_per_block)
roped_query = causal_rope_action_apply(
q,
freqs,
freqs_action,
freqs_state,
action_register_length,
self.num_action_per_block,
self.num_state_per_block,
action_state_index,
).type_as(v)
roped_key = causal_rope_action_apply(
k,
freqs,
freqs_action,
freqs_state,
action_register_length,
self.num_action_per_block,
self.num_state_per_block,
action_state_index,
).type_as(v)
roped_action_query = None
roped_action_key = None
action_v = None
if action_register_length is not None:
roped_action_query = roped_query[:, -action_register_length:]
roped_query = roped_query[:, :-action_register_length]
roped_action_key = roped_key[:, -action_register_length:]
roped_key = roped_key[:, :-action_register_length]
action_v = v[:, -action_register_length:]
v = v[:, :-action_register_length]
updated_k = kv_cache[0]
updated_v = kv_cache[1]
new_k = torch.cat([updated_k, roped_key], dim=1)
new_v = torch.cat([updated_v, v], dim=1)
new_k = new_k[:, -self.max_attention_size :]
new_v = new_v[:, -self.max_attention_size :]
if action_register_length is not None:
q_cat = torch.cat([roped_query, roped_action_query], dim=1)
k_cat = torch.cat([new_k, roped_action_key], dim=1)
v_cat = torch.cat([new_v, action_v], dim=1)
else:
q_cat = roped_query
k_cat = new_k
v_cat = new_v
x = self.attn(q_cat, k_cat, v_cat)
updated_kv_cache = torch.stack([new_k, new_v], dim=0)
x = x.flatten(2)
x = self.o(x)
return x, updated_kv_cache
# ── Attention Block ─────────────────────────────────────────────────
class CausalWanAttentionBlock(nn.Module):
"""Transformer block: self-attn + cross-attn + FFN with 6-param modulation."""
def __init__(
self,
cross_attn_type: str,
dim: int,
ffn_dim: int,
num_heads: int,
frame_seqlen: int,
local_attn_size: int = -1,
sink_size: int = 0,
num_frame_per_block: int = 1,
qk_norm: bool = True,
cross_attn_norm: bool = False,
eps: float = 1e-6,
num_action_per_block: int = 32,
num_state_per_block: int = 1,
) -> None:
super().__init__()
self.norm1 = WanLayerNorm(dim, eps)
self.self_attn = CausalWanSelfAttention(
dim=dim,
num_heads=num_heads,
frame_seqlen=frame_seqlen,
local_attn_size=local_attn_size,
sink_size=sink_size,
num_frame_per_block=num_frame_per_block,
qk_norm=qk_norm,
eps=eps,
num_action_per_block=num_action_per_block,
num_state_per_block=num_state_per_block,
)
self.norm3 = WanLayerNorm(dim, eps, elementwise_affine=True) if cross_attn_norm else nn.Identity()
self.cross_attn = WAN_CROSSATTENTION_CLASSES[cross_attn_type](dim, num_heads, (-1, -1), qk_norm, eps)
self.norm2 = WanLayerNorm(dim, eps)
self.ffn = nn.Sequential(
ColumnParallelLinear(dim, ffn_dim, bias=True, gather_output=False, return_bias=False),
nn.GELU(approximate="tanh"),
RowParallelLinear(ffn_dim, dim, bias=True, input_is_parallel=True, return_bias=False),
)
self.modulation = nn.Parameter(torch.randn(1, 6, dim) / dim**0.5)
def forward(
self,
x: torch.Tensor,
e: torch.Tensor,
freqs: torch.Tensor,
freqs_action: torch.Tensor,
freqs_state: torch.Tensor,
context: torch.Tensor,
action_register_length: int | None = None,
kv_cache: torch.Tensor | None = None,
crossattn_cache: dict | None = None,
current_start_frame: int = 0,
is_tf: bool = True,
) -> tuple[torch.Tensor, torch.Tensor | None]:
e = (self.modulation.unsqueeze(1) + e).chunk(6, dim=2)
y, updated_kv_cache = self.self_attn(
x=(self.norm1(x) * (1 + e[1].squeeze(2)) + e[0].squeeze(2)),
freqs=freqs,
freqs_action=freqs_action,
freqs_state=freqs_state,
action_register_length=action_register_length,
kv_cache=kv_cache,
is_tf=is_tf,
current_start_frame=current_start_frame,
)
x = x + (y * e[2].squeeze(2))
x = x + self.cross_attn(self.norm3(x), context, crossattn_cache=crossattn_cache)
y = self.ffn(self.norm2(x) * (1 + e[4].squeeze(2)) + e[3].squeeze(2))
x = x + (y * e[5].squeeze(2))
return x, updated_kv_cache
# ── Output Head ─────────────────────────────────────────────────────
class CausalHead(nn.Module):
"""Output norm + linear with 2-param modulation.
Runs once per step (not TP-critical), uses nn.Linear.
"""
def __init__(self, dim: int, out_dim: int, patch_size: tuple, eps: float = 1e-6) -> None:
super().__init__()
self.dim = dim
self.out_dim = out_dim
self.patch_size = patch_size
out_channels = math.prod(patch_size) * out_dim
self.norm = WanLayerNorm(dim, eps)
self.head = nn.Linear(dim, out_channels)
self.modulation = nn.Parameter(torch.randn(1, 2, dim) / dim**0.5)
def forward(self, x: torch.Tensor, e: torch.Tensor) -> torch.Tensor:
"""
Args:
x: [B, L1, C]
e: [B, F, 1, C] (time embedding, unsqueezed)
"""
e = (self.modulation.unsqueeze(1) + e).chunk(2, dim=2)
x = self.head(self.norm(x) * (1 + e[1].squeeze(2)) + e[0].squeeze(2))
return x
# ── Main Model ──────────────────────────────────────────────────────
class CausalWanModel(nn.Module):
"""Causal video diffusion transformer for DreamZero.
Architecture (14B): 40 layers, dim=5120, heads=40, ffn=13824
"""
def __init__(
self,
model_type: str = "t2v",
patch_size: tuple[int, int, int] = (1, 2, 2),
frame_seqlen: int = 220,
text_len: int = 512,
in_dim: int = 16,
dim: int = 2048,
ffn_dim: int = 8192,
freq_dim: int = 256,
text_dim: int = 4096,
out_dim: int = 16,
num_heads: int = 16,
num_layers: int = 32,
max_chunk_size: int = -1,
sink_size: int = 0,
qk_norm: bool = True,
cross_attn_norm: bool = True,
eps: float = 1e-6,
num_frame_per_block: int = 1,
action_dim: int = 32,
num_registers: int = 8,
max_state_dim: int = 64,
max_num_embodiments: int = 32,
hidden_size: int = 1024,
diffusion_model_pretrained_path: str | None = None,
num_action_per_block: int = 32,
num_state_per_block: int = 1,
) -> None:
super().__init__()
if model_type not in ["t2v", "i2v", "ti2v"]:
raise ValueError(f"Unsupported model_type={model_type!r}; expected one of ['t2v', 'i2v', 'ti2v'].")
self.model_type = model_type
self.patch_size = patch_size
self.frame_seqlen = frame_seqlen
self.text_len = text_len
self.dim = dim
self.freq_dim = freq_dim
self.out_dim = out_dim
self.num_heads = num_heads
self.num_layers = num_layers
self.local_attn_size = max_chunk_size * num_frame_per_block + 1 if max_chunk_size != -1 else -1
self.num_frame_per_block = num_frame_per_block
self.action_dim = action_dim
self.num_action_per_block = num_action_per_block
self.num_state_per_block = num_state_per_block
max_num_embodiments_local = 1
self.state_encoder = CategorySpecificMLP(
num_categories=max_num_embodiments_local,
input_dim=max_state_dim,
hidden_dim=hidden_size,
output_dim=dim,
)
self.action_encoder = MultiEmbodimentActionEncoder(
action_dim=action_dim,
hidden_size=dim,
num_embodiments=max_num_embodiments_local,
)
self.action_decoder = CategorySpecificMLP(
num_categories=max_num_embodiments_local,
input_dim=dim,
hidden_dim=hidden_size,
output_dim=action_dim,
)
# Disable the Conv3d GEMM rewrite for patch embedding.
self.patch_embedding = Conv3dLayer(
in_dim,
dim,
kernel_size=patch_size,
stride=patch_size,
)
self.patch_embedding.enable_linear = False
self.text_embedding = nn.Sequential(
nn.Linear(text_dim, dim),
nn.GELU(approximate="tanh"),
nn.Linear(dim, dim),
)
self.time_embedding = nn.Sequential(
nn.Linear(freq_dim, dim),
nn.SiLU(),
nn.Linear(dim, dim),
)
self.time_projection = nn.Sequential(
nn.SiLU(),
nn.Linear(dim, dim * 6),
)
cross_attn_type = "t2v_cross_attn" if model_type == "t2v" else "i2v_cross_attn"
self.blocks = nn.ModuleList(
[
CausalWanAttentionBlock(
cross_attn_type,
dim,
ffn_dim,
num_heads,
frame_seqlen,
self.local_attn_size,
sink_size,
num_frame_per_block,
qk_norm,
cross_attn_norm,
eps,
num_action_per_block,
num_state_per_block,
)
for _ in range(num_layers)
]
)
self.head = CausalHead(dim, out_dim, patch_size, eps)
if dim % num_heads != 0:
raise ValueError(f"dim={dim} must be divisible by num_heads={num_heads}.")
if (dim // num_heads) % 2 != 0:
raise ValueError(f"dim // num_heads must be even, got {dim // num_heads}.")
d = dim // num_heads
self.freqs_action = rope_params(1024 * 10, d)
self.freqs_state = rope_params(1024, d)
self.freqs = [
rope_params(1024, d - 4 * (d // 6)),
rope_params(1024, 2 * (d // 6)),
rope_params(1024, 2 * (d // 6)),
]
if model_type == "i2v":
self.img_emb = MLPProj(1280, dim)
self.init_weights()
def init_weights(self) -> None:
"""Initialize parameters."""
def _init_linear_like(module: nn.Module) -> None:
if isinstance(module, nn.Linear):
nn.init.xavier_uniform_(module.weight)
if module.bias is not None:
nn.init.zeros_(module.bias)
return
if isinstance(module, (ColumnParallelLinear, RowParallelLinear)):
fan_in = module.input_size
fan_out = module.output_size
bound = math.sqrt(6.0 / float(fan_in + fan_out))
nn.init.uniform_(module.weight, -bound, bound)
if module.bias is not None:
nn.init.zeros_(module.bias)
for module in self.modules():
_init_linear_like(module)
nn.init.xavier_uniform_(self.patch_embedding.weight.flatten(1))
if self.patch_embedding.bias is not None:
fan_in = self.patch_embedding.in_channels * math.prod(self.patch_embedding.kernel_size)
bound = 1 / math.sqrt(fan_in)
nn.init.uniform_(self.patch_embedding.bias, -bound, bound)
for module in self.text_embedding.modules():
if isinstance(module, nn.Linear):
nn.init.normal_(module.weight, std=0.02)
for module in self.time_embedding.modules():
if isinstance(module, nn.Linear):
nn.init.normal_(module.weight, std=0.02)
nn.init.zeros_(self.head.head.weight)
def _create_freqs(self, grid_size: torch.Tensor, start_frame: int) -> torch.Tensor:
"""Create 3D RoPE frequency tensor."""
device = self.patch_embedding.weight.device
if any(freq.device != device for freq in self.freqs):
self.freqs = [freq.to(device) for freq in self.freqs]
if self.freqs_action.device != device:
self.freqs_action = self.freqs_action.to(device)
if self.freqs_state.device != device:
self.freqs_state = self.freqs_state.to(device)
f, h, w = grid_size.tolist()
freqs = torch.cat(
[
self.freqs[0][start_frame : start_frame + f].view(f, 1, 1, -1).expand(f, h, w, -1),
self.freqs[1][:h].view(1, h, 1, -1).expand(f, h, w, -1),
self.freqs[2][:w].view(1, 1, w, -1).expand(f, h, w, -1),
],
dim=-1,
).reshape(f * h * w, 1, -1)
return freqs
def unpatchify(self, x: torch.Tensor, grid_size: torch.Tensor) -> torch.Tensor:
"""Reconstruct video from patch embeddings."""
B = x.shape[0]
c = self.out_dim
grid_size = grid_size.tolist()
expected_seq_len = math.prod(grid_size)
if x.shape[1] != expected_seq_len:
raise ValueError(f"x sequence length must equal product(grid_size)={expected_seq_len}, got {x.shape[1]}.")
x = x.view(B, *grid_size, *self.patch_size, c)
x = torch.einsum("bfhwpqrc->bcfphqwr", x)
x = x.reshape(B, c, *[i * j for i, j in zip(grid_size, self.patch_size)])
return x
def _forward_blocks(
self,
x: torch.Tensor,
seq_len: int,
freqs: torch.Tensor,
timestep: torch.Tensor,
context: torch.Tensor,
clip_feature: torch.Tensor | None,
embodiment_id: torch.Tensor | None,
action: torch.Tensor | None,
timestep_action: torch.Tensor | None,
state: torch.Tensor | None,
kv_cache: list[torch.Tensor],
current_start_frame: int,
) -> tuple[torch.Tensor, torch.Tensor | None, list[torch.Tensor]]:
x = x.flatten(start_dim=2).transpose(1, 2)
B = x.shape[0]
F_t = timestep.shape[1]
if action is not None:
# Current DreamZero checkpoints have one local action/state adapter.
# Global embodiment IDs are used by transforms and normalization.
adapter_category_id = torch.zeros(B, dtype=torch.long, device=x.device)
action_features = self.action_encoder(action, timestep_action, adapter_category_id)
state_features = self.state_encoder(state, adapter_category_id)
action_register = torch.cat([action_features, state_features], dim=1)
action_length = action_features.shape[1]
action_register_length = action_register.shape[1]
x = torch.cat([x, action_register], dim=1)
else:
action_length = 0
action_register_length = None
timestep = timestep.unsqueeze(-1).expand(B, F_t, seq_len // F_t).reshape(B, -1)
if action is not None:
if timestep_action is None or state is None:
raise RuntimeError("timestep_action and state are required when action is provided.")
state_features_t = self.state_encoder(state, adapter_category_id)
stride = timestep_action.shape[1] // state_features_t.shape[1]
timestep_state = timestep_action[:, ::stride]
timestep = torch.cat([timestep, timestep_action, timestep_state], dim=1)
e = self.time_embedding(sinusoidal_embedding_1d(self.freq_dim, timestep.flatten()).type_as(x))
e = e.unflatten(dim=0, sizes=(B, -1))
e0 = self.time_projection(e)
e0 = e0.unflatten(dim=2, sizes=(6, self.dim))
context = self.text_embedding(context)
if clip_feature is not None:
clip_embedding = self.img_emb(clip_feature)
context = torch.cat([clip_embedding, context], dim=1)
updated_kv_caches: list[torch.Tensor] = []
for block_index, block in enumerate(self.blocks):
x, updated_kv_cache = block(
x=x,
e=e0,
freqs=freqs,
freqs_action=self.freqs_action,
freqs_state=self.freqs_state,
context=context,
action_register_length=action_register_length,
kv_cache=kv_cache[block_index] if kv_cache else None,
current_start_frame=current_start_frame,
)
updated_kv_caches.append(updated_kv_cache)
if action is not None:
action_noise_pred = x[:, seq_len : seq_len + action_length]
action_noise_pred = self.action_decoder(action_noise_pred, adapter_category_id)
else:
action_noise_pred = None
x_video = x[:, :seq_len]
e_video = e[:, :seq_len]
x_video = self.head(x_video, e_video.unsqueeze(2))
return x_video, action_noise_pred, updated_kv_caches
def _forward_inference(
self,
x: torch.Tensor,
timestep: torch.Tensor,
context: torch.Tensor,
seq_len: int,
kv_cache: list[torch.Tensor],
crossattn_cache: list[torch.Tensor],
current_start_frame: int,
y: torch.Tensor | None = None,
clip_feature: torch.Tensor | None = None,
action: torch.Tensor | None = None,
timestep_action: torch.Tensor | None = None,
state: torch.Tensor | None = None,
embodiment_id: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor | None, list[torch.Tensor]]:
if self.model_type == "i2v":
if clip_feature is None or y is None:
raise RuntimeError("clip_feature and y are required for i2v inference.")
if context.shape[1] != self.text_len:
raise ValueError(f"context length must be {self.text_len}, got {context.shape[1]}.")
if y is not None:
x = torch.cat([x, y.to(dtype=x.dtype)], dim=1)
x = self.patch_embedding(x)
grid_size = torch.tensor(x.shape[2:], dtype=torch.long)
freqs = self._create_freqs(grid_size, current_start_frame)
x_video, action_noise_pred, updated_kv_caches = self._forward_blocks(
x=x,
seq_len=seq_len,
freqs=freqs,
timestep=timestep,
context=context,
clip_feature=clip_feature,
embodiment_id=embodiment_id,
action=action,
timestep_action=timestep_action,
state=state,
kv_cache=kv_cache,
current_start_frame=current_start_frame,
)
x_video = x_video.clone()
if action_noise_pred is not None:
action_noise_pred = action_noise_pred.clone()
video_noise_pred = self.unpatchify(x_video, grid_size)
return video_noise_pred, action_noise_pred, updated_kv_caches
def forward(self, *args: Any, **kwargs: Any):
"""Inference only. Requires kv_cache."""
return self._forward_inference(*args, **kwargs)
@@ -0,0 +1,243 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""DreamZero image encoder.
Only the visual tower used by DreamZero I2V inference is ported here. The
checkpoint keys under `action_head.image_encoder.*` load via simple prefix
stripping.
"""
from __future__ import annotations
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.transforms as T
class DreamZeroLayerNorm(nn.LayerNorm):
"""LayerNorm that preserves the input dtype."""
def forward(self, x: torch.Tensor) -> torch.Tensor:
return super().forward(x).type_as(x)
class DreamZeroVisionSelfAttention(nn.Module):
"""Self-attention for the vision tower."""
def __init__(
self,
dim: int,
num_heads: int,
proj_dropout: float = 0.0,
) -> None:
super().__init__()
if dim % num_heads != 0:
raise ValueError(f"dim={dim} must be divisible by num_heads={num_heads}.")
self.dim = dim
self.num_heads = num_heads
self.head_dim = dim // num_heads
self.proj_dropout = proj_dropout
self.to_qkv = nn.Linear(dim, dim * 3)
self.proj = nn.Linear(dim, dim)
def forward(self, x: torch.Tensor) -> torch.Tensor:
batch_size, seq_len, _ = x.shape
q, k, v = self.to_qkv(x).chunk(3, dim=-1)
q = q.view(batch_size, seq_len, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
k = k.view(batch_size, seq_len, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
v = v.view(batch_size, seq_len, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
x = F.scaled_dot_product_attention(q, k, v)
x = x.permute(0, 2, 1, 3).reshape(batch_size, seq_len, self.dim)
x = self.proj(x)
return F.dropout(x, self.proj_dropout, self.training)
class DreamZeroVisionAttentionBlock(nn.Module):
"""Attention block for the vision tower."""
def __init__(
self,
dim: int,
mlp_ratio: float,
num_heads: int,
post_norm: bool = False,
activation: str = "gelu",
proj_dropout: float = 0.0,
norm_eps: float = 1e-5,
) -> None:
super().__init__()
if activation != "gelu":
raise ValueError(f"DreamZero image encoder uses GELU; got activation={activation!r}.")
self.post_norm = post_norm
hidden_dim = int(dim * mlp_ratio)
self.norm1 = DreamZeroLayerNorm(dim, eps=norm_eps)
self.attn = DreamZeroVisionSelfAttention(
dim,
num_heads,
proj_dropout=proj_dropout,
)
self.norm2 = DreamZeroLayerNorm(dim, eps=norm_eps)
self.mlp = nn.Sequential(
nn.Linear(dim, hidden_dim),
nn.GELU(),
nn.Linear(hidden_dim, dim),
nn.Dropout(proj_dropout),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
if self.post_norm:
x = x + self.norm1(self.attn(x))
x = x + self.norm2(self.mlp(x))
else:
x = x + self.attn(self.norm1(x))
x = x + self.mlp(self.norm2(x))
return x
class DreamZeroVisionTransformer(nn.Module):
"""Vision transformer used by the image encoder."""
def __init__(
self,
image_size: int = 224,
patch_size: int = 14,
dim: int = 1280,
mlp_ratio: float = 4.0,
out_dim: int = 1024,
num_heads: int = 16,
num_layers: int = 32,
pool_type: str = "token",
pre_norm: bool = True,
post_norm: bool = False,
activation: str = "gelu",
proj_dropout: float = 0.0,
embedding_dropout: float = 0.0,
norm_eps: float = 1e-5,
) -> None:
super().__init__()
if pool_type != "token":
raise ValueError(f"DreamZero image encoder only supports pool_type='token', got {pool_type!r}.")
self.image_size = image_size
self.patch_size = patch_size
self.num_patches = (image_size // patch_size) ** 2
self.dim = dim
self.num_heads = num_heads
self.num_layers = num_layers
self.pool_type = pool_type
gain = 1.0 / math.sqrt(dim)
self.patch_embedding = nn.Conv2d(
3,
dim,
kernel_size=patch_size,
stride=patch_size,
bias=not pre_norm,
)
self.cls_embedding = nn.Parameter(gain * torch.randn(1, 1, dim))
self.pos_embedding = nn.Parameter(
gain * torch.randn(1, self.num_patches + 1, dim),
)
self.dropout = nn.Dropout(embedding_dropout)
self.pre_norm = DreamZeroLayerNorm(dim, eps=norm_eps) if pre_norm else None
self.transformer = nn.Sequential(
*[
DreamZeroVisionAttentionBlock(
dim=dim,
mlp_ratio=mlp_ratio,
num_heads=num_heads,
post_norm=post_norm,
activation=activation,
proj_dropout=proj_dropout,
norm_eps=norm_eps,
)
for _ in range(num_layers)
]
)
self.post_norm = DreamZeroLayerNorm(dim, eps=norm_eps)
self.head = nn.Parameter(gain * torch.randn(dim, out_dim))
def forward(self, x: torch.Tensor, use_31_block: bool = False) -> torch.Tensor:
batch_size = x.shape[0]
x = self.patch_embedding(x).flatten(2).permute(0, 2, 1)
x = torch.cat(
[
self.cls_embedding.expand(batch_size, -1, -1).to(dtype=x.dtype, device=x.device),
x,
],
dim=1,
)
x = self.dropout(x + self.pos_embedding.to(dtype=x.dtype, device=x.device))
if self.pre_norm is not None:
x = self.pre_norm(x)
if use_31_block:
return self.transformer[:-1](x)
return self.transformer(x)
class _DreamZeroCLIPContainer(nn.Module):
"""Container matching checkpoint names under `model.visual.*`."""
def __init__(self) -> None:
super().__init__()
self.log_scale = nn.Parameter(torch.ones(()))
self.visual = DreamZeroVisionTransformer(
image_size=224,
patch_size=14,
dim=1280,
mlp_ratio=4.0,
out_dim=1024,
num_heads=16,
num_layers=32,
pool_type="token",
pre_norm=True,
post_norm=False,
activation="gelu",
proj_dropout=0.0,
embedding_dropout=0.0,
norm_eps=1e-5,
)
class DreamZeroImageEncoder(nn.Module):
"""Image encoder wrapper."""
def __init__(self) -> None:
super().__init__()
self.model = _DreamZeroCLIPContainer()
# returns a composed transform whose last stage is CLIP normalization.
self.transforms = T.Compose(
[
T.Normalize(
mean=[0.48145466, 0.4578275, 0.40821073],
std=[0.26862954, 0.26130258, 0.27577711],
),
]
)
def encode_image(self, videos: torch.Tensor) -> torch.Tensor:
"""Encode images for I2V conditioning."""
size = (self.model.visual.image_size,) * 2
videos = torch.cat(
[
F.interpolate(
frame_batch,
size=size,
mode="bicubic",
align_corners=False,
)
for frame_batch in videos
]
)
videos = self.transforms.transforms[-1](videos.mul_(0.5).add_(0.5))
param_dtype = next(iter(self.model.visual.parameters())).dtype
videos = videos.to(dtype=param_dtype)
out = self.model.visual(videos, use_31_block=True)
return out.clone()
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,161 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""DreamZero pipeline persistent state."""
from __future__ import annotations
import logging
from collections import deque
import numpy as np
import torch
logger = logging.getLogger(__name__)
# Number of frames per chunk for subsequent calls (first call uses 1)
FRAMES_PER_CHUNK = 4
class DreamZeroState:
"""Pipeline persistent state across forward() calls.
Lifecycle:
- Created once in DreamZeroPipeline.__init__()
- Mutated every forward() call (frame append, KV cache grow)
- reset() on new session / language change / local_attn_size exceeded
"""
def __init__(self) -> None:
self.reset()
# ------------------------------------------------------------------
# Frame accumulation (single stitched buffer)
# Transform outputs stitched single frame per call.
# We accumulate here to build multi-frame video for AR inference.
# ------------------------------------------------------------------
def accumulate_frames(self, stitched: np.ndarray) -> np.ndarray:
"""Accumulate stitched frames and return multi-frame video.
Args:
stitched: (H, W, C) single frame or (T, H, W, C) multi-frame,
already stitched by transform.
Returns:
(T, H, W, C) ndarray. T=1 for first call, T=FRAMES_PER_CHUNK(4) after.
"""
if stitched.ndim == 3:
self.stitched_buffer.append(stitched)
elif stitched.ndim == 4:
self.stitched_buffer.extend(list(stitched))
else:
raise ValueError(f"Expected 3D or 4D stitched, got {stitched.ndim}D")
num_frames = 1 if self.call_count == 0 else FRAMES_PER_CHUNK
buffer_frames = list(self.stitched_buffer)
if len(buffer_frames) >= num_frames:
frames = buffer_frames[-num_frames:]
else:
frames = buffer_frames
while len(frames) < num_frames:
frames.insert(0, buffer_frames[0])
self.call_count += 1
return np.stack(frames, axis=0) # (T, H, W, C)
# ------------------------------------------------------------------
# Reset / should_reset
# ------------------------------------------------------------------
def reset(self) -> None:
"""Clear all state."""
self.stitched_buffer: deque[np.ndarray] = deque(maxlen=FRAMES_PER_CHUNK)
self.call_count: int = 0
# KV cache once robot-policy diffusion supports that integration.
self.kv_cache: list[torch.Tensor] | None = None
self.kv_cache_neg: list[torch.Tensor] | None = None
self.crossattn_cache: list[dict[str, bool | torch.Tensor | None]] | None = None
self.crossattn_cache_neg: list[dict[str, bool | torch.Tensor | None]] | None = None
self.current_start_frame: int = 0
self.clip_feas: torch.Tensor | None = None
self.ys: torch.Tensor | None = None
self.language: torch.Tensor | None = None
def should_reset(self, text_tokens: torch.Tensor | None, num_video_frames: int, local_attn_size: int) -> bool:
"""Determine if state should be reset before this forward()."""
if self.language is None:
logger.info("language is None, resetting")
return True
if text_tokens is not None and not torch.equal(self.language, text_tokens):
logger.info("language changed, resetting")
return True
# NOTE: after accumulate_frames, num_video_frames is the accumulated T
# (1 for first call, 4 for subsequent). Only reset on true single-frame
# which happens when the stitched_buffer was cleared externally.
if num_video_frames == 1 and self.call_count > 1:
logger.info("single frame input after first call, resetting")
return True
if local_attn_size != -1 and self.current_start_frame >= local_attn_size:
logger.info(
"current_start_frame %d >= local_attn_size %d, resetting", self.current_start_frame, local_attn_size
)
return True
return False
# ------------------------------------------------------------------
# KV cache management
# ------------------------------------------------------------------
def create_kv_caches(
self,
batch_size: int,
dtype: torch.dtype,
device: torch.device,
num_layers: int,
num_heads: int,
head_dim: int,
) -> None:
"""Initialize empty KV caches and cross-attention caches."""
self.kv_cache = [
torch.zeros(2, batch_size, 0, num_heads, head_dim, dtype=dtype, device=device) for _ in range(num_layers)
]
self.kv_cache_neg = [
torch.zeros(2, batch_size, 0, num_heads, head_dim, dtype=dtype, device=device) for _ in range(num_layers)
]
self.crossattn_cache = [{"is_init": False, "k": None, "v": None} for _ in range(num_layers)]
self.crossattn_cache_neg = [{"is_init": False, "k": None, "v": None} for _ in range(num_layers)]
def update_kv_cache(
self,
layer_index: int,
updated_kv: torch.Tensor,
is_negative: bool = False,
) -> None:
"""Update a single layer's KV cache after prefill."""
cache = self.kv_cache_neg if is_negative else self.kv_cache
if cache is None:
raise RuntimeError("KV caches not initialized, call create_kv_caches first.")
cache[layer_index] = updated_kv.clone()
def get_kv_caches(self, is_negative: bool = False) -> list[torch.Tensor]:
"""Get KV caches for the specified branch."""
cache = self.kv_cache_neg if is_negative else self.kv_cache
if cache is None:
raise RuntimeError("KV caches not initialized.")
return cache
def get_crossattn_caches(self, is_negative: bool = False) -> list[dict[str, bool | torch.Tensor | None]]:
"""Get cross-attention caches for the specified branch."""
cache = self.crossattn_cache_neg if is_negative else self.crossattn_cache
if cache is None:
raise RuntimeError("Cross-attn caches not initialized.")
return cache
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
import importlib
from vllm.logger import init_logger
from vllm_omni.diffusion.models.dreamzero.transform.base import TRANSFORMS
logger = init_logger(__name__)
DEFAULT_EMBODIMENT = "roboarena"
_BUILTIN_TRANSFORM_MODULES = (
"vllm_omni.diffusion.models.dreamzero.transform.droid",
"vllm_omni.diffusion.models.dreamzero.transform.roboarena",
)
def ensure_transforms_loaded() -> None:
"""Import DreamZero transform modules and verify registration."""
for module_name in _BUILTIN_TRANSFORM_MODULES:
try:
importlib.import_module(module_name)
except Exception as exc:
logger.exception("Failed to import DreamZero transform module %s", module_name)
raise RuntimeError(f"Failed to import DreamZero transform module '{module_name}'.") from exc
if DEFAULT_EMBODIMENT not in TRANSFORMS:
raise RuntimeError(f"Built-in DreamZero transform '{DEFAULT_EMBODIMENT}' is not registered after import.")
@@ -0,0 +1,126 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Base transform interface for DreamZero robot policy serving.
Transforms handle dataset-specific concerns ONLY:
- Observation key mapping
- Multi-view stitching (embodiment-specific layout)
- Language template wrapping (embodiment-specific)
- Raw state extraction (dataset-specific keys)
- Output action slicing (to actual action_dim)
Model-specific concerns belong in the pipeline:
- Tokenization (pipeline owns tokenizer)
- State padding (pipeline knows MAX_STATE_DIM)
- Negative prompt (pipeline owns the string)
- Noise generation, encoding, decoding
Flow:
raw obs (dataset format)
→ DreamZeroPipeline selects transform by embodiment
→ unified dict (stitched video, templated prompt str, raw state)
→ tokenize, pad, encode, denoise
→ transform_action_output()
→ ndarray (N, action_dim)
"""
from __future__ import annotations
from typing import Any
import numpy as np
class RobotPolicyTransform:
"""Base class for dataset-specific observation transforms.
Subclasses MUST define:
IMAGE_KEY_MAP: dict — dataset obs keys → unified keys
EMBODIMENT_NAME: str — embodiment identity (pipeline maps to numeric ID)
ACTION_DIM: int — actual action dimensions (for output slicing)
Subclasses MUST override:
_stitch_views() — multi-view → single stitched image
_language_template() — prompt → embodiment-aware template
_extract_raw_state() — obs → raw state ndarray
"""
IMAGE_KEY_MAP: dict[str, str]
EMBODIMENT_NAME: str
ACTION_DIM: int
def transform_input(self, obs: dict) -> dict:
"""Dataset-specific transform: key map → stitch → template → state."""
# 1. Map image keys → unified keys
images: dict[str, np.ndarray] = {}
for src_key, dst_key in self.IMAGE_KEY_MAP.items():
if src_key in obs:
images[dst_key] = np.asarray(obs[src_key])
# 2. Multi-view stitching
stitched = self._stitch_views(images)
# 3. Language template (string only, pipeline tokenizes)
prompt = obs.get("prompt", "")
templated_prompt = self._language_template(prompt)
# 4. Raw state extraction (pipeline pads)
raw_state = self._extract_raw_state(obs)
# 5. Build unified output
unified: dict[str, Any] = {
"images": stitched, # ndarray (T, H_out, W_out, 3)
"prompt": templated_prompt, # str (templated, not tokenized)
"state": raw_state, # ndarray (state_dim,) — pipeline pads
"embodiment_name": self.EMBODIMENT_NAME,
}
if "session_id" in obs:
unified["session_id"] = obs["session_id"]
return unified
def transform_action_output(self, actions: Any) -> np.ndarray:
"""Adapt model action output to this transform's action dimensions."""
actions = np.asarray(actions, dtype=np.float32)
# Handle any remaining batch dims: squeeze to 2D (horizon, dim)
while actions.ndim > 2:
actions = actions[0]
# Slice padded dim to actual ACTION_DIM
if actions.ndim == 2 and actions.shape[-1] > self.ACTION_DIM:
actions = actions[:, : self.ACTION_DIM]
return actions
# ------------------------------------------------------------------
# Subclass MUST override
# ------------------------------------------------------------------
def _stitch_views(self, images: dict[str, np.ndarray]) -> np.ndarray:
"""Stitch camera views into single image.
Input: unified key → ndarray (H,W,3) or (T,H,W,3).
Output: ndarray (T, H_out, W_out, 3).
"""
raise NotImplementedError
def _language_template(self, prompt: str) -> str:
"""Wrap prompt in embodiment-specific template string."""
raise NotImplementedError
def _extract_raw_state(self, obs: dict) -> np.ndarray:
"""Extract raw state vector from obs.
Returns: ndarray (state_dim,) float64. Pipeline handles padding.
"""
raise NotImplementedError
# Transform registry — keyed by embodiment/dataset name
TRANSFORMS: dict[str, RobotPolicyTransform] = {}
def register_transform(name: str, transform: RobotPolicyTransform) -> None:
TRANSFORMS[name] = transform
def get_transform(name: str) -> RobotPolicyTransform:
if name not in TRANSFORMS:
raise KeyError(f"Unknown transform '{name}'. Available: {list(TRANSFORMS.keys())}")
return TRANSFORMS[name]
@@ -0,0 +1,138 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""DROID dataset transform.
DROID uses 1-indexed exterior cameras, 3 views total (OXE_DROID embodiment).
Stitching layout (same as RoboArena — both are OXE_DROID):
┌─────────────────────────┐
│ wrist (2x width) │ ← pixel-repeat along width
├────────────┬────────────┤
│ left ext │ right ext │
└────────────┴────────────┘
"""
from __future__ import annotations
import numpy as np
import torch
import torchvision.transforms.v2 as T
from vllm_omni.diffusion.models.dreamzero.transform.base import (
RobotPolicyTransform,
register_transform,
)
class DroidTransform(RobotPolicyTransform):
"""Transform for DROID dataset (OXE_DROID embodiment).
DROID observation keys (1-indexed exterior cameras):
observation/exterior_image_1_left → left exterior
observation/exterior_image_2_left → right exterior
observation/wrist_image_left → wrist
"""
IMAGE_KEY_MAP = {
"observation/exterior_image_1_left": "images/exterior_0",
"observation/exterior_image_2_left": "images/exterior_1",
"observation/wrist_image_left": "images/wrist",
}
EMBODIMENT_NAME = "oxe_droid"
ACTION_DIM = 8 # 7 joint + 1 gripper
_VIDEO_CROP_SCALE = 0.95
_VIDEO_RESIZE_HW = (176, 320)
@classmethod
def _preprocess_view(cls, arr: np.ndarray) -> np.ndarray:
"""Apply per-view crop and resize before stitching."""
frames = torch.from_numpy(arr).to(torch.float32).permute(0, 3, 1, 2) / 255.0
crop_h = int(arr.shape[1] * cls._VIDEO_CROP_SCALE)
crop_w = int(arr.shape[2] * cls._VIDEO_CROP_SCALE)
frames = T.CenterCrop((crop_h, crop_w))(frames)
frames = T.Resize(
cls._VIDEO_RESIZE_HW,
interpolation=T.InterpolationMode.BILINEAR,
antialias=True,
)(frames)
return (frames.permute(0, 2, 3, 1) * 255.0).to(torch.uint8).cpu().numpy()
def _stitch_views(self, images: dict[str, np.ndarray]) -> np.ndarray:
"""OXE_DROID 2x2 stitching: wrist top (2x wide), exteriors bottom."""
left_ext = images.get("images/exterior_0")
right_ext = images.get("images/exterior_1")
wrist = images.get("images/wrist")
# Ensure 4D: (T, H, W, C)
def ensure_4d(arr: np.ndarray | None) -> np.ndarray | None:
if arr is None:
return None
return arr if arr.ndim == 4 else arr[np.newaxis]
left_ext = ensure_4d(left_ext)
right_ext = ensure_4d(right_ext)
wrist = ensure_4d(wrist)
# Determine shape from first available view.
ref = next((v for v in [wrist, left_ext, right_ext] if v is not None), None)
if ref is None:
return np.zeros((1, 352, 640, 3), dtype=np.uint8)
# Apply per-view crop + resize before stitching.
def maybe_preprocess(arr: np.ndarray | None) -> np.ndarray | None:
if arr is None:
return None
return self._preprocess_view(arr)
left_ext = maybe_preprocess(left_ext)
right_ext = maybe_preprocess(right_ext)
wrist = maybe_preprocess(wrist)
ref = next((v for v in [wrist, left_ext, right_ext] if v is not None), None)
if ref is None:
raise RuntimeError("Expected at least one DROID camera view after preprocessing.")
t, h, w, c = ref.shape
out = np.zeros((t, 2 * h, 2 * w, c), dtype=ref.dtype) # (T, 2H, 2W, C)
# Top row: wrist repeated 2x along width.
if wrist is not None:
wrist_wide = np.repeat(wrist, 2, axis=2) # (T, H, 2W, C)
out[:, :h, :] = wrist_wide
# Bottom row: left exterior | right exterior.
if left_ext is not None:
out[:, h:, :w] = left_ext
if right_ext is not None:
out[:, h:, w:] = right_ext
return out
def _language_template(self, prompt: str) -> str:
"""Expand the language prompt for the OXE_DROID multi-view format."""
prompt = (prompt or "Perform the default behavior.").strip()
prompt_lower = prompt.lower()
return (
"A multi-view video shows that a robot "
+ prompt_lower
+ " The video is split into three views: The top view shows the "
+ "camera view from the robot's wrist, the bottom-left view shows "
+ "the camera view from the left exterior camera, and the "
+ "bottom-right view shows the camera view from the right exterior "
+ "camera. During training, one of the two bottom exterior views "
+ "may be a black screen (dropped view). The robot "
+ prompt_lower
)
def _extract_raw_state(self, obs: dict) -> np.ndarray:
"""OXE_DROID state: 7 joint + 1 gripper = 8 dims."""
parts = []
if "observation/joint_position" in obs:
parts.append(np.asarray(obs["observation/joint_position"], dtype=np.float64).flatten())
if "observation/gripper_position" in obs:
parts.append(np.asarray(obs["observation/gripper_position"], dtype=np.float64).flatten())
if parts:
return np.concatenate(parts)
return np.zeros(8, dtype=np.float64)
register_transform("droid", DroidTransform())
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""RoboArena dataset transform.
RoboArena uses 0-indexed exterior cameras, 3 views total (OXE_DROID embodiment).
Same stitching layout as DROID — both map to OXE_DROID in DreamZero.
"""
from __future__ import annotations
from vllm_omni.diffusion.models.dreamzero.transform.base import (
register_transform,
)
from vllm_omni.diffusion.models.dreamzero.transform.droid import (
DroidTransform,
)
class RoboArenaTransform(DroidTransform):
"""Transform for RoboArena dataset.
Same embodiment as DROID (OXE_DROID), same stitching and template.
Only difference: 0-indexed exterior camera keys.
RoboArena observation keys (0-indexed):
observation/exterior_image_0_left → left exterior
observation/exterior_image_1_left → right exterior
observation/wrist_image_left → wrist
"""
IMAGE_KEY_MAP = {
"observation/exterior_image_0_left": "images/exterior_0",
"observation/exterior_image_1_left": "images/exterior_1",
"observation/wrist_image_left": "images/wrist",
}
register_transform("roboarena", RoboArenaTransform())
@@ -0,0 +1,29 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""DreamZero model constants shared by the pipeline."""
DEFAULT_NUM_INFERENCE_STEPS = 16
DEFAULT_CFG_SCALE = 5.0
DEFAULT_SIGMA_SHIFT = 5.0
DEFAULT_SEED = 1140
DEFAULT_NEGATIVE_PROMPT = (
"Vibrant colors, overexposed, static, blurry details, text, subtitles, "
"style, artwork, painting, image, still, grayscale, dull, worst quality, "
"low quality, JPEG artifacts, ugly, mutilated, extra fingers, bad hands, "
"bad face, deformed, disfigured, mutated limbs, fused fingers, stagnant "
"image, cluttered background, three legs, many people in the background, "
"walking backwards."
)
DEFAULT_EMBODIMENT_NAME_TO_ID = {
"oxe_droid": 17,
"agibot": 26,
"gr1_unified": 24,
"xdof": 22,
"yam": 32,
"mecka_hands": 27,
"lapa": 27,
"dream": 31,
}
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from __future__ import annotations
import torch
class DreamZeroVideoExportWorkerExtension:
"""DreamZero worker RPCs used by offline example video export."""
def decode_video_latents_to_uint8(self, video_latents: torch.Tensor) -> torch.Tensor:
if self.model_runner is None or self.model_runner.pipeline is None:
raise RuntimeError("DreamZero pipeline is not initialized on this worker.")
with torch.inference_mode():
decoded = self.model_runner.pipeline.decode_video_latents(video_latents)
decoded = decoded.squeeze(0).permute(1, 2, 3, 0).contiguous()
decoded = decoded.clamp(-1, 1) * 0.5 + 0.5
decoded = (decoded * 255.0).round().to(torch.uint8).cpu()
return decoded