2571 lines
109 KiB
Python
2571 lines
109 KiB
Python
# Copyright 2025 Bytedance Ltd. and/or its affiliates.
|
|
# Copyright (c) 2024 The Qwen Team and The HuggingFace Inc. team.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# This file has been modified by ByteDance Ltd. and/or its affiliates.
|
|
#
|
|
# Original file was released under Apache-2.0, with the full license text
|
|
# available at https://github.com/huggingface/transformers/blob/main/LICENSE.
|
|
|
|
from collections.abc import Iterable, Sequence
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
import numpy as np
|
|
import torch
|
|
import torch.distributed as dist
|
|
from cache_dit import ForwardPattern
|
|
from torch import nn
|
|
from transformers.models.qwen2.configuration_qwen2 import Qwen2Config
|
|
from transformers.models.qwen2.modeling_qwen2 import (
|
|
Qwen2PreTrainedModel,
|
|
)
|
|
from transformers.utils import ModelOutput
|
|
from vllm.distributed import get_tensor_model_parallel_world_size
|
|
from vllm.logger import init_logger
|
|
from vllm.model_executor.layers.activation import SiluAndMul
|
|
from vllm.model_executor.layers.linear import (
|
|
ColumnParallelLinear,
|
|
MergedColumnParallelLinear,
|
|
RowParallelLinear,
|
|
)
|
|
from vllm.model_executor.layers.quantization.base_config import (
|
|
QuantizationConfig,
|
|
)
|
|
from vllm.model_executor.layers.vocab_parallel_embedding import VocabParallelEmbedding
|
|
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
|
|
from vllm.transformers_utils.configs.bagel import BagelConfig
|
|
|
|
from vllm_omni.diffusion.attention.backends.abstract import AttentionMetadata as DiffusionAttentionMetadata
|
|
from vllm_omni.diffusion.attention.layer import Attention as DiffusionAttention
|
|
from vllm_omni.diffusion.cache.cachedit import BagelCachedAdapter, CacheDiTAdapterConfig
|
|
from vllm_omni.diffusion.data import DiffusionParallelConfig
|
|
from vllm_omni.diffusion.distributed.cfg_parallel import CFGParallelMixin
|
|
from vllm_omni.diffusion.distributed.parallel_state import (
|
|
get_cfg_group,
|
|
get_classifier_free_guidance_world_size,
|
|
get_sequence_parallel_rank,
|
|
get_sp_group,
|
|
)
|
|
from vllm_omni.diffusion.forward_context import get_forward_context, is_forward_context_available
|
|
from vllm_omni.diffusion.layers.mot.mot_layernorm import MoTRMSNorm
|
|
from vllm_omni.diffusion.layers.mot.mot_qkv_parallel_linear import MoTQKVParallelLinear
|
|
from vllm_omni.diffusion.layers.mot.mot_row_parallel_linear import MoTRowParallelLinear
|
|
from vllm_omni.diffusion.layers.rope import RotaryEmbedding
|
|
from vllm_omni.diffusion.utils.kv_utils import left_pad_stack
|
|
from vllm_omni.model_executor.layers.timestep_embedding import timestep_embedding
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
def patchify(imgs, p):
|
|
"""
|
|
imgs: (N, 3, H, W) or (3, H, W)
|
|
x: (N, L, patch_size**2 *3) or (L, patch_size**2 *3)
|
|
"""
|
|
is_batch = imgs.ndim == 4
|
|
if not is_batch:
|
|
imgs = imgs.unsqueeze(0)
|
|
|
|
# n: batch, c: channel, h: grid_h, p: patch_h, w: grid_w, q: patch_w
|
|
x = imgs.reshape(imgs.shape[0], 3, imgs.shape[2] // p, p, imgs.shape[3] // p, p)
|
|
# Permute to (n, grid_h, grid_w, c, patch_h, patch_w) to match Conv2d (c, h, w) flattening
|
|
x = torch.einsum("nchpwq->nhwcpq", x)
|
|
x = x.reshape(imgs.shape[0], -1, 3 * p**2)
|
|
|
|
if not is_batch:
|
|
x = x.squeeze(0)
|
|
return x
|
|
|
|
|
|
class MLPconnector(nn.Module):
|
|
def __init__(
|
|
self,
|
|
input_dim,
|
|
output_dim,
|
|
activation="gelu_pytorch_tanh",
|
|
quant_config: QuantizationConfig | None = None,
|
|
prefix: str = "",
|
|
):
|
|
super().__init__()
|
|
self.fc1 = ColumnParallelLinear(
|
|
input_dim, output_dim, bias=True, gather_output=False, quant_config=quant_config, prefix=f"{prefix}.fc1"
|
|
)
|
|
if activation == "gelu":
|
|
self.act = nn.GELU()
|
|
elif activation == "gelu_pytorch_tanh":
|
|
self.act = nn.GELU(approximate="tanh")
|
|
else:
|
|
self.act = nn.ReLU()
|
|
self.fc2 = RowParallelLinear(
|
|
output_dim, output_dim, bias=True, input_is_parallel=True, quant_config=quant_config, prefix=f"{prefix}.fc2"
|
|
)
|
|
|
|
def forward(self, x):
|
|
x_parallel, _ = self.fc1(x)
|
|
x_parallel = self.act(x_parallel)
|
|
return self.fc2(x_parallel)[0]
|
|
|
|
|
|
class BagelRotaryEmbedding(nn.Module):
|
|
"""Standalone rotary embedding that generates cos/sin from position ids.
|
|
|
|
Replaces HuggingFace's Qwen2RotaryEmbedding while preserving full
|
|
``rope_scaling`` support. When ``config.rope_scaling`` is set (e.g.
|
|
linear, dynamic-NTK, YaRN, …), we delegate the ``inv_freq`` /
|
|
``attention_scaling`` computation to HF's ``ROPE_INIT_FUNCTIONS`` so
|
|
that the frequency basis and scaling factor are identical to the
|
|
original checkpoint.
|
|
|
|
For Qwen2.5-VL-style multimodal RoPE (``rope_scaling.rope_type == "mrope"``)
|
|
the ``inv_freq`` basis is the standard default-rope one; the difference
|
|
is that position ids are 3-D ``(t, h, w)`` per token and the
|
|
``mrope_section`` describes how the head dimension is split across axes.
|
|
This module accepts either 2-D scalar position ids ``(B, S)`` or 3-D
|
|
multimodal position ids ``(B, 3, S)`` and dispatches accordingly so the
|
|
same module works for both BAGEL (1-D rope) and Lance (Qwen2.5-VL mrope).
|
|
This module has no learnable parameters.
|
|
"""
|
|
|
|
def __init__(self, config):
|
|
super().__init__()
|
|
|
|
# transformers>=5.0 stores rope params under ``rope_parameters`` and
|
|
# always populates ``rope_scaling`` (even for the default type), so we
|
|
# infer ``rope_type`` from either and fall back to "default".
|
|
rope_scaling = getattr(config, "rope_scaling", None) or {}
|
|
rope_parameters = getattr(config, "rope_parameters", None) or {}
|
|
rope_type = (
|
|
rope_scaling.get("rope_type") or rope_scaling.get("type") or rope_parameters.get("rope_type") or "default"
|
|
)
|
|
# Cache mrope_section for the forward-time section-split.
|
|
self._mrope_section: list[int] | None = None
|
|
if rope_type == "mrope":
|
|
section = rope_scaling.get("mrope_section") or rope_parameters.get("mrope_section")
|
|
if section is None:
|
|
raise ValueError("rope_scaling.rope_type == 'mrope' requires 'mrope_section'.")
|
|
self._mrope_section = list(section)
|
|
|
|
if rope_type in ("default", "mrope"):
|
|
# mrope shares the default sinusoidal frequency basis; the
|
|
# multimodal split happens at forward time, not at init.
|
|
rope_theta = (
|
|
getattr(config, "rope_theta", None)
|
|
or rope_parameters.get("rope_theta")
|
|
or rope_scaling.get("rope_theta")
|
|
or 10000.0
|
|
)
|
|
dim = config.hidden_size // config.num_attention_heads
|
|
inv_freq = 1.0 / (rope_theta ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
|
|
self.attention_scaling = 1.0
|
|
else:
|
|
from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS
|
|
|
|
rope_init_fn = ROPE_INIT_FUNCTIONS[rope_type]
|
|
inv_freq, self.attention_scaling = rope_init_fn(config, device=None)
|
|
|
|
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
|
|
|
@torch.no_grad()
|
|
def forward(self, x: torch.Tensor, position_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
|
|
"""Generate cos/sin embeddings for given position ids.
|
|
|
|
Args:
|
|
x: Input tensor (only used for dtype inference).
|
|
position_ids: Either 2-D scalar ``(batch_size, seq_len)`` for plain
|
|
1-D RoPE, or 3-D multimodal ``(batch_size, 3, seq_len)`` for
|
|
Qwen2.5-VL-style mRoPE. The latter is auto-detected from
|
|
``position_ids.ndim``.
|
|
|
|
Returns:
|
|
cos, sin: Rotary embeddings, each of shape (batch_size, seq_len, dim).
|
|
"""
|
|
if position_ids.ndim == 3 and self._mrope_section is not None:
|
|
# multimodal path: position_ids is (B, 3, S) with rows = (t, h, w).
|
|
# Compute per-axis frequencies, then assemble the per-section
|
|
# rotary basis matching Qwen2-VL's ``apply_multimodal_rotary_pos_emb``.
|
|
B = position_ids.shape[0]
|
|
inv_freq_expanded = self.inv_freq[None, None, :, None].float().expand(B, 3, -1, 1)
|
|
position_ids_expanded = position_ids[:, :, None, :].float()
|
|
freqs = (inv_freq_expanded @ position_ids_expanded).transpose(2, 3)
|
|
# ``freqs`` is (B, 3, S, head_dim/2); double along the last axis
|
|
# to get the full ``head_dim`` rotary basis.
|
|
emb = torch.cat((freqs, freqs), dim=-1) # (B, 3, S, head_dim)
|
|
cos_per_axis = emb.cos() * self.attention_scaling
|
|
sin_per_axis = emb.sin() * self.attention_scaling
|
|
# ``mrope_section`` (e.g. [16, 24, 24] for Qwen2.5-VL) sums to
|
|
# head_dim/2; doubled it sums to head_dim and cycles axis = i % 3.
|
|
sec_full = self._mrope_section * 2
|
|
cos_split = cos_per_axis.split(sec_full, dim=-1)
|
|
sin_split = sin_per_axis.split(sec_full, dim=-1)
|
|
cos = torch.cat([c[:, i % 3] for i, c in enumerate(cos_split)], dim=-1)
|
|
sin = torch.cat([s[:, i % 3] for i, s in enumerate(sin_split)], dim=-1)
|
|
return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
|
|
|
|
inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1)
|
|
position_ids_expanded = position_ids[:, None, :].float()
|
|
freqs = (inv_freq_expanded @ position_ids_expanded).transpose(1, 2)
|
|
emb = torch.cat((freqs, freqs), dim=-1)
|
|
cos = emb.cos() * self.attention_scaling
|
|
sin = emb.sin() * self.attention_scaling
|
|
return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
|
|
|
|
|
|
class BagelMLP(nn.Module):
|
|
"""FFN with Mixture-of-Tokens routing via MoT parallel linear layers.
|
|
|
|
gate_proj + up_proj are fused into a single MoTMergedColumnParallelLinear.
|
|
down_proj uses MoTRowParallelLinear. Both layers hold text weights on self
|
|
and vae weights on self.gen_exp, routing by text_indices / vae_indices.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
hidden_size: int,
|
|
intermediate_size: int,
|
|
hidden_act: str = "silu",
|
|
quant_config: QuantizationConfig | None = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
|
|
self.intermediate_size = intermediate_size
|
|
|
|
self.gate_up_proj = MergedColumnParallelLinear(
|
|
input_size=hidden_size,
|
|
output_sizes=[intermediate_size, intermediate_size],
|
|
bias=False,
|
|
gather_output=False,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.gate_up_proj",
|
|
)
|
|
self.down_proj = RowParallelLinear(
|
|
input_size=intermediate_size,
|
|
output_size=hidden_size,
|
|
bias=False,
|
|
input_is_parallel=True,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.down_proj",
|
|
)
|
|
if hidden_act != "silu":
|
|
raise ValueError(f"Unsupported activation: {hidden_act}. Only silu is supported.")
|
|
self.act_fn = SiluAndMul()
|
|
|
|
def forward(
|
|
self,
|
|
x: torch.Tensor,
|
|
) -> torch.Tensor:
|
|
gate_up, _ = self.gate_up_proj(x)
|
|
x = self.act_fn(gate_up)
|
|
x, _ = self.down_proj(x)
|
|
return x
|
|
|
|
|
|
class Qwen2MoTConfig(Qwen2Config):
|
|
"""Configuration for Qwen2MoT (Mixture of Tokens) model.
|
|
|
|
This is fundamentally different from Qwen2, hence the distinct name.
|
|
"""
|
|
|
|
model_type = "qwen2_mot"
|
|
keys_to_ignore_at_inference = ["past_key_values"]
|
|
|
|
def __init__(
|
|
self,
|
|
vocab_size=151936,
|
|
hidden_size=4096,
|
|
intermediate_size=22016,
|
|
num_hidden_layers=32,
|
|
num_attention_heads=32,
|
|
num_key_value_heads=32,
|
|
hidden_act="silu",
|
|
max_position_embeddings=32768,
|
|
initializer_range=0.02,
|
|
rms_norm_eps=1e-6,
|
|
use_cache=True,
|
|
tie_word_embeddings=False,
|
|
rope_theta=10000.0,
|
|
rope_scaling=None,
|
|
use_sliding_window=False,
|
|
sliding_window=4096,
|
|
max_window_layers=28,
|
|
attention_dropout=0.0,
|
|
is_causal=True,
|
|
_attn_implementation="eager",
|
|
qk_norm=True,
|
|
layer_module="Qwen2MoTDecoderLayer",
|
|
freeze_und=False,
|
|
**kwargs,
|
|
):
|
|
super().__init__(
|
|
vocab_size=vocab_size,
|
|
hidden_size=hidden_size,
|
|
intermediate_size=intermediate_size,
|
|
num_hidden_layers=num_hidden_layers,
|
|
num_attention_heads=num_attention_heads,
|
|
num_key_value_heads=num_key_value_heads,
|
|
hidden_act=hidden_act,
|
|
max_position_embeddings=max_position_embeddings,
|
|
initializer_range=initializer_range,
|
|
rms_norm_eps=rms_norm_eps,
|
|
use_cache=use_cache,
|
|
tie_word_embeddings=tie_word_embeddings,
|
|
rope_theta=rope_theta,
|
|
rope_scaling=rope_scaling,
|
|
use_sliding_window=use_sliding_window,
|
|
sliding_window=sliding_window,
|
|
max_window_layers=max_window_layers,
|
|
attention_dropout=attention_dropout,
|
|
is_causal=is_causal,
|
|
_attn_implementation=_attn_implementation,
|
|
**kwargs,
|
|
)
|
|
self.qk_norm = qk_norm
|
|
self.layer_module = layer_module
|
|
|
|
|
|
class NaiveCache:
|
|
def __init__(self, num_layers):
|
|
self.key_cache = {k: None for k in range(num_layers)}
|
|
self.value_cache = {k: None for k in range(num_layers)}
|
|
# Track kv_lens; we need this because we pack the forward passes
|
|
# for CFG into a single forward call and the kv length may be different,
|
|
# e.g., due to 0 kvs for text_cfg path and nonzero for others
|
|
self.key_values_lens: list[int] | None = None
|
|
|
|
@property
|
|
def num_layers(self):
|
|
return len(self.key_cache)
|
|
|
|
@property
|
|
def seq_lens(self):
|
|
if self.key_cache[0] is not None:
|
|
return self.key_cache[0].shape[0]
|
|
else:
|
|
return 0
|
|
|
|
@classmethod
|
|
def from_object(cls, obj) -> "NaiveCache":
|
|
"""Convert a duck-typed cache (e.g., SimpleNamespace from KV transfer)
|
|
to NaiveCache; in the future, we should find a better way to handle this,
|
|
e.g., a model agnostic abstraction for key cache transfer instead of having
|
|
this cache live in bagel.
|
|
|
|
NOTE: If a NaiveCache is provided, the object is just returned. Otherwise,
|
|
we enumerate over the key/value cache values and map layer indices to the
|
|
corresponding tensors.
|
|
"""
|
|
if isinstance(obj, cls):
|
|
return obj
|
|
cache = cls(len(obj.key_cache))
|
|
for i, (k, v) in enumerate(zip(obj.key_cache, obj.value_cache, strict=True)):
|
|
cache.key_cache[i] = k
|
|
cache.value_cache[i] = v
|
|
return cache
|
|
|
|
@staticmethod
|
|
def merge(caches: Sequence["NaiveCache"]) -> "NaiveCache":
|
|
"""Merge NaiveCaches into one for packed request/CFG attention.
|
|
|
|
Already-merged inputs keep their per-sequence lengths so nested
|
|
request-by-CFG packing can split cache rows back out correctly.
|
|
"""
|
|
num_layers = caches[0].num_layers
|
|
merged = NaiveCache(num_layers)
|
|
lens = []
|
|
for cache in caches:
|
|
if cache.key_values_lens is None:
|
|
lens.append(cache.seq_lens)
|
|
else:
|
|
lens.extend(cache.key_values_lens)
|
|
merged.key_values_lens = lens
|
|
|
|
nonempty = [c for c in caches if c.key_cache[0] is not None]
|
|
if not nonempty:
|
|
return merged
|
|
|
|
for layer in range(num_layers):
|
|
merged.key_cache[layer] = torch.cat([c.key_cache[layer] for c in nonempty], dim=0)
|
|
merged.value_cache[layer] = torch.cat([c.value_cache[layer] for c in nonempty], dim=0)
|
|
|
|
return merged
|
|
|
|
@staticmethod
|
|
def split_with_zeros(
|
|
tensor: torch.Tensor,
|
|
lengths: Sequence[int],
|
|
) -> list[torch.Tensor | None]:
|
|
"""Split tensor by lengths, which may include 0 entries, e.g., for splitting cfg
|
|
branches out, since text_cfg may have 0 kv length.
|
|
|
|
0 lengths will be replaced with None in the returned list.
|
|
"""
|
|
# Ensure that the lengths are all nonzero and sum to the first dim of our tensor
|
|
if not all(isinstance(ln, int) and ln >= 0 for ln in lengths):
|
|
raise ValueError("split lengths must be greater than or equal to zero")
|
|
|
|
expected = sum(ln for ln in lengths if ln > 0)
|
|
if tensor.shape[0] != expected:
|
|
raise ValueError(f"tensor dim 0 ({tensor.shape[0]}) != sum of nonzero lengths ({expected})")
|
|
|
|
result: list[torch.Tensor | None] = []
|
|
offset = 0
|
|
for ln in lengths:
|
|
if ln > 0:
|
|
result.append(tensor[offset : offset + ln])
|
|
offset += ln
|
|
else:
|
|
result.append(None)
|
|
return result
|
|
|
|
|
|
@dataclass
|
|
class BaseNavitOutputWithPast(ModelOutput):
|
|
packed_query_sequence: torch.FloatTensor = None
|
|
past_key_values: NaiveCache | None = None
|
|
|
|
|
|
class PackedAttentionMoT(nn.Module):
|
|
"""Packed attention with Mixture-of-Tokens routing for understanding/generation.
|
|
|
|
Uses MoTQKVParallelLinear and MoTRowParallelLinear for tensor parallelism.
|
|
Text and vae weights are held within the same MoT layer (text on self,
|
|
vae on self.gen_exp). Token routing is driven by text_indices / vae_indices.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config,
|
|
layer_idx: int | None = None,
|
|
parallel_config: DiffusionParallelConfig | None = None,
|
|
quant_config: QuantizationConfig | None = None,
|
|
prefix: str = "",
|
|
):
|
|
super().__init__()
|
|
self.layer_idx = layer_idx
|
|
self.hidden_size = config.hidden_size
|
|
self.parallel_config = parallel_config
|
|
|
|
tp_size = get_tensor_model_parallel_world_size()
|
|
self.total_num_heads = config.num_attention_heads
|
|
assert self.total_num_heads % tp_size == 0
|
|
self.num_heads = self.total_num_heads // tp_size
|
|
self.total_num_kv_heads = config.num_key_value_heads
|
|
if self.total_num_kv_heads >= tp_size:
|
|
assert self.total_num_kv_heads % tp_size == 0
|
|
else:
|
|
assert tp_size % self.total_num_kv_heads == 0
|
|
self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
|
|
self.head_dim = self.hidden_size // self.total_num_heads
|
|
self.q_size = self.num_heads * self.head_dim
|
|
self.kv_size = self.num_kv_heads * self.head_dim
|
|
|
|
self.qkv_proj = MoTQKVParallelLinear(
|
|
self.hidden_size,
|
|
self.head_dim,
|
|
self.total_num_heads,
|
|
self.total_num_kv_heads,
|
|
bias=True,
|
|
vae_bias=True,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.qkv_proj",
|
|
)
|
|
self.o_proj = MoTRowParallelLinear(
|
|
input_size=self.total_num_heads * self.head_dim,
|
|
output_size=self.hidden_size,
|
|
bias=False,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.o_proj",
|
|
)
|
|
|
|
self.q_norm = MoTRMSNorm(self.head_dim, head_norm=True, eps=config.rms_norm_eps)
|
|
self.k_norm = MoTRMSNorm(self.head_dim, head_norm=True, eps=config.rms_norm_eps)
|
|
|
|
self.rotary_op = RotaryEmbedding(is_neox_style=True)
|
|
|
|
self.attn_causal = DiffusionAttention(
|
|
num_heads=self.total_num_heads,
|
|
head_size=self.head_dim,
|
|
softmax_scale=1.0 / (self.head_dim**0.5),
|
|
causal=True,
|
|
num_kv_heads=self.total_num_kv_heads,
|
|
)
|
|
self.attn_noncausal = DiffusionAttention(
|
|
num_heads=self.total_num_heads,
|
|
head_size=self.head_dim,
|
|
softmax_scale=1.0 / (self.head_dim**0.5),
|
|
causal=False,
|
|
num_kv_heads=self.total_num_kv_heads,
|
|
)
|
|
|
|
def _is_sp_active(self) -> bool:
|
|
"""Check if SP is active for this attention layer."""
|
|
if not is_forward_context_available():
|
|
return False
|
|
return get_forward_context().sp_active
|
|
|
|
def _forward_gen(
|
|
self,
|
|
packed_query_sequence: torch.Tensor,
|
|
query_lens: torch.Tensor,
|
|
packed_query_position_embeddings: torch.Tensor,
|
|
past_key_values: NaiveCache | None,
|
|
packed_vae_token_indexes: torch.Tensor,
|
|
packed_text_indexes: torch.Tensor,
|
|
update_past_key_values: bool = False,
|
|
) -> tuple[torch.Tensor, NaiveCache | None]:
|
|
"""Forward pass for generation mode.
|
|
|
|
This path does the following:
|
|
|
|
1. Apply qkv projection to the text seq & vae seqs
|
|
2. Reshape both to 3D & apply RMS norms
|
|
3. Apply RoPE to text / VAE components independently
|
|
4. Create the full K/V; Bagel currently manages its own KV cache
|
|
(NaiveCache) independently since it is a diffusion model
|
|
5. Apply non-causal attention, while taking sequence parallelism into account
|
|
6. Apply output projections on the split parts
|
|
7. Merge back into the packed format
|
|
8. Update the NaiveCache.
|
|
|
|
TODO (Alex): it would be best to remove packing from Bagel to simplify the code.
|
|
Currently we shouldn't need it in the model, and it would be ideal to handle
|
|
packing/batching etc in a more model agnostic way.
|
|
"""
|
|
text_indices = packed_text_indexes
|
|
vae_indices = packed_vae_token_indexes
|
|
|
|
cache_k = cache_v = None
|
|
packed_query_sequence = packed_query_sequence.to(torch.bfloat16)
|
|
|
|
# MoT QKV projection routes text/vae tokens to the matching weights.
|
|
qkv, _ = self.qkv_proj(packed_query_sequence, text_indices, vae_indices)
|
|
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
|
|
|
|
# Reshape to (tokens, heads, head_dim)
|
|
q = q.view(-1, self.num_heads, self.head_dim)
|
|
k = k.view(-1, self.num_kv_heads, self.head_dim)
|
|
v = v.view(-1, self.num_kv_heads, self.head_dim)
|
|
|
|
# MoT QK norms route text/vae tokens to weight/gen_weight internally.
|
|
q = self.q_norm(q.to(torch.float32), text_indices, vae_indices)
|
|
k = self.k_norm(k.to(torch.float32), text_indices, vae_indices)
|
|
|
|
cos, sin = [x[..., : self.head_dim // 2] for x in packed_query_position_embeddings]
|
|
q = self.rotary_op(q.to(cos.dtype).unsqueeze(0), cos, sin).squeeze(0)
|
|
k = self.rotary_op(k.to(cos.dtype).unsqueeze(0), cos, sin).squeeze(0)
|
|
|
|
q = q.to(torch.bfloat16)
|
|
k = k.to(torch.bfloat16)
|
|
v = v.to(torch.bfloat16)
|
|
|
|
text_q = q[text_indices]
|
|
text_k = k[text_indices]
|
|
text_v = v[text_indices]
|
|
vae_q = q[vae_indices]
|
|
vae_k = k[vae_indices]
|
|
vae_v = v[vae_indices]
|
|
|
|
num_branches = len(query_lens)
|
|
text_per_branch = text_q.shape[0] // num_branches
|
|
vae_per_branch = vae_q.shape[0] // num_branches
|
|
|
|
# Build joint K/V: [kv_cache, text_markers] (replicated across SP ranks)
|
|
if past_key_values is not None and past_key_values.key_cache[self.layer_idx] is not None:
|
|
cache_k = past_key_values.key_cache[self.layer_idx]
|
|
cache_v = past_key_values.value_cache[self.layer_idx]
|
|
ctx_k = torch.cat([cache_k, text_k], dim=0)
|
|
ctx_v = torch.cat([cache_v, text_v], dim=0)
|
|
else:
|
|
ctx_k = text_k
|
|
ctx_v = text_v
|
|
|
|
# NOTE: we reshape to batched (1, S, H, D) for diffusion Attention
|
|
# attn_out should be: (1, text_len + local_vae_len, H, D)
|
|
if self._is_sp_active():
|
|
# Joint mechanism keeps text+cache replicated across SP ranks
|
|
attn_out = self.attn_noncausal(
|
|
vae_q.unsqueeze(0),
|
|
vae_k.unsqueeze(0),
|
|
vae_v.unsqueeze(0),
|
|
DiffusionAttentionMetadata(
|
|
joint_query=text_q.unsqueeze(0),
|
|
joint_key=ctx_k.unsqueeze(0),
|
|
joint_value=ctx_v.unsqueeze(0),
|
|
joint_strategy="front",
|
|
),
|
|
)
|
|
else:
|
|
text_q_parts = text_q.split([text_per_branch] * num_branches)
|
|
vae_q_parts = vae_q.split([vae_per_branch] * num_branches)
|
|
text_k_parts = text_k.split([text_per_branch] * num_branches)
|
|
vae_k_parts = vae_k.split([vae_per_branch] * num_branches)
|
|
text_v_parts = text_v.split([text_per_branch] * num_branches)
|
|
vae_v_parts = vae_v.split([vae_per_branch] * num_branches)
|
|
|
|
# Query lengths should not be variable since we
|
|
# just split above, so we just concat + stack to 4D
|
|
q_4d = torch.stack([torch.cat([t, v]) for t, v in zip(text_q_parts, vae_q_parts)])
|
|
|
|
if cache_k is not None and cache_v is not None:
|
|
kv_lens = getattr(past_key_values, "key_values_lens", None)
|
|
if kv_lens is None:
|
|
per_branch = cache_k.shape[0] // num_branches
|
|
kv_lens = [per_branch] * num_branches
|
|
|
|
ck_per_branch = NaiveCache.split_with_zeros(cache_k, kv_lens)
|
|
cv_per_branch = NaiveCache.split_with_zeros(cache_v, kv_lens)
|
|
k_branches = [
|
|
torch.cat([t for t in (ck_per_branch[i], text_k_parts[i], vae_k_parts[i]) if t is not None])
|
|
for i in range(num_branches)
|
|
]
|
|
v_branches = [
|
|
torch.cat([t for t in (cv_per_branch[i], text_v_parts[i], vae_v_parts[i]) if t is not None])
|
|
for i in range(num_branches)
|
|
]
|
|
k_4d, mask = left_pad_stack(k_branches)
|
|
v_4d, _ = left_pad_stack(v_branches)
|
|
metadata = DiffusionAttentionMetadata(attn_mask=mask) if mask is not None else None
|
|
else:
|
|
k_4d = torch.stack([torch.cat([t, v]) for t, v in zip(text_k_parts, vae_k_parts)])
|
|
v_4d = torch.stack([torch.cat([t, v]) for t, v in zip(text_v_parts, vae_v_parts)])
|
|
metadata = None
|
|
attn_out = self.attn_noncausal(q_4d, k_4d, v_4d, metadata)
|
|
|
|
attn_out = attn_out.reshape(num_branches, -1, self.q_size)
|
|
text_attn = attn_out[:, :text_per_branch].reshape(-1, self.q_size)
|
|
vae_attn = attn_out[:, text_per_branch:].reshape(-1, self.q_size)
|
|
text_len = text_attn.shape[0]
|
|
|
|
# MoT output projection over local [text, vae] order.
|
|
local_packed = torch.cat([text_attn, vae_attn], dim=0)
|
|
local_text_idx = torch.arange(text_len, device=local_packed.device)
|
|
local_vae_idx = torch.arange(text_len, text_len + vae_attn.shape[0], device=local_packed.device)
|
|
local_out, _ = self.o_proj(local_packed, local_text_idx, local_vae_idx)
|
|
text_out = local_out[:text_len]
|
|
vae_out = local_out[text_len:]
|
|
|
|
# Merge back into packed format
|
|
total_len = packed_query_sequence.shape[0]
|
|
full_output = text_out.new_zeros((total_len, self.hidden_size))
|
|
full_output[packed_text_indexes] = text_out
|
|
full_output[packed_vae_token_indexes] = vae_out
|
|
|
|
if update_past_key_values:
|
|
new_k = torch.cat([ctx_k, vae_k], dim=0)
|
|
new_v = torch.cat([ctx_v, vae_v], dim=0)
|
|
past_key_values.key_cache[self.layer_idx] = new_k
|
|
past_key_values.value_cache[self.layer_idx] = new_v
|
|
|
|
return full_output, past_key_values
|
|
|
|
def _forward_und(
|
|
self,
|
|
packed_query_sequence: torch.Tensor,
|
|
packed_query_position_embeddings: torch.Tensor,
|
|
past_key_values: NaiveCache | None,
|
|
is_causal: bool,
|
|
update_past_key_values: bool = True,
|
|
) -> tuple[torch.Tensor, NaiveCache | None]:
|
|
"""Forward pass for understanding mode.
|
|
|
|
This path does the following (not hard to read):
|
|
|
|
1. Apply qkv projection to the text seq
|
|
2. Reshape to 3D & apply RMS norms
|
|
3. Apply RoPE
|
|
4. Create the full K/V; Bagel currently manages its own KV cache
|
|
(NaiveCache) independently since it is a diffusion model
|
|
5. Apply attention based on causality kwarg
|
|
6. Apply output projection
|
|
7. Update the NaiveCache.
|
|
"""
|
|
|
|
qkv, _ = self.qkv_proj(packed_query_sequence)
|
|
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
|
|
q = q.view(-1, self.num_heads, self.head_dim)
|
|
k = k.view(-1, self.num_kv_heads, self.head_dim)
|
|
v = v.view(-1, self.num_kv_heads, self.head_dim)
|
|
# Pre-merge code cast to float32 before q_norm/k_norm — bf16
|
|
# RMSNorm accumulation drift compounds across segmented prefill
|
|
# (x2t builds the cache via 3+ sequential forward_cache_update_*
|
|
# calls), producing degenerate token repetition in generate_text.
|
|
q = self.q_norm(q.to(torch.float32))
|
|
k = self.k_norm(k.to(torch.float32))
|
|
|
|
cos, sin = [x[..., : self.head_dim // 2] for x in packed_query_position_embeddings]
|
|
q = self.rotary_op(q.to(cos.dtype).unsqueeze(0), cos, sin).squeeze(0)
|
|
k = self.rotary_op(k.to(cos.dtype).unsqueeze(0), cos, sin).squeeze(0)
|
|
|
|
q = q.to(torch.bfloat16)
|
|
k = k.to(torch.bfloat16)
|
|
v = v.to(torch.bfloat16)
|
|
|
|
if past_key_values is not None and past_key_values.key_cache[self.layer_idx] is not None:
|
|
cache_k = past_key_values.key_cache[self.layer_idx]
|
|
cache_v = past_key_values.value_cache[self.layer_idx]
|
|
full_k = torch.cat([cache_k, k], dim=0)
|
|
full_v = torch.cat([cache_v, v], dim=0)
|
|
cache_len = cache_k.shape[0]
|
|
else:
|
|
full_k = k
|
|
full_v = v
|
|
cache_len = 0
|
|
|
|
if is_causal and cache_len > 0:
|
|
# PyTorch SDPA's ``is_causal=True`` with ``Q != K`` uses
|
|
# ``tril(diagonal=0)`` — top-left aligned, so for Q=1 against a
|
|
# long cache only ``q_0 -> k_0`` is unmasked (degenerate
|
|
# generate_text output). Build the correct bottom-right
|
|
# aligned mask manually: ``mask[i, j] = -inf if j > cache_len
|
|
# + i`` so each new query attends to all of ``cache + self
|
|
# up to i``. Bypass ``DiffusionAttention`` entirely — its
|
|
# ``_maybe_reshape_attn_mask`` helper only handles 2-D
|
|
# ``(B, K)`` shape, not ``(Q, K)``, so the reshape path
|
|
# silently drops the per-query mask for Q>1.
|
|
Q_len = q.shape[0]
|
|
K_len = full_k.shape[0]
|
|
arange_q = torch.arange(Q_len, device=q.device).unsqueeze(1)
|
|
arange_k = torch.arange(K_len, device=q.device).unsqueeze(0)
|
|
mask = arange_k > (cache_len + arange_q) # (Q, K) bool
|
|
attn_bias = torch.zeros(Q_len, K_len, dtype=q.dtype, device=q.device)
|
|
attn_bias.masked_fill_(mask, float("-inf"))
|
|
# Permute to (B=1, H, S, D) for SDPA.
|
|
q_4d = q.unsqueeze(0).permute(0, 2, 1, 3)
|
|
k_4d = full_k.unsqueeze(0).permute(0, 2, 1, 3)
|
|
v_4d = full_v.unsqueeze(0).permute(0, 2, 1, 3)
|
|
attn_out_4d = torch.nn.functional.scaled_dot_product_attention(
|
|
q_4d,
|
|
k_4d,
|
|
v_4d,
|
|
attn_mask=attn_bias.unsqueeze(0).unsqueeze(0), # (1, 1, Q, K)
|
|
dropout_p=0.0,
|
|
is_causal=False,
|
|
scale=1.0 / (self.head_dim**0.5),
|
|
enable_gqa=(self.num_heads != self.num_kv_heads),
|
|
)
|
|
attn_out = attn_out_4d.permute(0, 2, 1, 3)
|
|
else:
|
|
attn = self.attn_causal if is_causal else self.attn_noncausal
|
|
attn_out = attn(
|
|
q.unsqueeze(0),
|
|
full_k.unsqueeze(0),
|
|
full_v.unsqueeze(0),
|
|
)
|
|
|
|
attn_out = attn_out.squeeze(0).reshape(-1, self.q_size)
|
|
attn_out, _ = self.o_proj(attn_out)
|
|
|
|
if update_past_key_values:
|
|
past_key_values.key_cache[self.layer_idx] = full_k
|
|
past_key_values.value_cache[self.layer_idx] = full_v
|
|
|
|
return attn_out, past_key_values
|
|
|
|
def forward(
|
|
self,
|
|
packed_query_sequence: torch.Tensor,
|
|
query_lens: torch.Tensor,
|
|
packed_query_position_embeddings: torch.Tensor,
|
|
past_key_values: NaiveCache | None = None,
|
|
update_past_key_values=True,
|
|
is_causal=True,
|
|
mode="und",
|
|
packed_vae_token_indexes=None,
|
|
packed_text_indexes=None,
|
|
):
|
|
if mode == "gen":
|
|
if is_causal:
|
|
raise ValueError("Generation model for Bagel requires non-causal attention")
|
|
return self._forward_gen(
|
|
packed_query_sequence=packed_query_sequence,
|
|
query_lens=query_lens,
|
|
packed_query_position_embeddings=packed_query_position_embeddings,
|
|
past_key_values=past_key_values,
|
|
packed_vae_token_indexes=packed_vae_token_indexes,
|
|
packed_text_indexes=packed_text_indexes,
|
|
update_past_key_values=update_past_key_values,
|
|
)
|
|
|
|
return self._forward_und(
|
|
packed_query_sequence=packed_query_sequence,
|
|
packed_query_position_embeddings=packed_query_position_embeddings,
|
|
past_key_values=past_key_values,
|
|
is_causal=is_causal,
|
|
update_past_key_values=update_past_key_values,
|
|
)
|
|
|
|
|
|
class Qwen2MoTDecoderLayer(nn.Module):
|
|
def __init__(
|
|
self,
|
|
config,
|
|
layer_idx: int | None = None,
|
|
attn_module: type[nn.Module] | None = PackedAttentionMoT,
|
|
parallel_config: DiffusionParallelConfig | None = None,
|
|
quant_config: QuantizationConfig | None = None,
|
|
prefix: str = "",
|
|
):
|
|
super().__init__()
|
|
self.layer_idx = layer_idx
|
|
self.hidden_size = config.hidden_size
|
|
|
|
self.self_attn = attn_module(
|
|
config, layer_idx, parallel_config=parallel_config, quant_config=quant_config, prefix=f"{prefix}.self_attn"
|
|
)
|
|
|
|
self.input_layernorm = MoTRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
|
|
self.mlp = BagelMLP(
|
|
config.hidden_size,
|
|
config.intermediate_size,
|
|
config.hidden_act,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.mlp",
|
|
)
|
|
self.mlp_moe_gen = BagelMLP(
|
|
config.hidden_size,
|
|
config.intermediate_size,
|
|
config.hidden_act,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.mlp_moe_gen",
|
|
)
|
|
|
|
self.post_attention_layernorm = MoTRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
|
|
def forward(
|
|
self,
|
|
hidden_states: torch.Tensor,
|
|
encoder_hidden_states: torch.Tensor | None = None,
|
|
packed_query_sequence: torch.Tensor | None = None,
|
|
query_lens: torch.Tensor = None,
|
|
packed_query_position_embeddings: torch.Tensor = None,
|
|
past_key_values: NaiveCache | None = None,
|
|
update_past_key_values=True,
|
|
is_causal=True,
|
|
mode="und",
|
|
packed_vae_token_indexes=None,
|
|
packed_text_indexes=None,
|
|
) -> BaseNavitOutputWithPast:
|
|
if packed_query_sequence is None:
|
|
packed_query_sequence = hidden_states
|
|
|
|
text_indices = packed_text_indexes if mode == "gen" else None
|
|
vae_indices = packed_vae_token_indexes if mode == "gen" else None
|
|
|
|
residual = packed_query_sequence
|
|
packed_query_sequence = self.input_layernorm(packed_query_sequence, text_indices, vae_indices)
|
|
|
|
# Self Attention
|
|
packed_query_sequence, past_key_values = self.self_attn(
|
|
packed_query_sequence=packed_query_sequence,
|
|
query_lens=query_lens,
|
|
packed_query_position_embeddings=packed_query_position_embeddings,
|
|
past_key_values=past_key_values,
|
|
update_past_key_values=update_past_key_values,
|
|
is_causal=is_causal,
|
|
mode=mode,
|
|
packed_vae_token_indexes=packed_vae_token_indexes,
|
|
packed_text_indexes=packed_text_indexes,
|
|
)
|
|
packed_query_sequence = residual + packed_query_sequence
|
|
|
|
# Fully Connected
|
|
residual = packed_query_sequence
|
|
if mode == "und":
|
|
packed_query_sequence = self.post_attention_layernorm(packed_query_sequence)
|
|
packed_query_sequence = self.mlp(packed_query_sequence)
|
|
elif mode == "gen":
|
|
packed_normed = self.post_attention_layernorm(packed_query_sequence, text_indices, vae_indices).to(
|
|
torch.bfloat16
|
|
)
|
|
packed_text_query_sequence = packed_normed[packed_text_indexes]
|
|
packed_vae_query_sequence = packed_normed[packed_vae_token_indexes]
|
|
packed_query_sequence_ = torch.zeros_like(packed_query_sequence).to(torch.bfloat16)
|
|
packed_query_sequence_[packed_text_indexes] = self.mlp(packed_text_query_sequence)
|
|
packed_query_sequence_[packed_vae_token_indexes] = self.mlp_moe_gen(packed_vae_query_sequence)
|
|
packed_query_sequence = packed_query_sequence_
|
|
|
|
packed_query_sequence = residual + packed_query_sequence
|
|
|
|
return packed_query_sequence, past_key_values
|
|
|
|
|
|
class Qwen2MoTModel(Qwen2PreTrainedModel):
|
|
_cache_dit_adapter_config = CacheDiTAdapterConfig(
|
|
block_forward_patterns={
|
|
"layers": ForwardPattern.Pattern_0,
|
|
},
|
|
cached_adapter_cls=BagelCachedAdapter,
|
|
)
|
|
|
|
_layerwise_offload_blocks_attrs = ["layers"]
|
|
|
|
@staticmethod
|
|
def _is_transformer_block(name: str, module) -> bool:
|
|
return "layers" in name and name.split(".")[-1].isdigit()
|
|
|
|
_hsdp_shard_conditions = [_is_transformer_block]
|
|
|
|
def __init__(
|
|
self,
|
|
config,
|
|
parallel_config: DiffusionParallelConfig | None = None,
|
|
quant_config: QuantizationConfig | None = None,
|
|
prefix: str = "",
|
|
):
|
|
super().__init__(config)
|
|
self.padding_idx = config.pad_token_id
|
|
self.vocab_size = config.vocab_size
|
|
self.use_moe = "Mo" in config.layer_module
|
|
|
|
self.embed_tokens = VocabParallelEmbedding(config.vocab_size, config.hidden_size)
|
|
self.layers = nn.ModuleList(
|
|
[
|
|
Qwen2MoTDecoderLayer(
|
|
config,
|
|
layer_idx,
|
|
attn_module=PackedAttentionMoT,
|
|
parallel_config=parallel_config,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.layers.{layer_idx}",
|
|
)
|
|
for layer_idx in range(config.num_hidden_layers)
|
|
]
|
|
)
|
|
|
|
self.norm = MoTRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
self.rotary_emb = BagelRotaryEmbedding(config=config)
|
|
|
|
# Initialize weights and apply final processing
|
|
self.post_init()
|
|
|
|
def forward(
|
|
self,
|
|
packed_query_sequence: torch.Tensor | None = None,
|
|
query_lens: torch.Tensor | None = None,
|
|
packed_query_position_ids: torch.Tensor | None = None,
|
|
past_key_values: NaiveCache | None = None,
|
|
update_past_key_values=True,
|
|
is_causal=True,
|
|
mode="und",
|
|
packed_vae_token_indexes=None,
|
|
packed_text_indexes=None,
|
|
packed_text_ids: torch.Tensor | None = None,
|
|
return_embeddings_only: bool = False,
|
|
) -> BaseNavitOutputWithPast:
|
|
if packed_query_sequence is None:
|
|
if packed_text_ids is None:
|
|
raise ValueError("Either packed_query_sequence or packed_text_ids must be provided.")
|
|
packed_query_sequence = self.embed_tokens(packed_text_ids)
|
|
|
|
if return_embeddings_only:
|
|
return BaseNavitOutputWithPast(
|
|
packed_query_sequence=packed_query_sequence,
|
|
past_key_values=past_key_values,
|
|
)
|
|
|
|
# create position embeddings to be shared across the decoder layers
|
|
cos, sin = self.rotary_emb(packed_query_sequence, packed_query_position_ids.unsqueeze(0))
|
|
cos = cos.squeeze(0)
|
|
sin = sin.squeeze(0)
|
|
packed_query_position_embeddings = (cos, sin)
|
|
|
|
extra_inputs = {}
|
|
if self.use_moe:
|
|
extra_inputs.update(mode=mode)
|
|
if mode == "gen":
|
|
assert packed_vae_token_indexes is not None
|
|
assert packed_text_indexes is not None
|
|
extra_inputs.update(
|
|
packed_vae_token_indexes=packed_vae_token_indexes,
|
|
packed_text_indexes=packed_text_indexes,
|
|
)
|
|
|
|
for layer_idx, decoder_layer in enumerate(self.layers):
|
|
# TODO (Alex): Remove encoder_hidden_states as a kwarg; currently we keep it
|
|
# for compatibility with the current custom CacheDiT adapter, as we need to be
|
|
# careful to not break the NaiveCache handling when switching from pattern
|
|
# 0 -> 4.
|
|
packed_query_sequence, past_key_values = decoder_layer(
|
|
hidden_states=packed_query_sequence,
|
|
encoder_hidden_states=None,
|
|
query_lens=query_lens,
|
|
packed_query_position_embeddings=packed_query_position_embeddings,
|
|
past_key_values=past_key_values,
|
|
update_past_key_values=update_past_key_values,
|
|
is_causal=is_causal,
|
|
**extra_inputs,
|
|
)
|
|
|
|
text_indices = packed_text_indexes if self.use_moe and mode == "gen" else None
|
|
vae_indices = packed_vae_token_indexes if self.use_moe and mode == "gen" else None
|
|
packed_query_sequence = self.norm(packed_query_sequence, text_indices, vae_indices)
|
|
|
|
return BaseNavitOutputWithPast(
|
|
packed_query_sequence=packed_query_sequence,
|
|
past_key_values=past_key_values,
|
|
)
|
|
|
|
|
|
class Qwen2MoTForCausalLM(Qwen2PreTrainedModel):
|
|
_tied_weights_keys = ["lm_head.weight"]
|
|
|
|
def __init__(
|
|
self,
|
|
config,
|
|
parallel_config: DiffusionParallelConfig | None = None,
|
|
quant_config: QuantizationConfig | None = None,
|
|
prefix: str = "",
|
|
):
|
|
super().__init__(config)
|
|
self.model = Qwen2MoTModel(
|
|
config, parallel_config=parallel_config, quant_config=quant_config, prefix=f"{prefix}.model"
|
|
)
|
|
self.vocab_size = config.vocab_size
|
|
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
|
|
|
|
# Initialize weights and apply final processing
|
|
self.post_init()
|
|
|
|
def get_input_embeddings(self):
|
|
return self.model.embed_tokens
|
|
|
|
def set_input_embeddings(self, value):
|
|
self.model.embed_tokens = value
|
|
|
|
def get_output_embeddings(self):
|
|
return self.lm_head
|
|
|
|
def set_output_embeddings(self, new_embeddings):
|
|
self.lm_head = new_embeddings
|
|
|
|
def set_decoder(self, decoder):
|
|
self.model = decoder
|
|
|
|
def get_decoder(self):
|
|
return self.model
|
|
|
|
def forward(
|
|
self,
|
|
packed_query_sequence: torch.Tensor | None = None,
|
|
query_lens: torch.Tensor | None = None,
|
|
packed_query_position_ids: torch.Tensor | None = None,
|
|
past_key_values: NaiveCache | None = None,
|
|
update_past_key_values=True,
|
|
is_causal=True,
|
|
mode="und",
|
|
packed_vae_token_indexes=None,
|
|
packed_text_indexes=None,
|
|
packed_text_ids: torch.Tensor | None = None,
|
|
return_embeddings_only: bool = False,
|
|
) -> BaseNavitOutputWithPast:
|
|
outputs = self.model(
|
|
packed_query_sequence=packed_query_sequence,
|
|
query_lens=query_lens,
|
|
packed_query_position_ids=packed_query_position_ids,
|
|
past_key_values=past_key_values,
|
|
update_past_key_values=update_past_key_values,
|
|
is_causal=is_causal,
|
|
mode=mode,
|
|
packed_vae_token_indexes=packed_vae_token_indexes,
|
|
packed_text_indexes=packed_text_indexes,
|
|
packed_text_ids=packed_text_ids,
|
|
return_embeddings_only=return_embeddings_only,
|
|
)
|
|
|
|
return outputs
|
|
|
|
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
|
"""Load weights for MoT parallel layers.
|
|
|
|
Stacked parameter remapping (checkpoint name → model parameter):
|
|
- q/k/v_proj → qkv_proj (text, shard q/k/v)
|
|
- q/k/v_proj_moe_gen → qkv_proj.gen_exp (gen, shard q/k/v)
|
|
|
|
Direct remapping (no shard dimension):
|
|
- o_proj_moe_gen → o_proj.gen_exp
|
|
- {norm}_moe_gen.weight → {norm}.gen_weight (all MoTRMSNorm layers)
|
|
|
|
Text norm weights (input_layernorm.weight, q_norm.weight, etc.) and
|
|
other names (embed_tokens, lm_head) pass through unchanged.
|
|
"""
|
|
stacked_params_mapping = [
|
|
# (param_name, weight_name, shard_id)
|
|
# _moe_gen patterns MUST come first — `.q_proj` is a substring
|
|
# of `.q_proj_moe_gen`, so the more specific pattern must match first.
|
|
(".qkv_proj.gen_exp", ".q_proj_moe_gen", "q"),
|
|
(".qkv_proj.gen_exp", ".k_proj_moe_gen", "k"),
|
|
(".qkv_proj.gen_exp", ".v_proj_moe_gen", "v"),
|
|
(".qkv_proj", ".q_proj", "q"),
|
|
(".qkv_proj", ".k_proj", "k"),
|
|
(".qkv_proj", ".v_proj", "v"),
|
|
(".mlp_moe_gen.gate_up_proj", ".mlp_moe_gen.gate_proj", 0),
|
|
(".mlp_moe_gen.gate_up_proj", ".mlp_moe_gen.up_proj", 1),
|
|
(".mlp.gate_up_proj", ".mlp.gate_proj", 0),
|
|
(".mlp.gate_up_proj", ".mlp.up_proj", 1),
|
|
]
|
|
|
|
direct_remap = [
|
|
(".o_proj_moe_gen.", ".o_proj.gen_exp."),
|
|
# Norm _moe_gen.weight → {norm_name}.gen_weight
|
|
(".input_layernorm_moe_gen.", ".input_layernorm.gen_"),
|
|
(".post_attention_layernorm_moe_gen.", ".post_attention_layernorm.gen_"),
|
|
(".q_norm_moe_gen.", ".q_norm.gen_"),
|
|
(".k_norm_moe_gen.", ".k_norm.gen_"),
|
|
(".norm_moe_gen.", ".norm.gen_"),
|
|
]
|
|
|
|
params_dict = dict(self.named_parameters())
|
|
loaded_params: set[str] = set()
|
|
|
|
def handle_weight(name, loaded_weight, shard_id=None):
|
|
param = params_dict.get(name)
|
|
if param is None:
|
|
logger.warning_once("Skipping weight %r: no matching parameter found in model.", name)
|
|
return
|
|
weight_loader = getattr(param, "weight_loader", default_weight_loader)
|
|
if shard_id is not None:
|
|
weight_loader(param, loaded_weight, shard_id)
|
|
else:
|
|
weight_loader(param, loaded_weight)
|
|
loaded_params.add(name)
|
|
|
|
for name, loaded_weight in weights:
|
|
# match direct remap
|
|
handled = False
|
|
for old_substr, new_substr in direct_remap:
|
|
if old_substr in name:
|
|
name = name.replace(old_substr, new_substr)
|
|
handle_weight(name, loaded_weight)
|
|
handled = True
|
|
break
|
|
|
|
if handled:
|
|
continue
|
|
|
|
# match stacked params mapping
|
|
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)
|
|
handle_weight(name, loaded_weight, shard_id)
|
|
handled = True
|
|
break
|
|
|
|
if handled:
|
|
continue
|
|
|
|
# no-name-match cases are handled here
|
|
handle_weight(name, loaded_weight)
|
|
|
|
return loaded_params
|
|
|
|
|
|
def get_2d_sincos_pos_embed(embed_dim, grid_size, cls_token=False, extra_tokens=0):
|
|
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) # here w goes first
|
|
grid = np.stack(grid, axis=0)
|
|
|
|
grid = grid.reshape([2, 1, grid_size, grid_size])
|
|
pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid)
|
|
if cls_token and extra_tokens > 0:
|
|
pos_embed = np.concatenate([np.zeros([extra_tokens, embed_dim]), pos_embed], axis=0)
|
|
return pos_embed
|
|
|
|
|
|
def get_2d_sincos_pos_embed_from_grid(embed_dim, grid):
|
|
assert embed_dim % 2 == 0
|
|
|
|
# use half of dimensions to encode grid_h
|
|
emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0]) # (H*W, D/2)
|
|
emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1]) # (H*W, D/2)
|
|
|
|
emb = np.concatenate([emb_h, emb_w], axis=1) # (H*W, D)
|
|
return emb
|
|
|
|
|
|
def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
|
|
"""
|
|
embed_dim: output dimension for each position
|
|
pos: a list of positions to be encoded: size (M,)
|
|
out: (M, D)
|
|
"""
|
|
assert embed_dim % 2 == 0
|
|
omega = np.arange(embed_dim // 2, dtype=np.float64)
|
|
omega /= embed_dim / 2.0
|
|
omega = 1.0 / 10000**omega # (D/2,)
|
|
|
|
pos = pos.reshape(-1) # (M,)
|
|
out = np.einsum("m,d->md", pos, omega) # (M, D/2), outer product
|
|
|
|
emb_sin = np.sin(out) # (M, D/2)
|
|
emb_cos = np.cos(out) # (M, D/2)
|
|
|
|
emb = np.concatenate([emb_sin, emb_cos], axis=1) # (M, D)
|
|
return emb
|
|
|
|
|
|
class TimestepEmbedder(nn.Module):
|
|
"""
|
|
Embeds scalar timesteps into vector representations.
|
|
"""
|
|
|
|
def __init__(self, hidden_size, frequency_embedding_size=256):
|
|
super().__init__()
|
|
self.mlp = nn.Sequential(
|
|
nn.Linear(frequency_embedding_size, hidden_size, bias=True),
|
|
nn.SiLU(),
|
|
nn.Linear(hidden_size, hidden_size, bias=True),
|
|
)
|
|
self.frequency_embedding_size = frequency_embedding_size
|
|
|
|
def forward(self, t):
|
|
t_freq = timestep_embedding(t, self.frequency_embedding_size)
|
|
t_emb = self.mlp(t_freq)
|
|
return t_emb
|
|
|
|
|
|
class PositionEmbedding(nn.Module):
|
|
def __init__(self, max_num_patch_per_side, hidden_size):
|
|
super().__init__()
|
|
self.max_num_patch_per_side = max_num_patch_per_side
|
|
self.hidden_size = hidden_size
|
|
self.pos_embed = nn.Parameter(torch.zeros(max_num_patch_per_side**2, hidden_size), requires_grad=False)
|
|
self._init_weights()
|
|
|
|
def _init_weights(self):
|
|
# Initialize (and freeze) pos_embed by sin-cos embedding:
|
|
pos_embed = get_2d_sincos_pos_embed(self.hidden_size, self.max_num_patch_per_side)
|
|
self.pos_embed.data.copy_(torch.from_numpy(pos_embed).float())
|
|
|
|
def forward(self, position_ids):
|
|
return self.pos_embed[position_ids]
|
|
|
|
|
|
def get_flattened_position_ids_extrapolate(img_h, img_w, patch_size, max_num_patches_per_side):
|
|
num_patches_h, num_patches_w = img_h // patch_size, img_w // patch_size
|
|
coords_h = torch.arange(0, num_patches_h)
|
|
coords_w = torch.arange(0, num_patches_w)
|
|
pos_ids = (coords_h[:, None] * max_num_patches_per_side + coords_w).flatten()
|
|
return pos_ids
|
|
|
|
|
|
class Bagel(CFGParallelMixin, nn.Module):
|
|
config_class = BagelConfig
|
|
base_model_prefix = "bagel"
|
|
|
|
# Include the terminal t=0 sample so ``num_timesteps`` means exactly
|
|
# ``num_timesteps`` Euler updates. Subclasses may override this for a
|
|
# model-specific schedule convention.
|
|
_denoise_schedule_extra_step: bool = True
|
|
|
|
def prepare_denoise_schedule(
|
|
self,
|
|
x_t: torch.Tensor,
|
|
num_timesteps: int,
|
|
timestep_shift: float,
|
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
"""Build BAGEL's flow-matching timesteps and per-step deltas."""
|
|
num_sample_points = num_timesteps + 1 if self._denoise_schedule_extra_step else num_timesteps
|
|
schedule = torch.linspace(1, 0, num_sample_points, device=x_t.device)
|
|
schedule = timestep_shift * schedule / (1 + (timestep_shift - 1) * schedule)
|
|
return schedule[:-1], schedule[:-1] - schedule[1:]
|
|
|
|
def __init__(
|
|
self,
|
|
language_model,
|
|
vit_model,
|
|
config: BagelConfig,
|
|
parallel_config: DiffusionParallelConfig | None = None,
|
|
quant_config: QuantizationConfig | None = None,
|
|
prefix: str = "",
|
|
):
|
|
super().__init__()
|
|
self.language_model = language_model
|
|
self.hidden_size = config.llm_config.hidden_size
|
|
self.use_moe = "Mo" in config.llm_config.layer_module
|
|
self.num_heads = config.llm_config.num_attention_heads
|
|
self.parallel_config = parallel_config
|
|
|
|
if config.visual_gen:
|
|
self.latent_patch_size = config.latent_patch_size
|
|
self.timestep_shift = config.timestep_shift
|
|
self.latent_downsample = config.vae_config.downsample * config.latent_patch_size
|
|
self.max_latent_size = config.max_latent_size
|
|
self.latent_channel = config.vae_config.z_channels
|
|
self.patch_latent_dim = self.latent_patch_size**2 * self.latent_channel
|
|
self.time_embedder = TimestepEmbedder(self.hidden_size)
|
|
self.vae2llm = nn.Linear(self.patch_latent_dim, self.hidden_size)
|
|
self.llm2vae = nn.Linear(self.hidden_size, self.patch_latent_dim)
|
|
self.latent_pos_embed = PositionEmbedding(self.max_latent_size, self.hidden_size)
|
|
|
|
if config.visual_und:
|
|
self.vit_model = vit_model
|
|
self.vit_patch_size = config.vit_config.patch_size
|
|
self.vit_max_num_patch_per_side = config.vit_max_num_patch_per_side
|
|
self.vit_hidden_size = config.vit_config.hidden_size
|
|
self.connector = MLPconnector(
|
|
self.vit_hidden_size,
|
|
self.hidden_size,
|
|
config.connector_act,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.connector",
|
|
)
|
|
self.vit_pos_embed = PositionEmbedding(self.vit_max_num_patch_per_side, self.hidden_size)
|
|
|
|
self.get_flattened_position_ids = get_flattened_position_ids_extrapolate
|
|
|
|
self.config = config
|
|
self._init_weights()
|
|
|
|
@property
|
|
def _sp_size(self) -> int:
|
|
if self.parallel_config is None:
|
|
return 1
|
|
sp = self.parallel_config.sequence_parallel_size
|
|
return sp if sp is not None and sp > 1 else 1
|
|
|
|
def _split_vae_for_sp(
|
|
self,
|
|
x_t: torch.Tensor,
|
|
packed_vae_position_ids: torch.Tensor,
|
|
packed_vae_token_indexes: torch.Tensor,
|
|
packed_text_indexes: torch.Tensor,
|
|
packed_seqlens: torch.Tensor,
|
|
packed_position_ids: torch.Tensor,
|
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
"""Split VAE tokens across SP ranks for the denoising loop.
|
|
|
|
Returns adjusted (x_t, packed_vae_position_ids, packed_vae_token_indexes,
|
|
packed_text_indexes, packed_seqlens, packed_position_ids) for the local rank.
|
|
"""
|
|
sp_size = self._sp_size
|
|
sp_rank = get_sequence_parallel_rank()
|
|
num_vae = x_t.shape[0]
|
|
assert num_vae % sp_size == 0, f"VAE token count {num_vae} not divisible by SP size {sp_size}"
|
|
chunk = num_vae // sp_size
|
|
start = sp_rank * chunk
|
|
end = start + chunk
|
|
|
|
local_x_t = x_t[start:end]
|
|
local_vae_pos_ids = packed_vae_position_ids[start:end]
|
|
|
|
# Rebuild local packed indices:
|
|
# packed sequence = [start_of_image, local_vae_tokens..., end_of_image]
|
|
# BAGEL always has exactly 2 text markers (start/end_of_image).
|
|
num_text = packed_text_indexes.shape[0]
|
|
assert num_text == 2, f"Expected exactly 2 text markers (start/end_of_image), got {num_text}"
|
|
assert packed_seqlens.numel() == 1, (
|
|
f"SP currently supports single-image batches only, got {packed_seqlens.numel()} sequences"
|
|
)
|
|
local_vae_len = chunk
|
|
local_total = num_text + local_vae_len
|
|
|
|
local_text_indexes = torch.tensor([0, local_vae_len + 1], device=packed_text_indexes.device)
|
|
local_vae_indexes = torch.arange(1, 1 + local_vae_len, device=packed_vae_token_indexes.device)
|
|
|
|
local_seqlens = torch.tensor([local_total], device=packed_seqlens.device, dtype=packed_seqlens.dtype)
|
|
|
|
# Build local position IDs preserving global positions.
|
|
# Text markers keep their original positions; VAE tokens get
|
|
# the global positions for the local chunk.
|
|
text_pos_ids = packed_position_ids[packed_text_indexes]
|
|
vae_pos_ids_full = packed_position_ids[packed_vae_token_indexes]
|
|
local_vae_pos = vae_pos_ids_full[start:end]
|
|
local_position_ids = torch.zeros(
|
|
local_total, device=packed_position_ids.device, dtype=packed_position_ids.dtype
|
|
)
|
|
local_position_ids[local_text_indexes] = text_pos_ids
|
|
local_position_ids[local_vae_indexes] = local_vae_pos
|
|
|
|
return local_x_t, local_vae_pos_ids, local_vae_indexes, local_text_indexes, local_seqlens, local_position_ids
|
|
|
|
def _gather_vae_for_sp(self, local_v_t: torch.Tensor) -> torch.Tensor:
|
|
"""Gather VAE velocity outputs from all SP ranks."""
|
|
sp_size = self._sp_size
|
|
gathered = [torch.zeros_like(local_v_t) for _ in range(sp_size)]
|
|
sp_group = get_sp_group()
|
|
dist.all_gather(gathered, local_v_t.contiguous(), group=sp_group.device_group)
|
|
return torch.cat(gathered, dim=0)
|
|
|
|
def _init_weights(self):
|
|
if self.config.visual_gen:
|
|
nn.init.constant_(self.llm2vae.weight, 0)
|
|
nn.init.constant_(self.llm2vae.bias, 0)
|
|
|
|
def prepare_prompts(self, curr_kvlens, curr_rope, prompts, tokenizer, new_token_ids):
|
|
packed_text_ids = list()
|
|
packed_text_position_ids = list()
|
|
text_token_lens = list()
|
|
|
|
newlens, new_rope = list(), list()
|
|
for prompt, curr_kvlen, curr_position_id in zip(prompts, curr_kvlens, curr_rope):
|
|
text_ids = tokenizer.encode(prompt, add_special_tokens=False)
|
|
text_ids = [new_token_ids["bos_token_id"]] + text_ids + [new_token_ids["eos_token_id"]]
|
|
text_token_lens.append(len(text_ids))
|
|
packed_text_ids.extend(text_ids)
|
|
packed_text_position_ids.extend(range(curr_position_id, curr_position_id + len(text_ids)))
|
|
newlens.append(curr_kvlen + len(text_ids))
|
|
new_rope.append(curr_position_id + len(text_ids))
|
|
|
|
generation_input = {
|
|
"text_token_lens": torch.tensor(text_token_lens, dtype=torch.int),
|
|
"packed_text_ids": torch.tensor(packed_text_ids, dtype=torch.long),
|
|
"packed_text_position_ids": torch.tensor(packed_text_position_ids, dtype=torch.long),
|
|
}
|
|
|
|
return generation_input, newlens, new_rope
|
|
|
|
def forward_cache_update_text(
|
|
self,
|
|
past_key_values: NaiveCache,
|
|
packed_text_ids: torch.IntTensor,
|
|
packed_text_position_ids: torch.LongTensor,
|
|
text_token_lens: torch.LongTensor,
|
|
):
|
|
extra_inputs = {}
|
|
if self.use_moe:
|
|
extra_inputs = {"mode": "und"}
|
|
|
|
output = self.language_model.forward(
|
|
packed_text_ids=packed_text_ids,
|
|
query_lens=text_token_lens,
|
|
packed_query_position_ids=packed_text_position_ids,
|
|
past_key_values=past_key_values,
|
|
update_past_key_values=True,
|
|
is_causal=True,
|
|
**extra_inputs,
|
|
)
|
|
past_key_values = output.past_key_values
|
|
|
|
return past_key_values
|
|
|
|
def prepare_vae_images(self, curr_kvlens, curr_rope, images, transforms, new_token_ids, timestep=0):
|
|
patchified_vae_latent_shapes, packed_vae_position_ids = list(), list()
|
|
packed_vae_token_indexes = list()
|
|
packed_text_ids, packed_text_indexes = list(), list()
|
|
packed_seqlens, packed_position_ids = list(), list()
|
|
|
|
_curr = 0
|
|
vae_image_tensors = list()
|
|
newlens, new_rope = list(), list()
|
|
for image, curr_kvlen, curr_position_id in zip(images, curr_kvlens, curr_rope):
|
|
packed_text_ids.append(new_token_ids["start_of_image"])
|
|
packed_text_indexes.append(_curr)
|
|
_curr += 1
|
|
|
|
image_tensor = transforms(image)
|
|
vae_image_tensors.append(image_tensor)
|
|
vae_position_ids = self.get_flattened_position_ids(
|
|
image_tensor.size(1),
|
|
image_tensor.size(2),
|
|
self.latent_downsample,
|
|
max_num_patches_per_side=self.max_latent_size,
|
|
)
|
|
packed_vae_position_ids.append(vae_position_ids)
|
|
H, W = image_tensor.shape[1:]
|
|
h = H // self.latent_downsample
|
|
w = W // self.latent_downsample
|
|
patchified_vae_latent_shapes.append((h, w))
|
|
|
|
num_img_tokens = w * h
|
|
packed_vae_token_indexes.extend(range(_curr, _curr + num_img_tokens))
|
|
_curr += num_img_tokens
|
|
|
|
packed_text_ids.append(new_token_ids["end_of_image"])
|
|
packed_text_indexes.append(_curr)
|
|
_curr += 1
|
|
|
|
packed_position_ids.extend([curr_position_id] * (num_img_tokens + 2))
|
|
packed_seqlens.append(num_img_tokens + 2)
|
|
newlens.append(curr_kvlen + num_img_tokens + 2)
|
|
new_rope.append(curr_position_id + 1)
|
|
|
|
image_sizes = [item.shape for item in vae_image_tensors]
|
|
max_image_size = [max(item) for item in list(zip(*image_sizes))]
|
|
padded_images = torch.zeros(size=(len(vae_image_tensors), *max_image_size))
|
|
for i, image_tensor in enumerate(vae_image_tensors):
|
|
padded_images[i, :, : image_tensor.shape[1], : image_tensor.shape[2]] = image_tensor
|
|
|
|
generation_input = {
|
|
"padded_images": padded_images,
|
|
"patchified_vae_latent_shapes": patchified_vae_latent_shapes,
|
|
"packed_vae_position_ids": torch.cat(packed_vae_position_ids, dim=0),
|
|
"packed_timesteps": torch.tensor([timestep]),
|
|
"packed_vae_token_indexes": torch.tensor(packed_vae_token_indexes, dtype=torch.long),
|
|
"packed_text_ids": torch.tensor(packed_text_ids, dtype=torch.long),
|
|
"packed_text_indexes": torch.tensor(packed_text_indexes, dtype=torch.long),
|
|
"packed_position_ids": torch.tensor(packed_position_ids, dtype=torch.long),
|
|
"packed_seqlens": torch.tensor(packed_seqlens, dtype=torch.int),
|
|
}
|
|
|
|
return generation_input, newlens, new_rope
|
|
|
|
def forward_cache_update_vae(
|
|
self,
|
|
vae_model,
|
|
past_key_values: NaiveCache,
|
|
padded_images: torch.Tensor,
|
|
patchified_vae_latent_shapes: list,
|
|
packed_vae_position_ids: torch.LongTensor,
|
|
packed_timesteps: torch.Tensor,
|
|
packed_vae_token_indexes: torch.LongTensor,
|
|
packed_text_ids: torch.LongTensor,
|
|
packed_text_indexes: torch.LongTensor,
|
|
packed_position_ids: torch.LongTensor,
|
|
packed_seqlens: torch.IntTensor,
|
|
):
|
|
padded_latent = vae_model.encode(padded_images)
|
|
|
|
p = self.latent_patch_size
|
|
packed_latent = list()
|
|
for latent, (h, w) in zip(padded_latent, patchified_vae_latent_shapes):
|
|
latent = latent[:, : h * p, : w * p].reshape(self.latent_channel, h, p, w, p)
|
|
latent = torch.einsum("chpwq->hwpqc", latent).reshape(-1, p * p * self.latent_channel)
|
|
packed_latent.append(latent)
|
|
packed_latent = torch.cat(packed_latent, dim=0)
|
|
packed_pos_embed = self.latent_pos_embed(packed_vae_position_ids)
|
|
packed_timestep_embeds = self.time_embedder(packed_timesteps)
|
|
packed_latent = self.vae2llm(packed_latent) + packed_timestep_embeds + packed_pos_embed
|
|
|
|
# Mirror forward_cache_update_vit: build a full packed_sequence so MoE gen-mode
|
|
# indexes (packed_text_indexes / packed_vae_token_indexes) match tensor length.
|
|
packed_text_embedding = self.language_model.forward(
|
|
packed_text_ids=packed_text_ids,
|
|
return_embeddings_only=True,
|
|
).packed_query_sequence
|
|
packed_sequence = packed_text_embedding.new_zeros((sum(packed_seqlens), self.hidden_size))
|
|
packed_sequence[packed_text_indexes] = packed_text_embedding
|
|
if packed_latent.dtype != packed_sequence.dtype:
|
|
packed_latent = packed_latent.to(packed_sequence.dtype)
|
|
packed_sequence[packed_vae_token_indexes] = packed_latent
|
|
|
|
extra_inputs = {}
|
|
if self.use_moe:
|
|
extra_inputs = {
|
|
"mode": "gen",
|
|
"packed_vae_token_indexes": packed_vae_token_indexes,
|
|
"packed_text_indexes": packed_text_indexes,
|
|
}
|
|
|
|
output = self.language_model.forward(
|
|
packed_query_sequence=packed_sequence,
|
|
query_lens=packed_seqlens,
|
|
packed_query_position_ids=packed_position_ids,
|
|
past_key_values=past_key_values,
|
|
update_past_key_values=True,
|
|
is_causal=False,
|
|
**extra_inputs,
|
|
)
|
|
past_key_values = output.past_key_values
|
|
|
|
return past_key_values
|
|
|
|
def prepare_vit_images(self, curr_kvlens, curr_rope, images, transforms, new_token_ids):
|
|
packed_vit_token_indexes = list()
|
|
vit_token_seqlens, packed_vit_tokens, packed_vit_position_ids = list(), list(), list()
|
|
packed_text_ids, packed_text_indexes = list(), list()
|
|
packed_seqlens, packed_position_ids = list(), list()
|
|
|
|
_curr = 0
|
|
newlens, new_rope = list(), list()
|
|
for image, curr_kvlen, curr_position_id in zip(images, curr_kvlens, curr_rope):
|
|
packed_text_ids.append(new_token_ids["start_of_image"])
|
|
packed_text_indexes.append(_curr)
|
|
_curr += 1
|
|
|
|
image_tensor = transforms(image)
|
|
vit_position_ids = self.get_flattened_position_ids(
|
|
image_tensor.size(1),
|
|
image_tensor.size(2),
|
|
self.vit_patch_size,
|
|
max_num_patches_per_side=self.vit_max_num_patch_per_side,
|
|
)
|
|
vit_tokens = patchify(image_tensor, self.vit_patch_size)
|
|
packed_vit_tokens.append(vit_tokens)
|
|
num_img_tokens = vit_tokens.shape[0]
|
|
packed_vit_position_ids.append(vit_position_ids)
|
|
vit_token_seqlens.append(num_img_tokens)
|
|
packed_vit_token_indexes.extend(range(_curr, _curr + num_img_tokens))
|
|
_curr += num_img_tokens
|
|
|
|
packed_text_ids.append(new_token_ids["end_of_image"])
|
|
packed_text_indexes.append(_curr)
|
|
_curr += 1
|
|
|
|
packed_position_ids.extend([curr_position_id] * (num_img_tokens + 2))
|
|
packed_seqlens.append(num_img_tokens + 2)
|
|
newlens.append(curr_kvlen + num_img_tokens + 2)
|
|
new_rope.append(curr_position_id + 1)
|
|
|
|
generation_input = {
|
|
"packed_text_ids": torch.tensor(packed_text_ids, dtype=torch.long),
|
|
"packed_text_indexes": torch.tensor(packed_text_indexes, dtype=torch.long),
|
|
"vit_token_seqlens": torch.tensor(vit_token_seqlens, dtype=torch.int),
|
|
"packed_vit_tokens": torch.cat(packed_vit_tokens, dim=0),
|
|
"packed_vit_position_ids": torch.cat(packed_vit_position_ids, dim=0),
|
|
"packed_vit_token_indexes": torch.tensor(packed_vit_token_indexes, dtype=torch.long),
|
|
"packed_position_ids": torch.tensor(packed_position_ids, dtype=torch.long),
|
|
"packed_seqlens": torch.tensor(packed_seqlens, dtype=torch.int),
|
|
}
|
|
|
|
return generation_input, newlens, new_rope
|
|
|
|
def forward_cache_update_vit(
|
|
self,
|
|
past_key_values: NaiveCache,
|
|
packed_text_ids: torch.LongTensor,
|
|
packed_text_indexes: torch.LongTensor,
|
|
packed_vit_tokens: torch.Tensor,
|
|
packed_vit_token_indexes: torch.LongTensor,
|
|
packed_vit_position_ids: torch.LongTensor,
|
|
vit_token_seqlens: torch.IntTensor,
|
|
packed_position_ids: torch.LongTensor,
|
|
packed_seqlens: torch.IntTensor,
|
|
):
|
|
packed_text_embedding = self.language_model.forward(
|
|
packed_text_ids=packed_text_ids,
|
|
return_embeddings_only=True,
|
|
).packed_query_sequence
|
|
packed_sequence = packed_text_embedding.new_zeros((sum(packed_seqlens), self.hidden_size))
|
|
packed_sequence[packed_text_indexes] = packed_text_embedding
|
|
|
|
cu_seqlens = torch.nn.functional.pad(torch.cumsum(vit_token_seqlens, dim=0), (1, 0))
|
|
cu_seqlens = cu_seqlens.to(torch.int32)
|
|
max_seqlen = torch.max(vit_token_seqlens).item()
|
|
packed_vit_token_embed = self.vit_model(
|
|
packed_pixel_values=packed_vit_tokens,
|
|
packed_flattened_position_ids=packed_vit_position_ids,
|
|
cu_seqlens=cu_seqlens,
|
|
max_seqlen=max_seqlen,
|
|
)
|
|
packed_vit_token_embed = self.connector(packed_vit_token_embed)
|
|
pos_emb = self.vit_pos_embed(packed_vit_position_ids)
|
|
packed_vit_token_embed = packed_vit_token_embed + pos_emb
|
|
if packed_vit_token_embed.dtype != packed_sequence.dtype:
|
|
packed_vit_token_embed = packed_vit_token_embed.to(packed_sequence.dtype)
|
|
packed_sequence[packed_vit_token_indexes] = packed_vit_token_embed
|
|
|
|
extra_inputs = {}
|
|
if self.use_moe:
|
|
extra_inputs = {"mode": "und"}
|
|
|
|
output = self.language_model.forward(
|
|
packed_query_sequence=packed_sequence,
|
|
query_lens=packed_seqlens,
|
|
packed_query_position_ids=packed_position_ids,
|
|
past_key_values=past_key_values,
|
|
update_past_key_values=True,
|
|
is_causal=False,
|
|
**extra_inputs,
|
|
)
|
|
past_key_values = output.past_key_values
|
|
|
|
return past_key_values
|
|
|
|
def prepare_input(self, curr_kvlens, curr_rope, image_sizes, new_token_ids=None):
|
|
packed_text_ids, packed_text_indexes = list(), list()
|
|
packed_vae_position_ids, packed_vae_token_indexes, packed_init_noises = list(), list(), list()
|
|
packed_position_ids, packed_seqlens = list(), list()
|
|
|
|
query_curr = 0
|
|
for (H, W), curr_kvlen, curr_position_id in zip(image_sizes, curr_kvlens, curr_rope):
|
|
packed_text_ids.append(new_token_ids["start_of_image"])
|
|
packed_text_indexes.append(query_curr)
|
|
query_curr += 1
|
|
|
|
vae_position_ids = self.get_flattened_position_ids(
|
|
H, W, self.latent_downsample, max_num_patches_per_side=self.max_latent_size
|
|
)
|
|
packed_vae_position_ids.append(vae_position_ids)
|
|
|
|
h, w = H // self.latent_downsample, W // self.latent_downsample
|
|
num_image_tokens = h * w
|
|
|
|
packed_init_noises.append(torch.randn(num_image_tokens, self.latent_channel * self.latent_patch_size**2))
|
|
packed_vae_token_indexes.extend(range(query_curr, query_curr + num_image_tokens))
|
|
packed_seqlens.append(num_image_tokens + 2)
|
|
query_curr += num_image_tokens
|
|
|
|
packed_text_ids.append(new_token_ids["end_of_image"])
|
|
packed_text_indexes.append(query_curr)
|
|
query_curr += 1
|
|
|
|
packed_position_ids.extend([curr_position_id] * (num_image_tokens + 2))
|
|
|
|
# Construct Output
|
|
generation_input = {
|
|
"packed_text_ids": torch.tensor(packed_text_ids, dtype=torch.long),
|
|
"packed_text_indexes": torch.tensor(packed_text_indexes, dtype=torch.long),
|
|
"packed_init_noises": torch.cat(packed_init_noises, dim=0),
|
|
"packed_vae_position_ids": torch.cat(packed_vae_position_ids, dim=0),
|
|
"packed_vae_token_indexes": torch.tensor(packed_vae_token_indexes, dtype=torch.long),
|
|
"packed_seqlens": torch.tensor(packed_seqlens, dtype=torch.int),
|
|
"packed_position_ids": torch.tensor(packed_position_ids, dtype=torch.long),
|
|
}
|
|
|
|
return generation_input
|
|
|
|
def prepare_vae_latent(self, curr_kvlens, curr_rope, image_sizes, new_token_ids):
|
|
return self.prepare_input(curr_kvlens, curr_rope, image_sizes, new_token_ids)
|
|
|
|
def prepare_vae_latent_cfg(self, curr_kvlens, curr_rope, image_sizes):
|
|
packed_position_ids = list()
|
|
|
|
for (H, W), curr_kvlen, curr_position_id in zip(image_sizes, curr_kvlens, curr_rope):
|
|
h, w = H // self.latent_downsample, W // self.latent_downsample
|
|
num_image_tokens = h * w
|
|
packed_position_ids.extend([curr_position_id] * (num_image_tokens + 2))
|
|
|
|
generation_input = {
|
|
"cfg_packed_position_ids": torch.tensor(packed_position_ids, dtype=torch.long),
|
|
}
|
|
|
|
return generation_input
|
|
|
|
def prepare_start_tokens(self, curr_kvlens, curr_rope, new_token_ids):
|
|
"""Prepare start tokens for autoregressive text generation.
|
|
|
|
Ported from the original BAGEL ``Bagel.prepare_start_tokens``.
|
|
"""
|
|
packed_start_tokens = list()
|
|
packed_query_position_ids = list()
|
|
|
|
for curr_kvlen, curr_position_id in zip(curr_kvlens, curr_rope):
|
|
packed_start_tokens.append(new_token_ids["bos_token_id"])
|
|
packed_query_position_ids.append(curr_position_id)
|
|
generation_input = {
|
|
"packed_start_tokens": torch.tensor(packed_start_tokens, dtype=torch.long),
|
|
"packed_query_position_ids": torch.tensor(packed_query_position_ids, dtype=torch.long),
|
|
}
|
|
return generation_input
|
|
|
|
@torch.no_grad()
|
|
def generate_text(
|
|
self,
|
|
past_key_values: NaiveCache,
|
|
packed_start_tokens: torch.LongTensor,
|
|
packed_query_position_ids: torch.LongTensor,
|
|
max_length: int,
|
|
do_sample: bool = False,
|
|
temperature: float = 1.0,
|
|
end_token_id: int | None = None,
|
|
):
|
|
"""Autoregressive text generation (ported from original BAGEL).
|
|
|
|
Decodes tokens one at a time, appending to ``past_key_values``
|
|
until ``max_length`` is reached or ``end_token_id`` is generated.
|
|
"""
|
|
step = 0
|
|
generated_sequence = []
|
|
curr_tokens = packed_start_tokens
|
|
while step < max_length:
|
|
generated_sequence.append(curr_tokens)
|
|
query_lens = torch.ones_like(curr_tokens)
|
|
|
|
output = self.language_model(
|
|
packed_text_ids=curr_tokens,
|
|
query_lens=query_lens,
|
|
packed_query_position_ids=packed_query_position_ids,
|
|
past_key_values=past_key_values,
|
|
update_past_key_values=True,
|
|
is_causal=True,
|
|
mode="und",
|
|
)
|
|
past_key_values = output.past_key_values
|
|
packed_query_sequence = output.packed_query_sequence
|
|
pred_logits = self.language_model.lm_head(packed_query_sequence)
|
|
|
|
if do_sample:
|
|
probs = nn.functional.softmax(pred_logits / temperature, dim=-1)
|
|
curr_tokens = torch.multinomial(probs, num_samples=1).squeeze(1)
|
|
else:
|
|
curr_tokens = torch.argmax(pred_logits, dim=-1)
|
|
|
|
packed_query_position_ids = packed_query_position_ids + 1
|
|
step += 1
|
|
|
|
if end_token_id is not None and curr_tokens[0] == end_token_id:
|
|
break
|
|
|
|
output_device = generated_sequence[0].device
|
|
return torch.stack([i.to(output_device) for i in generated_sequence], dim=0)
|
|
|
|
def generate_image(
|
|
self,
|
|
packed_text_ids: torch.LongTensor,
|
|
packed_text_indexes: torch.LongTensor,
|
|
packed_init_noises: torch.Tensor,
|
|
packed_vae_position_ids: torch.LongTensor,
|
|
packed_vae_token_indexes: torch.LongTensor,
|
|
packed_seqlens: torch.IntTensor,
|
|
packed_position_ids: torch.LongTensor,
|
|
past_key_values: NaiveCache,
|
|
num_timesteps: int = 24,
|
|
timestep_shift: float = 1.0,
|
|
cfg_renorm_min: float = 0.0,
|
|
cfg_renorm_type: str = "global",
|
|
cfg_interval: tuple[float, float] = [0, 1],
|
|
# cfg_text
|
|
cfg_text_scale: float = 1.0,
|
|
cfg_text_packed_position_ids: torch.LongTensor | None = None,
|
|
cfg_text_past_key_values: NaiveCache | None = None,
|
|
# cfg_img
|
|
cfg_img_scale: float = 1.0,
|
|
cfg_img_packed_position_ids: torch.LongTensor | None = None,
|
|
cfg_img_past_key_values: NaiveCache | None = None,
|
|
return_trajectory_latents: bool = False,
|
|
scheduler: object | None = None,
|
|
scheduler_kwargs: dict | None = None,
|
|
# Lance i2v: tokens to freeze at their initial (encoded-image)
|
|
# value throughout the denoise loop. Matches upstream's
|
|
# ``mse_loss_indexes``-exclusion behaviour from PR #33: cond
|
|
# positions are never updated and get ``timestep=0``.
|
|
frame_condition_token_indexes: torch.LongTensor | None = None,
|
|
):
|
|
x_t = packed_init_noises
|
|
# Snapshot the pinned subtensor BEFORE the denoise loop touches
|
|
# x_t; the cond positions in packed_init_noises hold the
|
|
# VAE-encoded conditioning latent that must be preserved verbatim.
|
|
pinned_x_t = None
|
|
if frame_condition_token_indexes is not None:
|
|
frame_condition_token_indexes = frame_condition_token_indexes.to(x_t.device).long()
|
|
pinned_x_t = x_t[frame_condition_token_indexes].clone()
|
|
|
|
# Build ``num_timesteps`` flow-matching Euler updates over [1, 0].
|
|
timesteps, dts = Bagel.prepare_denoise_schedule(self, x_t, num_timesteps, timestep_shift)
|
|
|
|
# Optional trajectory recording for RL rollout data collection
|
|
trajectory_latents: list[torch.Tensor] | None = [] if return_trajectory_latents else None
|
|
trajectory_timesteps: list[torch.Tensor] | None = [] if return_trajectory_latents else None
|
|
trajectory_log_probs: list[torch.Tensor] | None = (
|
|
[] if (return_trajectory_latents and scheduler is not None) else None
|
|
)
|
|
_sched_kw = scheduler_kwargs or {}
|
|
|
|
use_cfg_text = cfg_text_scale > 1.0
|
|
use_cfg_img = cfg_img_scale > 1.0
|
|
|
|
# ── Detect CFG parallel mode ──
|
|
cfg_parallel_ready = use_cfg_text and get_classifier_free_guidance_world_size() > 1
|
|
|
|
if cfg_parallel_ready:
|
|
return self._generate_image_parallel(
|
|
x_t=x_t,
|
|
timesteps=timesteps,
|
|
dts=dts,
|
|
packed_text_ids=packed_text_ids,
|
|
packed_text_indexes=packed_text_indexes,
|
|
packed_vae_position_ids=packed_vae_position_ids,
|
|
packed_vae_token_indexes=packed_vae_token_indexes,
|
|
packed_seqlens=packed_seqlens,
|
|
packed_position_ids=packed_position_ids,
|
|
past_key_values=past_key_values,
|
|
cfg_renorm_min=cfg_renorm_min,
|
|
cfg_renorm_type=cfg_renorm_type,
|
|
cfg_interval=cfg_interval,
|
|
cfg_text_scale=cfg_text_scale,
|
|
cfg_text_packed_position_ids=cfg_text_packed_position_ids,
|
|
cfg_text_past_key_values=cfg_text_past_key_values,
|
|
cfg_img_scale=cfg_img_scale,
|
|
cfg_img_packed_position_ids=cfg_img_packed_position_ids,
|
|
cfg_img_past_key_values=cfg_img_past_key_values,
|
|
return_trajectory_latents=return_trajectory_latents,
|
|
scheduler=scheduler,
|
|
scheduler_kwargs=scheduler_kwargs,
|
|
)
|
|
|
|
# ── SP + CFG: sequential single-branch forwards ──
|
|
use_sp = self._sp_size > 1
|
|
if use_sp and use_cfg_text:
|
|
if return_trajectory_latents and len(timesteps) > 0:
|
|
trajectory_latents.append(x_t.clone())
|
|
for i, t in enumerate(timesteps.tolist()): # host floats; a 0-d tensor t would sync each step
|
|
timestep = torch.tensor([t] * x_t.shape[0], device=x_t.device)
|
|
if frame_condition_token_indexes is not None:
|
|
# Cond positions stay at t=0 (clean signal). Matches upstream
|
|
# PR #33 lance.py line 1605:
|
|
# timestep[current_vae_mse_indexes_local_in_vae] = t
|
|
# (cond positions remain at the ``torch.zeros`` init value).
|
|
timestep[frame_condition_token_indexes] = 0.0
|
|
in_cfg_window = t > cfg_interval[0] and t <= cfg_interval[1]
|
|
cfg_text_scale_ = cfg_text_scale if in_cfg_window else 1.0
|
|
cfg_img_scale_ = cfg_img_scale if in_cfg_window else 1.0
|
|
|
|
common = dict(
|
|
x_t=x_t,
|
|
timestep=timestep,
|
|
packed_vae_token_indexes=packed_vae_token_indexes,
|
|
packed_vae_position_ids=packed_vae_position_ids,
|
|
packed_text_ids=packed_text_ids,
|
|
packed_text_indexes=packed_text_indexes,
|
|
packed_seqlens=packed_seqlens,
|
|
)
|
|
|
|
v_t = self.forward_single_branch(
|
|
**common,
|
|
packed_position_ids=packed_position_ids,
|
|
past_key_values=past_key_values,
|
|
)
|
|
|
|
if cfg_text_scale_ > 1.0:
|
|
cfg_text_v_t = self.forward_single_branch(
|
|
**common,
|
|
packed_position_ids=cfg_text_packed_position_ids,
|
|
past_key_values=cfg_text_past_key_values,
|
|
)
|
|
cfg_img_v_t = None
|
|
if cfg_img_scale_ > 1.0:
|
|
cfg_img_v_t = self.forward_single_branch(
|
|
**common,
|
|
packed_position_ids=cfg_img_packed_position_ids,
|
|
past_key_values=cfg_img_past_key_values,
|
|
)
|
|
v_t = self._combine_cfg(
|
|
v_t,
|
|
cfg_text_v_t,
|
|
cfg_img_v_t,
|
|
cfg_text_scale_,
|
|
cfg_img_scale_,
|
|
cfg_renorm_type,
|
|
cfg_renorm_min,
|
|
)
|
|
|
|
if scheduler is not None:
|
|
out = scheduler.step(v_t.to(x_t.device), timesteps[i], x_t, dts[i], **_sched_kw)
|
|
x_t = out.prev_sample
|
|
if trajectory_log_probs is not None and out.log_prob is not None:
|
|
trajectory_log_probs.append(out.log_prob)
|
|
else:
|
|
x_t = x_t - v_t.to(x_t.device) * dts[i]
|
|
if return_trajectory_latents:
|
|
trajectory_latents.append(x_t.clone())
|
|
trajectory_timesteps.append(timesteps[i])
|
|
|
|
unpacked_latent = x_t.split((packed_seqlens - 2).tolist())
|
|
return unpacked_latent, trajectory_latents, trajectory_timesteps, trajectory_log_probs
|
|
|
|
# ── SP without CFG: direct single-branch loop ──
|
|
if use_sp:
|
|
if return_trajectory_latents and len(timesteps) > 0:
|
|
trajectory_latents.append(x_t.clone())
|
|
for i, t in enumerate(timesteps.tolist()): # host floats; a 0-d tensor t would sync each step
|
|
timestep = torch.tensor([t] * x_t.shape[0], device=x_t.device)
|
|
if frame_condition_token_indexes is not None:
|
|
# Cond positions stay at t=0 (clean signal). Matches upstream
|
|
# PR #33 lance.py line 1605:
|
|
# timestep[current_vae_mse_indexes_local_in_vae] = t
|
|
# (cond positions remain at the ``torch.zeros`` init value).
|
|
timestep[frame_condition_token_indexes] = 0.0
|
|
v_t = self.forward_single_branch(
|
|
x_t=x_t,
|
|
timestep=timestep,
|
|
packed_vae_token_indexes=packed_vae_token_indexes,
|
|
packed_vae_position_ids=packed_vae_position_ids,
|
|
packed_text_ids=packed_text_ids,
|
|
packed_text_indexes=packed_text_indexes,
|
|
packed_position_ids=packed_position_ids,
|
|
packed_seqlens=packed_seqlens,
|
|
past_key_values=past_key_values,
|
|
)
|
|
if scheduler is not None:
|
|
out = scheduler.step(v_t.to(x_t.device), timesteps[i], x_t, dts[i], **_sched_kw)
|
|
x_t = out.prev_sample
|
|
out_log_prob = getattr(out, "log_prob", None)
|
|
if trajectory_log_probs is not None and out_log_prob is not None:
|
|
trajectory_log_probs.append(out_log_prob)
|
|
else:
|
|
x_t = x_t - v_t.to(x_t.device) * dts[i]
|
|
if return_trajectory_latents:
|
|
trajectory_latents.append(x_t.clone())
|
|
trajectory_timesteps.append(timesteps[i])
|
|
|
|
unpacked_latent = x_t.split((packed_seqlens - 2).tolist())
|
|
return unpacked_latent, trajectory_latents, trajectory_timesteps, trajectory_log_probs
|
|
|
|
# ── Sequential CFG mode (cfg_parallel_size=1, no SP) ──
|
|
# Each CFG branch runs its own LLM forward; we just need the
|
|
# per-branch packed_position_ids and past_key_values for
|
|
# ``Bagel.forward`` to dispatch through.
|
|
cfg_branch_pids: list[torch.Tensor] | None = None
|
|
cfg_branch_caches: list[NaiveCache] | None = None
|
|
|
|
if use_cfg_text:
|
|
cfg_branch_pids = [packed_position_ids, cfg_text_packed_position_ids]
|
|
cfg_branch_caches = [past_key_values, cfg_text_past_key_values]
|
|
if use_cfg_img:
|
|
cfg_branch_pids.append(cfg_img_packed_position_ids)
|
|
cfg_branch_caches.append(cfg_img_past_key_values)
|
|
|
|
if return_trajectory_latents and len(timesteps) > 0:
|
|
trajectory_latents.append(x_t.clone())
|
|
|
|
for i, t in enumerate(timesteps.tolist()): # host floats; a 0-d tensor t would sync each step
|
|
timestep = torch.tensor([t] * x_t.shape[0], device=x_t.device)
|
|
if frame_condition_token_indexes is not None:
|
|
# Cond positions stay at t=0 (clean signal). Matches upstream
|
|
# PR #33 lance.py line 1605:
|
|
# timestep[current_vae_mse_indexes_local_in_vae] = t
|
|
# (cond positions remain at the ``torch.zeros`` init value).
|
|
timestep[frame_condition_token_indexes] = 0.0
|
|
if t > cfg_interval[0] and t <= cfg_interval[1]:
|
|
cfg_text_scale_ = cfg_text_scale
|
|
cfg_img_scale_ = cfg_img_scale
|
|
else:
|
|
cfg_text_scale_ = 1.0
|
|
cfg_img_scale_ = 1.0
|
|
v_t = self.forward(
|
|
x_t=x_t,
|
|
timestep=timestep,
|
|
packed_vae_token_indexes=packed_vae_token_indexes,
|
|
packed_vae_position_ids=packed_vae_position_ids,
|
|
packed_text_ids=packed_text_ids,
|
|
packed_text_indexes=packed_text_indexes,
|
|
packed_position_ids=packed_position_ids,
|
|
packed_seqlens=packed_seqlens,
|
|
past_key_values=past_key_values,
|
|
cfg_renorm_min=cfg_renorm_min,
|
|
cfg_renorm_type=cfg_renorm_type,
|
|
cfg_text_scale=cfg_text_scale_,
|
|
cfg_img_scale=cfg_img_scale_,
|
|
cfg_branch_pids=cfg_branch_pids,
|
|
cfg_branch_caches=cfg_branch_caches,
|
|
)
|
|
|
|
if scheduler is not None:
|
|
out = scheduler.step(v_t.to(x_t.device), timesteps[i], x_t, dts[i], **_sched_kw)
|
|
x_t = out.prev_sample
|
|
if trajectory_log_probs is not None and out.log_prob is not None:
|
|
trajectory_log_probs.append(out.log_prob)
|
|
else:
|
|
x_t = x_t - v_t.to(x_t.device) * dts[i] # velocity pointing from data to noise
|
|
if pinned_x_t is not None:
|
|
# i2v: restore cond positions to their encoded-image
|
|
# latent. Matches upstream PR #33 lance.py line 1712:
|
|
# x_t[mse_indexes] = x_t[mse_indexes] - v_t[mse_indexes] * dts[i]
|
|
# (cond positions are excluded from the update).
|
|
x_t[frame_condition_token_indexes] = pinned_x_t
|
|
if return_trajectory_latents:
|
|
trajectory_latents.append(x_t.clone())
|
|
trajectory_timesteps.append(timesteps[i])
|
|
|
|
unpacked_latent = x_t.split((packed_seqlens - 2).tolist())
|
|
return unpacked_latent, trajectory_latents, trajectory_timesteps, trajectory_log_probs
|
|
|
|
def _generate_image_parallel(
|
|
self,
|
|
x_t: torch.Tensor,
|
|
timesteps: torch.Tensor,
|
|
dts: torch.Tensor,
|
|
packed_text_ids: torch.LongTensor,
|
|
packed_text_indexes: torch.LongTensor,
|
|
packed_vae_position_ids: torch.LongTensor,
|
|
packed_vae_token_indexes: torch.LongTensor,
|
|
packed_seqlens: torch.IntTensor,
|
|
packed_position_ids: torch.LongTensor,
|
|
past_key_values: NaiveCache,
|
|
cfg_renorm_min: float,
|
|
cfg_renorm_type: str,
|
|
cfg_interval: tuple[float, float],
|
|
cfg_text_scale: float,
|
|
cfg_text_packed_position_ids: torch.LongTensor | None,
|
|
cfg_text_past_key_values: NaiveCache | None,
|
|
cfg_img_scale: float,
|
|
cfg_img_packed_position_ids: torch.LongTensor | None,
|
|
cfg_img_past_key_values: NaiveCache | None,
|
|
return_trajectory_latents: bool = False,
|
|
scheduler: object | None = None,
|
|
scheduler_kwargs: dict | None = None,
|
|
frame_condition_token_indexes: torch.LongTensor | None = None,
|
|
):
|
|
"""CFG parallel denoising loop: each rank computes one CFG branch.
|
|
|
|
Rank 0: gen branch (full conditioning)
|
|
Rank 1: text_cfg branch (unconditional text)
|
|
Rank 2: img_cfg branch (no image condition), only when cfg_img_scale > 1.0
|
|
"""
|
|
cfg_group = get_cfg_group()
|
|
cfg_world_size = get_classifier_free_guidance_world_size()
|
|
use_cfg_img = cfg_img_scale > 1.0
|
|
|
|
# Validate cfg_parallel_size vs cfg_img_scale consistency
|
|
if cfg_world_size == 3 and not use_cfg_img:
|
|
raise ValueError(
|
|
f"cfg_parallel_size=3 requires cfg_img_scale > 1.0, "
|
|
f"but got cfg_img_scale={cfg_img_scale}. "
|
|
f"Use cfg_parallel_size=2 for text-only CFG parallel(text2img), or set cfg_img_scale > 1.0."
|
|
)
|
|
if cfg_world_size == 2 and use_cfg_img:
|
|
raise ValueError(
|
|
f"Image CFG (cfg_img_scale={cfg_img_scale}) requires cfg_parallel_size=3, "
|
|
f"but got cfg_parallel_size=2. "
|
|
f"Use cfg_parallel_size=3 to enable image CFG in parallel mode."
|
|
)
|
|
|
|
# Ensure all ranks start with the same x_t (initial noise may differ
|
|
# across ranks when no per-request seed is set).
|
|
x_t = x_t.contiguous()
|
|
cfg_group.broadcast(x_t, src=0)
|
|
|
|
trajectory_latents: list[torch.Tensor] | None = [] if return_trajectory_latents else None
|
|
trajectory_timesteps: list[torch.Tensor] | None = [] if return_trajectory_latents else None
|
|
trajectory_log_probs: list[torch.Tensor] | None = (
|
|
[] if (return_trajectory_latents and scheduler is not None) else None
|
|
)
|
|
_sched_kw = scheduler_kwargs or {}
|
|
|
|
if return_trajectory_latents and len(timesteps) > 0:
|
|
trajectory_latents.append(x_t.clone())
|
|
|
|
for i, t in enumerate(timesteps.tolist()): # host floats; a 0-d tensor t would sync each step
|
|
timestep = torch.tensor([t] * x_t.shape[0], device=x_t.device)
|
|
if frame_condition_token_indexes is not None:
|
|
# Cond positions stay at t=0 (clean signal). Matches upstream
|
|
# PR #33 lance.py line 1605:
|
|
# timestep[current_vae_mse_indexes_local_in_vae] = t
|
|
# (cond positions remain at the ``torch.zeros`` init value).
|
|
timestep[frame_condition_token_indexes] = 0.0
|
|
use_cfg_this_step = t > cfg_interval[0] and t <= cfg_interval[1] and cfg_text_scale > 1.0
|
|
|
|
# Per-branch kwargs. Branch 0 (gen, full conditioning) is also the
|
|
# branch used by all ranks when do_true_cfg is False (outside the
|
|
# CFG interval) — CFGParallelMixin only runs branches_kwargs[0] then,
|
|
# mirroring the previous "all ranks compute gen inputs, no comm" path.
|
|
common = dict(
|
|
x_t=x_t,
|
|
timestep=timestep,
|
|
packed_vae_token_indexes=packed_vae_token_indexes,
|
|
packed_vae_position_ids=packed_vae_position_ids,
|
|
packed_text_ids=packed_text_ids,
|
|
packed_text_indexes=packed_text_indexes,
|
|
packed_seqlens=packed_seqlens,
|
|
)
|
|
branches_kwargs = [
|
|
dict(**common, packed_position_ids=packed_position_ids, past_key_values=past_key_values),
|
|
dict(
|
|
**common, packed_position_ids=cfg_text_packed_position_ids, past_key_values=cfg_text_past_key_values
|
|
),
|
|
]
|
|
if use_cfg_img:
|
|
branches_kwargs.append(
|
|
dict(
|
|
**common,
|
|
packed_position_ids=cfg_img_packed_position_ids,
|
|
past_key_values=cfg_img_past_key_values,
|
|
)
|
|
)
|
|
|
|
# Each rank computes its assigned branch, then all_gather + combine
|
|
# happen inside the mixin (identical result on every rank).
|
|
v_t = self.predict_noise_with_multi_branch_cfg(
|
|
do_true_cfg=use_cfg_this_step,
|
|
true_cfg_scale={
|
|
"cfg_text_scale": cfg_text_scale,
|
|
"cfg_img_scale": cfg_img_scale,
|
|
"cfg_renorm_type": cfg_renorm_type,
|
|
"cfg_renorm_min": cfg_renorm_min,
|
|
},
|
|
branches_kwargs=branches_kwargs,
|
|
)
|
|
|
|
if scheduler is not None:
|
|
out = scheduler.step(v_t.to(x_t.device), timesteps[i], x_t, dts[i], **_sched_kw)
|
|
x_t = out.prev_sample
|
|
if trajectory_log_probs is not None and out.log_prob is not None:
|
|
trajectory_log_probs.append(out.log_prob)
|
|
else:
|
|
x_t = x_t - v_t.to(x_t.device) * dts[i]
|
|
if return_trajectory_latents:
|
|
trajectory_latents.append(x_t.clone())
|
|
trajectory_timesteps.append(timesteps[i])
|
|
|
|
unpacked_latent = x_t.split((packed_seqlens - 2).tolist())
|
|
return unpacked_latent, trajectory_latents, trajectory_timesteps, trajectory_log_probs
|
|
|
|
@staticmethod
|
|
def _combine_cfg(
|
|
v_t: torch.Tensor,
|
|
cfg_text_v_t: torch.Tensor,
|
|
cfg_img_v_t: torch.Tensor | None,
|
|
cfg_text_scale: float,
|
|
cfg_img_scale: float,
|
|
cfg_renorm_type: str,
|
|
cfg_renorm_min: float,
|
|
) -> torch.Tensor:
|
|
"""Combine 3-branch CFG predictions with renormalization.
|
|
|
|
Args:
|
|
v_t: velocity from gen branch (full conditioning)
|
|
cfg_text_v_t: velocity from text_cfg branch (unconditional text)
|
|
cfg_img_v_t: velocity from img_cfg branch (no image), or None
|
|
cfg_text_scale: text guidance scale
|
|
cfg_img_scale: image guidance scale
|
|
cfg_renorm_type: "text_channel", "global", or "channel"
|
|
cfg_renorm_min: minimum renormalization scale
|
|
"""
|
|
if cfg_renorm_type == "text_channel":
|
|
v_t_text_ = cfg_text_v_t + cfg_text_scale * (v_t - cfg_text_v_t)
|
|
norm_v_t = torch.norm(v_t, dim=-1, keepdim=True)
|
|
norm_v_t_text_ = torch.norm(v_t_text_, dim=-1, keepdim=True)
|
|
scale = (norm_v_t / (norm_v_t_text_ + 1e-8)).clamp(min=cfg_renorm_min, max=1.0)
|
|
v_t_text = v_t_text_ * scale
|
|
if cfg_img_scale > 1.0 and cfg_img_v_t is not None:
|
|
v_t = cfg_img_v_t + cfg_img_scale * (v_t_text - cfg_img_v_t)
|
|
else:
|
|
v_t = v_t_text
|
|
else:
|
|
v_t_text_ = cfg_text_v_t + cfg_text_scale * (v_t - cfg_text_v_t)
|
|
|
|
if cfg_img_scale > 1.0 and cfg_img_v_t is not None:
|
|
v_t_ = cfg_img_v_t + cfg_img_scale * (v_t_text_ - cfg_img_v_t)
|
|
else:
|
|
v_t_ = v_t_text_
|
|
|
|
# NOTE norm is computed over all dimensions, thus currently only supports batch_size = 1 with navit
|
|
if cfg_renorm_type == "global":
|
|
norm_v_t = torch.norm(v_t)
|
|
norm_v_t_ = torch.norm(v_t_)
|
|
elif cfg_renorm_type == "channel":
|
|
norm_v_t = torch.norm(v_t, dim=-1, keepdim=True)
|
|
norm_v_t_ = torch.norm(v_t_, dim=-1, keepdim=True)
|
|
else:
|
|
raise NotImplementedError(f"{cfg_renorm_type} is not supported")
|
|
scale = (norm_v_t / (norm_v_t_ + 1e-8)).clamp(min=cfg_renorm_min, max=1.0)
|
|
v_t = v_t_ * scale
|
|
|
|
return v_t
|
|
|
|
def _combine_cfg_per_request(
|
|
self,
|
|
v_t: torch.Tensor,
|
|
cfg_text_v_t: torch.Tensor,
|
|
cfg_img_v_t: torch.Tensor | None,
|
|
cfg_vae_lengths: Sequence[int],
|
|
cfg_text_scales: Sequence[float],
|
|
cfg_img_scales: Sequence[float],
|
|
cfg_renorm_type: str,
|
|
cfg_renorm_min: float,
|
|
) -> torch.Tensor:
|
|
"""Apply BAGEL CFG independently to each packed request."""
|
|
if len(cfg_text_scales) != len(cfg_vae_lengths):
|
|
raise ValueError("cfg_text_scales must match cfg_vae_lengths.")
|
|
if len(cfg_img_scales) != len(cfg_vae_lengths):
|
|
raise ValueError("cfg_img_scales must match cfg_vae_lengths.")
|
|
|
|
gen_parts = v_t.split(list(cfg_vae_lengths))
|
|
text_parts = cfg_text_v_t.split(list(cfg_vae_lengths))
|
|
img_parts = (
|
|
cfg_img_v_t.split(list(cfg_vae_lengths)) if cfg_img_v_t is not None else [None] * len(cfg_vae_lengths)
|
|
)
|
|
return torch.cat(
|
|
[
|
|
gen_part
|
|
if text_scale <= 1.0
|
|
else self._combine_cfg(
|
|
gen_part,
|
|
text_part,
|
|
img_part,
|
|
text_scale,
|
|
img_scale,
|
|
cfg_renorm_type,
|
|
cfg_renorm_min,
|
|
)
|
|
for gen_part, text_part, img_part, text_scale, img_scale in zip(
|
|
gen_parts,
|
|
text_parts,
|
|
img_parts,
|
|
cfg_text_scales,
|
|
cfg_img_scales,
|
|
strict=True,
|
|
)
|
|
]
|
|
)
|
|
|
|
# ── CFGParallelMixin hooks ──
|
|
# Bagel mounts CFGParallelMixin (see class declaration) to reuse the shared
|
|
# N-branch CFG dispatch/all_gather logic in predict_noise_with_multi_branch_cfg.
|
|
# Only two hooks need model-specific behaviour:
|
|
# * predict_noise: one branch == one Bagel forward (per-branch KV cache).
|
|
# * combine_multi_branch_cfg_noise: Bagel's renorm-aware 3-branch combine.
|
|
|
|
def predict_noise(self, **kwargs) -> torch.Tensor:
|
|
"""Single-branch velocity prediction for CFGParallelMixin.
|
|
|
|
Each CFG branch differs only by ``packed_position_ids`` and
|
|
``past_key_values`` (carried in ``kwargs``); the heavy lifting is the
|
|
per-branch ``forward_single_branch`` pass.
|
|
"""
|
|
return self.forward_single_branch(**kwargs)
|
|
|
|
def combine_multi_branch_cfg_noise(
|
|
self,
|
|
predictions: list[torch.Tensor],
|
|
true_cfg_scale: dict[str, Any],
|
|
cfg_normalize: bool = False,
|
|
) -> torch.Tensor:
|
|
"""Combine gen/text/img branch velocities via Bagel's renorm CFG.
|
|
|
|
``predictions[0]`` is the gen branch, ``[1]`` the text-CFG branch, and
|
|
``[2]`` (when present) the image-CFG branch. ``cfg_normalize`` is unused
|
|
because renormalization is folded into ``_combine_cfg`` itself.
|
|
"""
|
|
cfg_img_v_t = predictions[2] if len(predictions) > 2 else None
|
|
cfg_vae_lengths = true_cfg_scale.get("cfg_vae_lengths")
|
|
if cfg_vae_lengths is not None:
|
|
return self._combine_cfg_per_request(
|
|
predictions[0],
|
|
predictions[1],
|
|
cfg_img_v_t,
|
|
cfg_vae_lengths,
|
|
true_cfg_scale["cfg_text_scales"],
|
|
true_cfg_scale["cfg_img_scales"],
|
|
true_cfg_scale["cfg_renorm_type"],
|
|
true_cfg_scale["cfg_renorm_min"],
|
|
)
|
|
return self._combine_cfg(
|
|
predictions[0],
|
|
predictions[1],
|
|
cfg_img_v_t,
|
|
true_cfg_scale["cfg_text_scale"],
|
|
true_cfg_scale["cfg_img_scale"],
|
|
true_cfg_scale["cfg_renorm_type"],
|
|
true_cfg_scale["cfg_renorm_min"],
|
|
)
|
|
|
|
def forward_single_branch(
|
|
self,
|
|
x_t: torch.Tensor,
|
|
timestep: torch.Tensor,
|
|
packed_vae_token_indexes: torch.LongTensor,
|
|
packed_vae_position_ids: torch.LongTensor,
|
|
packed_text_ids: torch.LongTensor,
|
|
packed_text_indexes: torch.LongTensor,
|
|
packed_position_ids: torch.LongTensor,
|
|
packed_seqlens: torch.IntTensor,
|
|
past_key_values: NaiveCache,
|
|
) -> torch.Tensor:
|
|
"""Run a single-branch forward pass (no CFG batching).
|
|
|
|
Used by CFG parallel mode where each rank computes one branch.
|
|
Returns the velocity v_t for the given branch.
|
|
Supports Ulysses / Ring SP when parallel_config.sequence_parallel_size > 1.
|
|
"""
|
|
use_sp = self._sp_size > 1
|
|
|
|
if use_sp:
|
|
# Split VAE tokens across SP ranks
|
|
(
|
|
local_x_t,
|
|
local_vae_pos_ids,
|
|
local_vae_indexes,
|
|
local_text_indexes,
|
|
local_seqlens,
|
|
local_position_ids,
|
|
) = self._split_vae_for_sp(
|
|
x_t,
|
|
packed_vae_position_ids,
|
|
packed_vae_token_indexes,
|
|
packed_text_indexes,
|
|
packed_seqlens,
|
|
packed_position_ids,
|
|
)
|
|
|
|
packed_text_embedding = self.language_model.forward(
|
|
packed_text_ids=packed_text_ids,
|
|
return_embeddings_only=True,
|
|
).packed_query_sequence
|
|
packed_sequence = packed_text_embedding.new_zeros((int(local_seqlens.sum()), self.hidden_size))
|
|
packed_sequence[local_text_indexes] = packed_text_embedding
|
|
|
|
# i2v relaxes this: per-token timestep (cond=0, noncond=t) is valid.
|
|
packed_pos_embed = self.latent_pos_embed(local_vae_pos_ids)
|
|
local_timestep = timestep[: local_x_t.shape[0]]
|
|
packed_timestep_embeds = self.time_embedder(local_timestep)
|
|
x_t_emb = self.vae2llm(local_x_t) + packed_timestep_embeds + packed_pos_embed
|
|
if x_t_emb.dtype != packed_sequence.dtype:
|
|
x_t_emb = x_t_emb.to(packed_sequence.dtype)
|
|
packed_sequence[local_vae_indexes] = x_t_emb
|
|
|
|
extra_inputs = {}
|
|
if self.use_moe:
|
|
extra_inputs["mode"] = "gen"
|
|
extra_inputs["packed_vae_token_indexes"] = local_vae_indexes
|
|
extra_inputs["packed_text_indexes"] = local_text_indexes
|
|
|
|
output = self.language_model.forward(
|
|
packed_query_sequence=packed_sequence,
|
|
query_lens=local_seqlens,
|
|
packed_query_position_ids=local_position_ids,
|
|
past_key_values=past_key_values,
|
|
update_past_key_values=False,
|
|
is_causal=False,
|
|
**extra_inputs,
|
|
)
|
|
|
|
local_v_t = self.llm2vae(output.packed_query_sequence)
|
|
local_v_t = local_v_t[local_vae_indexes]
|
|
return self._gather_vae_for_sp(local_v_t)
|
|
|
|
# Original non-SP path
|
|
packed_text_embedding = self.language_model.forward(
|
|
packed_text_ids=packed_text_ids,
|
|
return_embeddings_only=True,
|
|
).packed_query_sequence
|
|
packed_sequence = packed_text_embedding.new_zeros((sum(packed_seqlens), self.hidden_size))
|
|
packed_sequence[packed_text_indexes] = packed_text_embedding
|
|
|
|
# i2v relaxes this: per-token timestep (cond=0, noncond=t) is valid.
|
|
packed_pos_embed = self.latent_pos_embed(packed_vae_position_ids)
|
|
packed_timestep_embeds = self.time_embedder(timestep)
|
|
x_t_emb = self.vae2llm(x_t) + packed_timestep_embeds + packed_pos_embed
|
|
if x_t_emb.dtype != packed_sequence.dtype:
|
|
x_t_emb = x_t_emb.to(packed_sequence.dtype)
|
|
packed_sequence[packed_vae_token_indexes] = x_t_emb
|
|
|
|
extra_inputs = {}
|
|
if self.use_moe:
|
|
extra_inputs["mode"] = "gen"
|
|
extra_inputs["packed_vae_token_indexes"] = packed_vae_token_indexes
|
|
extra_inputs["packed_text_indexes"] = packed_text_indexes
|
|
|
|
output = self.language_model.forward(
|
|
packed_query_sequence=packed_sequence,
|
|
query_lens=packed_seqlens,
|
|
packed_query_position_ids=packed_position_ids,
|
|
past_key_values=past_key_values,
|
|
update_past_key_values=False,
|
|
is_causal=False,
|
|
**extra_inputs,
|
|
)
|
|
v_t = self.llm2vae(output.packed_query_sequence)
|
|
v_t = v_t[packed_vae_token_indexes]
|
|
return v_t
|
|
|
|
def forward(
|
|
self,
|
|
x_t: torch.Tensor,
|
|
timestep: torch.Tensor,
|
|
packed_vae_token_indexes: torch.LongTensor,
|
|
packed_vae_position_ids: torch.LongTensor,
|
|
packed_text_ids: torch.LongTensor,
|
|
packed_text_indexes: torch.LongTensor,
|
|
packed_position_ids: torch.LongTensor,
|
|
packed_seqlens: torch.IntTensor,
|
|
past_key_values: NaiveCache,
|
|
cfg_renorm_min: float = 0.0,
|
|
cfg_renorm_type: str = "global",
|
|
cfg_text_scale: float = 1.0,
|
|
cfg_img_scale: float = 1.0,
|
|
cfg_branch_pids: list[torch.Tensor] | None = None,
|
|
cfg_branch_caches: list[NaiveCache] | None = None,
|
|
cfg_vae_lengths: list[int] | None = None,
|
|
cfg_text_scales: list[float] | None = None,
|
|
cfg_img_scales: list[float] | None = None,
|
|
):
|
|
# Build query sequence (identical for all CFG branches)
|
|
packed_text_embedding = self.language_model.forward(
|
|
packed_text_ids=packed_text_ids,
|
|
return_embeddings_only=True,
|
|
).packed_query_sequence
|
|
packed_sequence = packed_text_embedding.new_zeros((sum(packed_seqlens), self.hidden_size))
|
|
packed_sequence[packed_text_indexes] = packed_text_embedding
|
|
|
|
# i2v relaxes this: per-token timestep (cond=0, noncond=t) is valid.
|
|
packed_pos_embed = self.latent_pos_embed(packed_vae_position_ids)
|
|
packed_timestep_embeds = self.time_embedder(timestep)
|
|
x_t = self.vae2llm(x_t) + packed_timestep_embeds + packed_pos_embed
|
|
if x_t.dtype != packed_sequence.dtype:
|
|
x_t = x_t.to(packed_sequence.dtype)
|
|
packed_sequence[packed_vae_token_indexes] = x_t
|
|
|
|
extra_inputs = {}
|
|
if self.use_moe:
|
|
extra_inputs["mode"] = "gen"
|
|
extra_inputs["packed_vae_token_indexes"] = packed_vae_token_indexes
|
|
extra_inputs["packed_text_indexes"] = packed_text_indexes
|
|
|
|
has_cfg_branches = cfg_branch_pids is not None and cfg_branch_caches is not None
|
|
use_cfg = has_cfg_branches and (
|
|
cfg_text_scale > 1.0 or (cfg_text_scales is not None and any(scale > 1.0 for scale in cfg_text_scales))
|
|
)
|
|
cfg_text_v_t = None
|
|
cfg_img_v_t = None
|
|
|
|
if use_cfg:
|
|
assert cfg_branch_pids is not None
|
|
assert cfg_branch_caches is not None
|
|
num_branches = len(cfg_branch_pids)
|
|
seq_len = int(packed_seqlens.sum())
|
|
|
|
batched_sequence = packed_sequence.repeat(num_branches, 1)
|
|
batched_vae_indexes = torch.cat([packed_vae_token_indexes + i * seq_len for i in range(num_branches)])
|
|
batched_position_ids = torch.cat(cfg_branch_pids, dim=1 if cfg_branch_pids[0].ndim == 2 else 0)
|
|
batched_seqlens = packed_seqlens.repeat(num_branches)
|
|
merged_cache = NaiveCache.merge(cfg_branch_caches)
|
|
|
|
if self.use_moe:
|
|
batched_text_indices = torch.cat([packed_text_indexes + i * seq_len for i in range(num_branches)])
|
|
extra_inputs["packed_vae_token_indexes"] = batched_vae_indexes
|
|
extra_inputs["packed_text_indexes"] = batched_text_indices
|
|
|
|
output = self.language_model.forward(
|
|
packed_query_sequence=batched_sequence,
|
|
query_lens=batched_seqlens,
|
|
packed_query_position_ids=batched_position_ids,
|
|
past_key_values=merged_cache,
|
|
update_past_key_values=False,
|
|
is_causal=False,
|
|
**extra_inputs,
|
|
)
|
|
|
|
all_vae_v_t = self.llm2vae(output.packed_query_sequence)[batched_vae_indexes]
|
|
vae_per_branch = packed_vae_token_indexes.shape[0]
|
|
branch_v_ts = all_vae_v_t.split(vae_per_branch)
|
|
v_t = branch_v_ts[0]
|
|
cfg_text_v_t = branch_v_ts[1]
|
|
cfg_img_v_t = branch_v_ts[2] if len(branch_v_ts) > 2 else None
|
|
else:
|
|
# Single forward (no CFG or outside cfg_interval).
|
|
output = self.language_model.forward(
|
|
packed_query_sequence=packed_sequence,
|
|
query_lens=packed_seqlens,
|
|
packed_query_position_ids=packed_position_ids,
|
|
past_key_values=past_key_values,
|
|
update_past_key_values=False,
|
|
is_causal=False,
|
|
**extra_inputs,
|
|
)
|
|
v_t = self.llm2vae(output.packed_query_sequence)[packed_vae_token_indexes]
|
|
|
|
# ── CFG combination ──
|
|
if use_cfg:
|
|
if cfg_vae_lengths is None:
|
|
v_t = self._combine_cfg(
|
|
v_t,
|
|
cfg_text_v_t,
|
|
cfg_img_v_t,
|
|
cfg_text_scale,
|
|
cfg_img_scale,
|
|
cfg_renorm_type,
|
|
cfg_renorm_min,
|
|
)
|
|
else:
|
|
if cfg_text_scales is None:
|
|
raise ValueError("cfg_text_scales must be provided with cfg_vae_lengths.")
|
|
if cfg_img_scales is None:
|
|
cfg_img_scales = [cfg_img_scale] * len(cfg_vae_lengths)
|
|
v_t = self._combine_cfg_per_request(
|
|
v_t,
|
|
cfg_text_v_t,
|
|
cfg_img_v_t,
|
|
cfg_vae_lengths,
|
|
cfg_text_scales,
|
|
cfg_img_scales,
|
|
cfg_renorm_type,
|
|
cfg_renorm_min,
|
|
)
|
|
|
|
return v_t
|