Signed-off-by: Yuanheng Zhao <jonathan.zhaoyh@gmail.com> Signed-off-by: yuanheng <jonathan.zhaoyh@gmail.com>
449 lines
17 KiB
Python
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
|