multimodalart's picture
multimodalart HF Staff
Bernini-Diffusers-v2 r2v demo
fed6c68 verified
Raw
History Blame Contribute Delete
23.6 kB
# Copyright (c) 2026 Bytedance Ltd. and/or its affiliate
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import torch
import torch.nn as nn
import torch.distributed as dist
import os
from transformers import UMT5EncoderModel, Qwen2_5_VLConfig
from transformers.configuration_utils import PretrainedConfig
from transformers.modeling_utils import PreTrainedModel
from transformers.utils import logging
from .diffloss_fm import DiffLoss_FM
from .wan_diffusion import GEN_Wanx22
from .modeling_qwen2_5_vl import Qwen2_5_VLForConditionalGeneration
logger = logging.get_logger(__name__)
def _join_subfolder(base_subfolder, leaf):
if base_subfolder:
return f"{base_subfolder}/{leaf}"
return leaf
class RMSNorm(nn.Module):
def __init__(self, dim, eps=1e-6):
super().__init__()
self.weight = nn.Parameter(torch.ones(dim))
self.eps = eps
def forward(self, x):
dtype = x.dtype
x = x.float()
x = x * torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps)
return (x * self.weight).to(dtype)
class MLPConnector(nn.Module):
"""Local connector implementation used by the Bernini checkpoint.
Keep the same parameter layout as the original training connector so the
released checkpoint loads without the external training package.
"""
def __init__(
self,
in_dim,
num_layers_for_gen=1,
out_dim_for_gen=4096,
enable_gen_branch=True,
gen_head_type="mlp",
num_layers_for_vit=1,
out_dim_for_vit=3584,
enable_vit_branch=True,
):
super().__init__()
self.enable_gen_branch = enable_gen_branch
self.enable_vit_branch = enable_vit_branch
if enable_gen_branch:
self.proj_gen = nn.Sequential(
nn.Linear(in_dim, out_dim_for_gen),
nn.GELU(),
RMSNorm(out_dim_for_gen),
nn.Linear(out_dim_for_gen, out_dim_for_gen),
)
if enable_vit_branch:
self.pred_vit = nn.Sequential(
nn.Linear(in_dim, out_dim_for_vit),
nn.GELU(),
nn.Linear(out_dim_for_vit, out_dim_for_vit),
RMSNorm(out_dim_for_vit),
nn.Linear(out_dim_for_vit, out_dim_for_vit),
)
@staticmethod
def _run_projection(proj, x):
param = next(proj.parameters(), None)
if param is not None and (x.device != param.device or x.dtype != param.dtype):
x = x.to(device=param.device, dtype=param.dtype)
return proj(x)
def for_gen(self, x):
return self._run_projection(self.proj_gen, x)
def for_vit(self, x):
return self._run_projection(self.pred_vit, x)
def with_skip_config(config, skip_transformer_1=False, skip_transformer_2=False):
"""Create a copy of config with skip_transformer_* flags set."""
config_copy = copy.deepcopy(config)
config_copy.skip_transformer_1 = skip_transformer_1
config_copy.skip_transformer_2 = skip_transformer_2
return config_copy
def _join_if_present(base, *parts):
if base is None:
return None
return os.path.join(base, *parts)
class BerniniConfig(PretrainedConfig):
model_type = "bernini"
def __init__(
self,
base_dir=None,
mllm_config_path=None,
mllm_subfolder=None,
processor_config_path=None,
processor_subfolder=None,
mllm_attn_implementation="sdpa",
diff_dec_config_path=None,
transformer_config_path=None,
transformer_2_config_path=None,
scheduler_config_path=None,
bernini_ckpt_subfolder=None,
scratch_mllm=False,
scratch=False,
noise_tmin=0.0, # [0, 0.875] for WAN2.2 high noise; [0.875, 1.0] for WAN2.2 low noise
noise_tmax=1.0, # [0, 1.0] for WAN2.1
flow_shift=5,
use_unipc=False,
target_fps=16,
switch_dit_boundary=0.875,
shift=3.0,
cotrain=False,
# setting for clip fmmar
num_mask_token=256,
clip_diff_cfg=None,
connector_cfg=None,
mask_ratio_infer_cfg=None,
feature_type_from_stage_one=None,
additional_special_tokens=[],
tie_word_embeddings=False,
ema_decay=None,
partial_pretrain_model=None,
use_src_id_rotary_emb=False,
interpolate_src_id=True,
max_trained_src_id=5,
max_sequence_length=512,
# t5 embedding
t5_text_encoder_path=None,
t5_text_encoder_subfolder=None,
t5_tokenizer_path=None,
t5_tokenizer_subfolder=None,
t5_max_sequence_length=512,
t5_combine_type="kl_loss",
vae_model_path=None,
vae_subfolder=None,
vae_config_path=None,
wovae_task_list=['und_img', 'und_txt', 'und_vid'],
**kwargs,
):
super().__init__(**kwargs)
self.base_dir = base_dir
self.mllm_config_path = mllm_config_path if mllm_config_path is not None else base_dir
self.mllm_subfolder = mllm_subfolder
self.mllm_attn_implementation = mllm_attn_implementation
self.processor_config_path = (
processor_config_path if processor_config_path is not None else self.mllm_config_path
)
self.processor_subfolder = processor_subfolder
self.diff_dec_config_path = diff_dec_config_path if diff_dec_config_path is not None else base_dir
self.transformer_config_path = (
transformer_config_path
if transformer_config_path is not None
else _join_if_present(base_dir, "transformer_config.json")
)
self.transformer_2_config_path = (
transformer_2_config_path
if transformer_2_config_path is not None
else _join_if_present(base_dir, "transformer_2_config.json")
)
self.scheduler_config_path = scheduler_config_path or (
os.path.join(base_dir, "scheduler")
if base_dir is not None
else None
)
self.bernini_ckpt_subfolder = bernini_ckpt_subfolder
self.vae_model_path = vae_model_path if vae_model_path is not None else base_dir
self.vae_subfolder = vae_subfolder
self.vae_config_path = vae_config_path or (
os.path.join(self.vae_model_path, _join_subfolder(self.vae_subfolder or "vae", "config.json"))
if self.vae_model_path is not None
else None
)
self.scratch = scratch
self.scratch_mllm = scratch_mllm
self.ema_decay = ema_decay
self.noise_tmin = noise_tmin
self.noise_tmax = noise_tmax
self.flow_shift = flow_shift
self.use_unipc = use_unipc
self.target_fps = target_fps
self.switch_dit_boundary = switch_dit_boundary
self.shift = shift
self.cotrain = cotrain
self.use_src_id_rotary_emb = use_src_id_rotary_emb
# When the number of conditioning segments exceeds `max_trained_src_id`
# (the largest source_id seen in training), evenly map their ids into
# the trained range [1, max_trained_src_id] instead of extrapolating to
# unseen integer ids. The target segment keeps source_id 0.
self.interpolate_src_id = interpolate_src_id
self.max_trained_src_id = max_trained_src_id
self.max_sequence_length = max_sequence_length
self.wovae_task_list = wovae_task_list
self.num_mask_token = num_mask_token
self.clip_diff_cfg = clip_diff_cfg
self.connector_cfg = connector_cfg
self.mask_ratio_infer_cfg = mask_ratio_infer_cfg
self.feature_type_from_stage_one = feature_type_from_stage_one
self.additional_special_tokens = additional_special_tokens
self.tie_word_embeddings = tie_word_embeddings
self.partial_pretrain_model = partial_pretrain_model
self.t5_text_encoder_path = t5_text_encoder_path if t5_text_encoder_path is not None else base_dir
self.t5_text_encoder_subfolder = t5_text_encoder_subfolder
self.t5_tokenizer_path = t5_tokenizer_path if t5_tokenizer_path is not None else base_dir
self.t5_tokenizer_subfolder = t5_tokenizer_subfolder
self.t5_max_sequence_length = t5_max_sequence_length
self.t5_combine_type = t5_combine_type
self.architectures = ["BerniniModel"]
class BerniniModel(PreTrainedModel):
config_class = BerniniConfig
def __init__(self, config):
super().__init__(config)
self.mllm = None
self.diff_dec = None
self.vit_decoder = None
self.base_dir = getattr(config, "base_dir", None)
self.mllm_config_path = config.mllm_config_path
self.mllm_subfolder = getattr(config, "mllm_subfolder", None)
self.diff_dec_config_path = config.diff_dec_config_path
self.processor_config_path = config.processor_config_path
self.feature_type_from_stage_one = config.feature_type_from_stage_one
self.num_mask_token = config.num_mask_token
self.use_t5_encoder = config.t5_text_encoder_path is not None
# =============== Init MLLM ===============
self.mllm_attn_implementation = config.mllm_attn_implementation
logger.info(
f"MLLM attention implement: config.mllm_attn_implementation={config.mllm_attn_implementation}"
)
if self.config.mllm_config_path is not None:
mllm_config = Qwen2_5_VLConfig.from_pretrained(
self.config.mllm_config_path,
subfolder=self.config.mllm_subfolder,
)
if self.config.scratch_mllm:
self.mllm = Qwen2_5_VLForConditionalGeneration._from_config(
mllm_config,
attn_implementation=config.mllm_attn_implementation,
torch_dtype=torch.bfloat16,
)
else:
self.mllm = Qwen2_5_VLForConditionalGeneration.from_pretrained(
self.config.mllm_config_path,
subfolder=self.config.mllm_subfolder,
attn_implementation=config.mllm_attn_implementation,
)
self.mask_tokens = nn.Parameter(torch.randn(1, self.num_mask_token, self.mllm.config.hidden_size) * 0.01)
# =============== Init Diff Dec ===============
if self.config.diff_dec_config_path:
if getattr(self.config, "cotrain", False):
self.diff_dec = GEN_Wanx22(with_skip_config(config, skip_transformer_1=False, skip_transformer_2=True))
self.diff_dec_low = GEN_Wanx22(with_skip_config(config, skip_transformer_1=True, skip_transformer_2=False))
else:
self.diff_dec = GEN_Wanx22(config)
self.diff_dec_low = None
# =============== Init Connector ===============
if config.connector_cfg.get('enable_gen_branch', True):
assert self.diff_dec is not None
if config.connector_cfg.get('enable_vit_branch', True):
assert self.mllm is not None
self.connector = MLPConnector(
in_dim=self.mllm.config.hidden_size if self.mllm is not None else 3584,
# setting for diffusion generator
num_layers_for_gen=config.connector_cfg.get('num_layers_for_gen', 1),
out_dim_for_gen=config.connector_cfg.get('out_dim_for_gen', 4096),
enable_gen_branch=config.connector_cfg.get('enable_gen_branch', True),
gen_head_type=config.connector_cfg.get('gen_head_type', 'mlp'),
# setting for predict vit embed
num_layers_for_vit=config.connector_cfg.get('num_layers_for_vit', 1),
out_dim_for_vit=config.connector_cfg.get('out_dim_for_vit', 3584),
enable_vit_branch=config.connector_cfg.get('enable_vit_branch', True),
)
# =============== vit decoder ===============
self.vit_decoder = DiffLoss_FM(
z_channels=config.clip_diff_cfg.get('z_channels', 3584),
target_channels=config.clip_diff_cfg.get('target_channels', 3584),
depth=config.clip_diff_cfg.get('depth', 16),
width=config.clip_diff_cfg.get('width', 1536),
diff_net=config.clip_diff_cfg.get("diff_net", "SimpleMLPAdaLN"),
scheduler_type=config.clip_diff_cfg.get("scheduler_type", "FlowMatchScheduler"),
shift=config.clip_diff_cfg.get("shift", 3.0),
num_inference_steps=config.clip_diff_cfg.get("num_inference_steps", 100),
extra_one_step=config.clip_diff_cfg.get("extra_one_step", True),
diffusion_batch_mul=config.clip_diff_cfg.get("diffusion_batch_mul", 1),
grad_checkpointing=True,
)
# =============== t5 embedding ===============
if self.use_t5_encoder:
logger.info(f"Initializing UMT5 encoder from {config.t5_text_encoder_path}")
def load_t5_text_encoder():
return UMT5EncoderModel.from_pretrained(
config.t5_text_encoder_path,
subfolder=config.t5_text_encoder_subfolder,
torch_dtype=torch.bfloat16,
)
# Stagger loading across ranks to reduce peak memory
if dist.is_initialized():
rank = dist.get_rank()
world_size = dist.get_world_size()
for r in range(world_size):
if r == rank:
self.t5_text_encoder = load_t5_text_encoder()
dist.barrier()
else:
self.t5_text_encoder = load_t5_text_encoder()
self.t5_max_sequence_length = getattr(config, 't5_max_sequence_length', 512)
self.t5_text_encoder.eval()
for param in self.t5_text_encoder.parameters():
param.requires_grad = False
def get_t5_text_embeddings(self, input_ids, attention_mask, input_lens, pad_text_embeds=True):
"""
Args:
input_ids: tensor with shape (1, n) where n = sum of all sequence lengths
attention_mask: tensor with shape (1, n)
input_lens: tensor with shape (1, b) where b = batch_size
Returns:
batch_text_seqlen: list of t5_max_sequence_length repeated b times
batch_text_embs: tensor with shape (1, b * t5_max_sequence_length, hidden_dim)
"""
# Remove batch dim
input_ids = input_ids.squeeze(0) # (n,)
attention_mask = attention_mask.squeeze(0) # (n,)
input_lens = input_lens.squeeze(0) # (b,)
batch_size = input_lens.size(0)
# Split concatenated sequence into individual samples by lengths
input_ids_list = torch.split(input_ids, input_lens.tolist())
attention_mask_list = torch.split(attention_mask, input_lens.tolist())
# Pad each sample to t5_max_sequence_length
padded_input_ids = []
padded_attention_mask = []
for ids, mask in zip(input_ids_list, attention_mask_list):
seq_len = ids.size(0)
if seq_len < self.t5_max_sequence_length:
pad_len = self.t5_max_sequence_length - seq_len
ids = torch.cat([ids, ids.new_zeros(pad_len)])
mask = torch.cat([mask, mask.new_zeros(pad_len)])
else:
ids = ids[:self.t5_max_sequence_length]
mask = mask[:self.t5_max_sequence_length]
padded_input_ids.append(ids)
padded_attention_mask.append(mask)
encoder_device = next(self.t5_text_encoder.parameters()).device
# Stack to batch: (batch_size, t5_max_sequence_length)
input_ids_batch = torch.stack(padded_input_ids, dim=0)
attention_mask_batch = torch.stack(padded_attention_mask, dim=0)
input_ids_batch = input_ids_batch.to(encoder_device)
attention_mask_batch = attention_mask_batch.to(encoder_device)
# Get actual sequence lengths (clamped to t5_max_sequence_length)
seq_lens = torch.clamp(input_lens, max=self.t5_max_sequence_length)
# Get embeddings
with torch.no_grad():
prompt_embeds = self.t5_text_encoder(
input_ids_batch, attention_mask_batch
).last_hidden_state # (batch_size, t5_max_sequence_length, hidden_dim)
# Zero out padding positions
if pad_text_embeds:
prompt_embeds = [u[:v] for u, v in zip(prompt_embeds, seq_lens)]
prompt_embeds = torch.stack([
torch.cat([u, u.new_zeros(self.t5_max_sequence_length - u.size(0), u.size(1))])
for u in prompt_embeds
], dim=0) # (batch_size, t5_max_sequence_length, hidden_dim)
# Build return values
batch_text_seqlen = [self.t5_max_sequence_length] * batch_size
batch_text_embs = prompt_embeds.view(1, -1, prompt_embeds.size(-1))
return batch_text_embs, batch_text_seqlen
else:
prompt_embeds = [u[:v].unsqueeze(0) for u, v in zip(prompt_embeds, seq_lens)]
seq_lens = seq_lens.to(dtype=torch.long).cpu().tolist()
batch_text_embs = torch.cat(prompt_embeds, dim=1)
return batch_text_embs, seq_lens
def get_t5_text_embeddings_sample(self, input_ids, attention_mask):
encoder_device = next(self.t5_text_encoder.parameters()).device
input_ids = input_ids.to(encoder_device)
attention_mask = attention_mask.to(encoder_device)
seq_lens = attention_mask.gt(0).sum(dim=1).long()
with torch.no_grad():
prompt_embeds = self.t5_text_encoder(
input_ids, attention_mask).last_hidden_state
prompt_embeds = [u[:min(v, self.t5_max_sequence_length)] for u, v in zip(prompt_embeds, seq_lens)]
prompt_embeds = torch.stack(prompt_embeds, dim=0)
return prompt_embeds
def get_ignore_modules_in_mixed_precision(self):
from diffusers.models.embeddings import TimestepEmbedding
from diffusers.models.normalization import FP32LayerNorm
return (TimestepEmbedding, FP32LayerNorm)
def post_process_input_embeds(
self,
input_embeds,
visual_output_mask,
tgt_vit_mask,
inference=False
):
target_vit_embed_mask = visual_output_mask.squeeze(0)
target_vit_embeds = input_embeds[:, target_vit_embed_mask, :]
target_vit_embeds_gt = target_vit_embeds.clone()
mask_token = self.mask_tokens[:, :1]
if inference:
# mask all tokens
mask_rate = 1
all_vit_token_num = sum(target_vit_embed_mask).detach().cpu().numpy()
target_vit_embeds[:, :, :] = mask_token.expand(1, all_vit_token_num, -1)
input_embeds[:, target_vit_embed_mask, :] = target_vit_embeds
diff_loss_mask = torch.ones(all_vit_token_num).to(target_vit_embeds.device)
elif tgt_vit_mask is not None:
diff_loss_mask = tgt_vit_mask.squeeze(0).bool()
token_num = int(diff_loss_mask.sum().detach().cpu().item())
target_vit_embeds[:, diff_loss_mask, :] = mask_token.expand(1, token_num, -1)
input_embeds[:, target_vit_embed_mask, :] = target_vit_embeds
else: # tgt_vit_mask is None
all_vit_token_num = sum(target_vit_embed_mask).detach().cpu().numpy()
diff_loss_mask = torch.zeros(all_vit_token_num).to(target_vit_embeds.device)
return dict(
input_embeds=input_embeds,
diff_loss_mask=diff_loss_mask,
target_vit_embeds=target_vit_embeds_gt
)
def feat_from_planner_to_renderer(
self,
hidden_states,
tgt_vit_mask,
visual_output_mask,
inference=False
):
pred_vit_embed_mask = visual_output_mask.squeeze(0)
pred_vit_embeds = hidden_states[:, pred_vit_embed_mask, :].clone() # For calculate vit decoder loss
txt_and_vit_token_mask = visual_output_mask.squeeze(0).logical_not()
if not inference:
all_idx = torch.nonzero(pred_vit_embed_mask, as_tuple=False).squeeze(-1) # shape [N]
cur_clip_mask = tgt_vit_mask.bool().logical_not()
valid_clip_idx = all_idx[cur_clip_mask]
pred_vit_embed_mask = torch.zeros(hidden_states.shape[1], dtype=torch.bool, device=hidden_states.device)
pred_vit_embed_mask[valid_clip_idx] = True
cond_embed_mask = (txt_and_vit_token_mask | pred_vit_embed_mask)
diff_mllm_context_txt_mask = txt_and_vit_token_mask[cond_embed_mask]
diff_mllm_context_vit_mask = pred_vit_embed_mask[cond_embed_mask]
connector_param = next(self.connector.parameters())
if connector_param.device != hidden_states.device or connector_param.dtype != hidden_states.dtype:
self.connector.to(device=hidden_states.device, dtype=hidden_states.dtype)
diff_mllm_contexts = hidden_states[:, cond_embed_mask, :]
diff_mllm_contexts = self.connector.for_gen(diff_mllm_contexts)
mllm_context_seqlens = []
pred_vit_embed_seqlens = []
pred_vit_embed_mask = visual_output_mask.squeeze(0)
mllm_context_seqlens.append(int(cond_embed_mask.sum().item()))
pred_vit_embed_seqlens.append(int(pred_vit_embed_mask.sum().item()))
return dict(
diff_mllm_contexts=diff_mllm_contexts,
mllm_context_seqlens=mllm_context_seqlens,
pred_vit_embeds=pred_vit_embeds,
pred_vit_embed_seqlens=pred_vit_embed_seqlens,
diff_mllm_context_txt_mask=diff_mllm_context_txt_mask,
diff_mllm_context_vit_mask=diff_mllm_context_vit_mask,
)
def format_mllm_inputs_embeds(
self,
input_ids,
visual_embeds,
visual_input_mask,
visual_output_mask,
):
inputs_embeds = self.mllm.get_input_embeddings()(input_ids).to(dtype=torch.bfloat16)
if visual_embeds is not None and len(visual_embeds) > 0:
visual_mask = visual_input_mask | visual_output_mask
n_visual_tokens = visual_mask.sum().long().item()
n_visual_features = visual_embeds.shape[0]
if n_visual_tokens != n_visual_features:
raise ValueError(
f"Image features and image tokens do not match: tokens: {n_visual_tokens}, features {n_visual_features}"
)
visual_mask = (
visual_mask.unsqueeze(-1)
.expand_as(inputs_embeds)
.to(inputs_embeds.device)
)
visual_embeds = visual_embeds.to(
inputs_embeds.device, inputs_embeds.dtype)
inputs_embeds = inputs_embeds.masked_scatter(
visual_mask, visual_embeds)
return inputs_embeds