Files
vllm-omni/vllm_omni/diffusion/models/nextstep_1_1/modeling_nextstep.py
T

449 lines
17 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# Adapted from NextStep-1.1 (https://huggingface.co/stepfun-ai/NextStep-1.1)
# Original: models/nextstep_model.py — local version with TP-aware layers.
from __future__ import annotations
import json
from collections.abc import Iterable
import numpy as np
import torch
import torch.nn as nn
from transformers.cache_utils import Cache, DynamicCache, StaticCache
from transformers.modeling_attn_mask_utils import AttentionMaskConverter
from transformers.modeling_outputs import BaseModelOutputWithPast
from transformers.models.llama.configuration_llama import LlamaConfig
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
from vllm_omni.diffusion.models.nextstep_1_1.modeling_nextstep_heads import (
FlowMatchingHead,
)
from vllm_omni.diffusion.models.nextstep_1_1.modeling_nextstep_llama import (
LlamaDecoderLayer,
LlamaRMSNorm,
LlamaRotaryEmbedding,
)
# ---------------------------------------------------------------------------
# Positional embedding utilities (inlined from remote utils/model_utils.py)
# ---------------------------------------------------------------------------
def _get_1d_sincos_pos_embed_from_grid(embed_dim: int, pos: np.ndarray) -> np.ndarray:
assert embed_dim % 2 == 0
omega = np.arange(embed_dim // 2, dtype=np.float64)
omega /= embed_dim / 2.0
omega = 1.0 / 10000**omega
pos = pos.reshape(-1)
out = np.einsum("m,d->md", pos, omega)
return np.concatenate([np.sin(out), np.cos(out)], axis=1)
def _get_2d_sincos_pos_embed_from_grid(embed_dim: int, grid: np.ndarray) -> np.ndarray:
assert embed_dim % 2 == 0
emb_h = _get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0])
emb_w = _get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1])
return np.concatenate([emb_h, emb_w], axis=1)
def get_2d_sincos_pos_embed(embed_dim: int, grid_size: int) -> np.ndarray:
grid_h = np.arange(grid_size, dtype=np.float32)
grid_w = np.arange(grid_size, dtype=np.float32)
grid = np.meshgrid(grid_w, grid_h)
grid = np.stack(grid, axis=0).reshape(2, 1, grid_size, grid_size)
return _get_2d_sincos_pos_embed_from_grid(embed_dim, grid)
# ---------------------------------------------------------------------------
# NextStepConfig — extends LlamaConfig with NextStep-specific fields.
# This mirrors the remote models/config.py.
# ---------------------------------------------------------------------------
class NextStepConfig(LlamaConfig):
model_type = "nextstep"
def __init__(
self,
vae_name_or_path: str | None = None,
latent_size: int = 32,
latent_patch_size: int = 2,
latent_channels: int = 16,
boi: int | None = None,
eoi: int | None = None,
image_placeholder_id: int | None = None,
pad_token_id_added: int | None = None,
lm_loss_weight: float = 0.01,
im_loss_weight: float = 1.0,
fm_head_dim: int = 1536,
fm_head_layers: int = 12,
fm_head_batch_mul: int = 4,
o_attention_bias: bool | None = None,
**kwargs,
):
super().__init__(**kwargs)
self.vae_name_or_path = vae_name_or_path
self.latent_size = latent_size
self.latent_patch_size = latent_patch_size
self.latent_channels = latent_channels
self.boi = boi
self.eoi = eoi
self.image_placeholder_id = image_placeholder_id
self.pad_token_id_added = pad_token_id_added
self.lm_loss_weight = lm_loss_weight
self.im_loss_weight = im_loss_weight
self.fm_head_dim = fm_head_dim
self.fm_head_layers = fm_head_layers
self.fm_head_batch_mul = fm_head_batch_mul
self.o_attention_bias = self.attention_bias if o_attention_bias is None else o_attention_bias
@classmethod
def from_json(cls, path: str) -> NextStepConfig:
with open(path) as f:
data = json.load(f)
# Remove keys that are not constructor parameters (accept extra keys)
# LlamaConfig.__init__ uses **kwargs to absorb extras.
return cls(**data)
# ---------------------------------------------------------------------------
# NextStepModel — main model class
# ---------------------------------------------------------------------------
class NextStepModel(nn.Module):
_layerwise_offload_blocks_attrs = ["layers"]
def __init__(self, config: NextStepConfig):
super().__init__()
self.config = config
self.padding_idx = config.pad_token_id
self.vocab_size = config.vocab_size
self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
self.layers = nn.ModuleList(
[LlamaDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
)
self.norm = LlamaRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
self.rotary_emb = LlamaRotaryEmbedding(config=config)
# lm_head is part of the checkpoint but not used during image generation
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
# Image projectors.
token_dim = config.latent_channels * config.latent_patch_size**2
self.image_in_projector = nn.Linear(token_dim, config.hidden_size)
self.image_out_projector = nn.Linear(config.hidden_size, config.hidden_size)
# Flow-matching head (no TP — tiny network)
self.image_head = FlowMatchingHead(
input_dim=token_dim,
cond_dim=config.hidden_size,
dim=config.fm_head_dim,
layers=config.fm_head_layers,
)
# Optional generation position embeddings
if getattr(config, "use_gen_pos_embed", False):
self._init_gen_pos_embed()
# ------------------------------------------------------------------
# Generation positional embeddings
# ------------------------------------------------------------------
def _init_gen_pos_embed(self):
self.register_buffer(
"gen_pos_embed",
torch.from_numpy(get_2d_sincos_pos_embed(self.config.hidden_size, self.config.base_image_grid_size))
.float()
.unsqueeze(0),
)
def gen_pos_embed_with_ar(self, h: int, w: int) -> torch.Tensor:
bsz, hw, dim = self.gen_pos_embed.shape
side = int(hw**0.5)
gen_pos_embed = self.gen_pos_embed.reshape(bsz, side, side, dim)
gen_pos_embed = gen_pos_embed[:, :h, :w, :]
return gen_pos_embed.reshape(bsz, -1, dim)
# ------------------------------------------------------------------
# Patchify / Unpatchify
# ------------------------------------------------------------------
def patchify(self, img: torch.Tensor) -> torch.Tensor:
bsz, c, h, w = img.shape
p = self.config.latent_patch_size
h_, w_ = h // p, w // p
img = img.reshape(bsz, c, h_, p, w_, p)
img = torch.einsum("nchpwq->nhwcpq", img)
return img.reshape(bsz, h_ * w_, c * p**2)
def unpatchify(
self,
x: torch.Tensor,
h: int | None = None,
w: int | None = None,
) -> torch.Tensor:
bsz = x.shape[0]
p = self.config.latent_patch_size
c = self.config.latent_channels
if h is None and w is None:
h_ = w_ = int(x.shape[1] ** 0.5)
else:
h_, w_ = h, w
assert h_ * w_ == x.shape[1], f"Invalid sequence length {x.shape[1]}."
x = x.reshape(bsz, h_, w_, c, p, p)
x = torch.einsum("nhwcpq->nchpwq", x)
return x.reshape(bsz, c, h_ * p, w_ * p)
# ------------------------------------------------------------------
# Input embedding preparation
# ------------------------------------------------------------------
def prepare_inputs_embeds(
self,
input_ids: torch.LongTensor,
latents: torch.FloatTensor | None = None,
) -> torch.Tensor:
if latents is None:
return self.embed_tokens(input_ids)
bs, seq_length = input_ids.shape
inputs_embeds = torch.zeros(
(bs, seq_length, self.config.hidden_size),
device=self.embed_tokens.weight.device,
dtype=self.embed_tokens.weight.dtype,
)
im_indices = input_ids == self.config.image_placeholder_id
lm_indices = ~im_indices
if isinstance(latents, list):
tokens = torch.cat([self.patchify(latent) for latent in latents], dim=1)
else:
tokens = self.patchify(latents)
image_embeds = self.image_in_projector(tokens)
image_embeds = image_embeds.view(-1, self.config.hidden_size)
token_embeds = self.embed_tokens(input_ids[lm_indices])
inputs_embeds[im_indices] = image_embeds.to(inputs_embeds.dtype)
inputs_embeds[lm_indices] = token_embeds
return inputs_embeds
# ------------------------------------------------------------------
# Causal mask utilities
# ------------------------------------------------------------------
def _update_causal_mask(
self,
attention_mask: torch.Tensor,
input_tensor: torch.Tensor,
cache_position: torch.Tensor,
past_key_values: Cache,
output_attentions: bool,
) -> torch.Tensor | None:
# For SDPA we build the 4D mask; flash_attention_2 uses None.
attn_impl = getattr(self.config, "_attn_implementation", "sdpa")
if attn_impl == "flash_attention_2":
if attention_mask is not None and (attention_mask == 0.0).any():
return attention_mask
return None
past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
using_static_cache = isinstance(past_key_values, StaticCache)
if attn_impl == "sdpa" and not using_static_cache and not output_attentions:
if AttentionMaskConverter._ignore_causal_mask_sdpa(
attention_mask,
inputs_embeds=input_tensor,
past_key_values_length=past_seen_tokens,
is_training=self.training,
):
return None
dtype, device = input_tensor.dtype, input_tensor.device
sequence_length = input_tensor.shape[1]
if using_static_cache:
target_length = past_key_values.get_max_cache_shape()
else:
target_length = (
attention_mask.shape[-1]
if isinstance(attention_mask, torch.Tensor)
else past_seen_tokens + sequence_length + 1
)
causal_mask = self._prepare_4d_causal_attention_mask_with_cache_position(
attention_mask,
sequence_length=sequence_length,
target_length=target_length,
dtype=dtype,
device=device,
cache_position=cache_position,
batch_size=input_tensor.shape[0],
)
if (
attn_impl == "sdpa"
and attention_mask is not None
and attention_mask.device.type == "cuda"
and not output_attentions
):
min_dtype = torch.finfo(dtype).min
causal_mask = AttentionMaskConverter._unmask_unattended(causal_mask, min_dtype)
return causal_mask
@staticmethod
def _prepare_4d_causal_attention_mask_with_cache_position(
attention_mask: torch.Tensor,
sequence_length: int,
target_length: int,
dtype: torch.dtype,
device: torch.device,
cache_position: torch.Tensor,
batch_size: int,
**kwargs,
) -> torch.Tensor:
if attention_mask is not None and attention_mask.dim() == 4:
return attention_mask
min_dtype = torch.finfo(dtype).min
causal_mask = torch.full(
(sequence_length, target_length),
fill_value=min_dtype,
dtype=dtype,
device=device,
)
if sequence_length != 1:
causal_mask = torch.triu(causal_mask, diagonal=1)
causal_mask *= torch.arange(target_length, device=device) > cache_position.reshape(-1, 1)
causal_mask = causal_mask[None, None, :, :].expand(batch_size, 1, -1, -1)
if attention_mask is not None:
causal_mask = causal_mask.clone()
mask_length = attention_mask.shape[-1]
padding_mask = causal_mask[:, :, :, :mask_length] + attention_mask[:, None, None, :].to(causal_mask.device)
padding_mask = padding_mask == 0
causal_mask[:, :, :, :mask_length] = causal_mask[:, :, :, :mask_length].masked_fill(padding_mask, min_dtype)
return causal_mask
# ------------------------------------------------------------------
# Forward through decoder layers
# ------------------------------------------------------------------
def forward_model(
self,
inputs_embeds: torch.FloatTensor,
attention_mask: torch.Tensor | None = None,
past_key_values: Cache | list[torch.FloatTensor] | None = None,
use_cache: bool | None = None,
output_attentions: bool | None = None,
output_hidden_states: bool | None = None,
cache_position: torch.LongTensor | None = None,
) -> BaseModelOutputWithPast:
output_attentions = (
output_attentions if output_attentions is not None else getattr(self.config, "output_attentions", False)
)
output_hidden_states = (
output_hidden_states
if output_hidden_states is not None
else getattr(self.config, "output_hidden_states", False)
)
use_cache = use_cache if use_cache is not None else getattr(self.config, "use_cache", True)
if use_cache and past_key_values is None:
past_key_values = DynamicCache()
if cache_position is None:
past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
cache_position = torch.arange(
past_seen_tokens,
past_seen_tokens + inputs_embeds.shape[1],
device=inputs_embeds.device,
)
position_ids = cache_position.unsqueeze(0)
causal_mask = self._update_causal_mask(
attention_mask,
inputs_embeds,
cache_position,
past_key_values,
output_attentions,
)
hidden_states = inputs_embeds
position_embeddings = self.rotary_emb(hidden_states, position_ids)
all_hidden_states = () if output_hidden_states else None
all_self_attns = () if output_attentions else None
for decoder_layer in self.layers:
if output_hidden_states:
all_hidden_states += (hidden_states,)
layer_outputs = decoder_layer(
hidden_states,
attention_mask=causal_mask,
past_key_value=past_key_values,
output_attentions=output_attentions,
use_cache=use_cache,
cache_position=cache_position,
position_embeddings=position_embeddings,
)
hidden_states = layer_outputs[0]
if output_attentions:
all_self_attns += (layer_outputs[1],)
hidden_states = self.norm(hidden_states)
if output_hidden_states:
all_hidden_states += (hidden_states,)
return BaseModelOutputWithPast(
last_hidden_state=hidden_states,
past_key_values=past_key_values if use_cache else None,
hidden_states=all_hidden_states,
attentions=all_self_attns,
)
# ------------------------------------------------------------------
# Weight loading with TP sharding support
# ------------------------------------------------------------------
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
stacked_params_mapping = [
# (param_name_in_model, weight_name_in_checkpoint, shard_id)
(".qkv_proj", ".q_proj", "q"),
(".qkv_proj", ".k_proj", "k"),
(".qkv_proj", ".v_proj", "v"),
(".gate_up_proj", ".gate_proj", 0),
(".gate_up_proj", ".up_proj", 1),
]
params_dict = dict(self.named_parameters())
loaded_params: set[str] = set()
for name, loaded_weight in weights:
for param_name, weight_name, shard_id in stacked_params_mapping:
if weight_name not in name:
continue
name = name.replace(weight_name, param_name)
param = params_dict[name]
weight_loader = param.weight_loader
weight_loader(param, loaded_weight, shard_id)
break
else:
if name not in params_dict:
continue
param = params_dict[name]
weight_loader = getattr(param, "weight_loader", default_weight_loader)
weight_loader(param, loaded_weight)
loaded_params.add(name)
return loaded_params