[Bagel]Add image edit (#588)

Signed-off-by: princepride <wangzhipeng628@gmail.com>
This commit is contained in:
汪志鹏 authored and GitHub committed 2026-01-05 12:18:04 +08:00
1 parent cd623598b1
commit d430beef76
2 files changed
+477 -18

No files matched your search

@@ -29,6 +29,43 @@ from vllm.vllm_flash_attn import flash_attn_varlen_func
from vllm_omni.diffusion.layers.rope import RotaryEmbedding
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"):
super().__init__()
self.fc1 = nn.Linear(input_dim, output_dim)
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 = nn.Linear(output_dim, output_dim)
def forward(self, x):
return self.fc2(self.act(self.fc1(x)))
torch._dynamo.config.cache_size_limit = 512
torch._dynamo.config.accumulated_cache_size_limit = 4096
flex_attention = torch.compile(flex_attention)
@@ -600,18 +637,30 @@ def get_flattened_position_ids_extrapolate(img_h, img_w, patch_size, max_num_pat
class BagelConfig(PretrainedConfig):
def __init__(
self,
visual_gen=True,
visual_und=True,
llm_config=None,
vit_config=None,
vae_config=None,
latent_patch_size=2,
max_latent_size=32,
vit_max_num_patch_per_side=70,
connector_act="gelu_pytorch_tanh",
interpolate_pos=False,
timestep_shift=1.0,
**kwargs,
):
super().__init__(**kwargs)
self.visual_gen = visual_gen
self.visual_und = visual_und
self.llm_config = llm_config
self.vit_config = vit_config
self.vae_config = vae_config
self.latent_patch_size = latent_patch_size
self.max_latent_size = max_latent_size
self.vit_max_num_patch_per_side = vit_max_num_patch_per_side
self.connector_act = connector_act
self.interpolate_pos = interpolate_pos
self.timestep_shift = timestep_shift
@@ -619,23 +668,32 @@ class Bagel(torch.nn.Module):
config_class = BagelConfig
base_model_prefix = "bagel"
def __init__(self, language_model, config: BagelConfig):
def __init__(self, language_model, vit_model, config: BagelConfig):
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.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_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)
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
@@ -643,8 +701,9 @@ class Bagel(torch.nn.Module):
self._init_weights()
def _init_weights(self):
nn.init.constant_(self.llm2vae.weight, 0)
nn.init.constant_(self.llm2vae.bias, 0)
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()
@@ -713,6 +772,261 @@ class Bagel(torch.nn.Module):
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, packed_indexes = list(), list(), list()
packed_key_value_indexes = list()
_curr = 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_key_value_indexes.extend(range(curr, curr + curr_kvlen))
curr += curr_kvlen
packed_text_ids.append(new_token_ids["start_of_image"])
packed_text_indexes.append(_curr)
packed_indexes.append(curr)
curr += 1
_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))
packed_indexes.extend(range(curr, curr + num_img_tokens))
curr += num_img_tokens
_curr += num_img_tokens
packed_text_ids.append(new_token_ids["end_of_image"])
packed_text_indexes.append(_curr)
packed_indexes.append(curr)
curr += 1
_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),
"packed_indexes": torch.tensor(packed_indexes, dtype=torch.long),
"packed_key_value_indexes": torch.tensor(packed_key_value_indexes, dtype=torch.long),
"key_values_lens": torch.tensor(curr_kvlens, dtype=torch.int),
}
return generation_input, newlens, new_rope
@torch.no_grad
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,
packed_indexes: torch.LongTensor,
key_values_lens: torch.IntTensor,
packed_key_value_indexes: torch.Tensor,
):
packed_text_embedding = self.language_model.model.embed_tokens(packed_text_ids)
packed_sequence = packed_text_embedding.new_zeros((sum(packed_seqlens), self.hidden_size))
packed_sequence[packed_text_indexes] = packed_text_embedding
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
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,
packed_query_indexes=packed_indexes,
past_key_values=past_key_values,
key_values_lens=key_values_lens,
packed_key_value_indexes=packed_key_value_indexes,
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, packed_indexes = list(), list(), list()
packed_key_value_indexes = list()
_curr = curr = 0
newlens, new_rope = list(), list()
for image, curr_kvlen, curr_position_id in zip(images, curr_kvlens, curr_rope):
packed_key_value_indexes.extend(range(curr, curr + curr_kvlen))
curr += curr_kvlen
packed_text_ids.append(new_token_ids["start_of_image"])
packed_text_indexes.append(_curr)
packed_indexes.append(curr)
curr += 1
_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))
packed_indexes.extend(range(curr, curr + num_img_tokens))
curr += num_img_tokens
_curr += num_img_tokens
packed_text_ids.append(new_token_ids["end_of_image"])
packed_text_indexes.append(_curr)
packed_indexes.append(curr)
curr += 1
_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),
"packed_indexes": torch.tensor(packed_indexes, dtype=torch.long),
"packed_key_value_indexes": torch.tensor(packed_key_value_indexes, dtype=torch.long),
"key_values_lens": torch.tensor(curr_kvlens, dtype=torch.int),
}
return generation_input, newlens, new_rope
@torch.no_grad
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_indexes: torch.LongTensor,
packed_key_value_indexes: torch.LongTensor,
key_values_lens: torch.IntTensor,
):
packed_text_embedding = self.language_model.model.embed_tokens(packed_text_ids)
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,
packed_query_indexes=packed_indexes,
past_key_values=past_key_values,
packed_key_value_indexes=packed_key_value_indexes,
key_values_lens=key_values_lens,
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()
@@ -12,10 +12,12 @@ from collections.abc import Iterable
from dataclasses import dataclass
from math import isqrt
import numpy as np
import torch
import torch.nn.functional as F
from PIL import Image
from torch import nn
from transformers import AutoTokenizer
from transformers import AutoTokenizer, SiglipImageProcessor, SiglipVisionConfig, SiglipVisionModel
from vllm.logger import init_logger
from vllm.model_executor.models.utils import AutoWeightsLoader
@@ -89,6 +91,12 @@ class _VaeCfg:
downsample: int = 8
@dataclass
class _VitCfg:
patch_size: int = 14
hidden_size: int = 1152
def default_ae_params() -> AutoEncoderParams:
return AutoEncoderParams(
resolution=256,
@@ -104,6 +112,37 @@ def default_ae_params() -> AutoEncoderParams:
)
class SiglipNaViTWrapper(nn.Module):
def __init__(self, vision_model):
super().__init__()
# If input is SiglipVisionModel, unwrap it to get SiglipVisionTransformer
if hasattr(vision_model, "vision_model"):
self.vision_model = vision_model.vision_model
else:
self.vision_model = vision_model
# Configure weights for linear equivalent of patch embedding
self.patch_embed_weight = self.vision_model.embeddings.patch_embedding.weight
self.patch_embed_bias = self.vision_model.embeddings.patch_embedding.bias
def forward(self, packed_pixel_values, packed_flattened_position_ids, cu_seqlens, max_seqlen):
w = self.patch_embed_weight.view(self.patch_embed_weight.shape[0], -1)
x = F.linear(packed_pixel_values, w, self.patch_embed_bias)
pos = self.vision_model.embeddings.position_embedding(packed_flattened_position_ids)
x = x + pos
hidden_states = x.unsqueeze(0)
seq_len = x.shape[0]
mask = torch.full((1, 1, seq_len, seq_len), torch.finfo(x.dtype).min, device=x.device, dtype=x.dtype)
cu_seqlens_list = cu_seqlens.tolist()
for i in range(len(cu_seqlens_list) - 1):
start = cu_seqlens_list[i]
end = cu_seqlens_list[i + 1]
mask[..., start:end, start:end] = 0.0
outputs = self.vision_model.encoder(inputs_embeds=hidden_states, attention_mask=mask)
return outputs.last_hidden_state.squeeze(0)
class BagelPipeline(nn.Module):
"""Bagel generation pipeline (MoT) packaged for vllm-omni diffusion engine.
@@ -150,12 +189,26 @@ class BagelPipeline(nn.Module):
local_files_only=True,
trust_remote_code=True,
)
# Try finding vision_config or interpolate from top-level config
vit_cfg_dict = bagel_cfg.get("vit_config") or {}
vit_cfg = _VitCfg(
patch_size=int(vit_cfg_dict.get("patch_size", 14)),
hidden_size=int(vit_cfg_dict.get("hidden_size", 1152)),
)
vit_config_path = os.path.join(model_path, "vit_config.json")
vit_conf = SiglipVisionConfig.from_json_file(vit_config_path)
self.vit_model = SiglipVisionModel(vit_conf)
self.image_processor = SiglipImageProcessor.from_pretrained(model_path, local_files_only=True)
if self.vit_model:
self.vit_model = SiglipNaViTWrapper(self.vit_model)
vit_cfg.hidden_size = self.vit_model.vision_model.config.hidden_size
vit_cfg.patch_size = self.vit_model.vision_model.config.patch_size
self.tokenizer, self.new_token_ids, _ = add_special_tokens(self.tokenizer)
try:
tok_len = len(self.tokenizer)
except Exception: # pragma: no cover - very old tokenizers
tok_len = getattr(self.tokenizer, "vocab_size", llm_config.vocab_size)
tok_len = len(self.tokenizer)
required_max_id = max(int(v) for v in self.new_token_ids.values())
llm_config.vocab_size = max(
int(getattr(llm_config, "vocab_size", tok_len)),
@@ -169,9 +222,14 @@ class BagelPipeline(nn.Module):
self.bagel = Bagel(
language_model=self.language_model,
vit_model=self.vit_model,
config=BagelConfig(
llm_config=llm_config,
vae_config=vae_cfg,
vit_config=vit_cfg,
vit_max_num_patch_per_side=int(bagel_cfg.get("vit_max_num_patch_per_side", 70)),
connector_act=str(bagel_cfg.get("connector_act", "gelu_pytorch_tanh")),
interpolate_pos=bool(bagel_cfg.get("interpolate_pos", False)),
latent_patch_size=int(bagel_cfg.get("latent_patch_size", 2)),
max_latent_size=int(bagel_cfg.get("max_latent_size", 32)),
timestep_shift=float(bagel_cfg.get("timestep_shift", 1.0)),
@@ -248,6 +306,69 @@ class BagelPipeline(nn.Module):
injected_kv = getattr(req, "past_key_values", None)
injected_metadata = getattr(req, "kv_metadata", None)
# Image input handling
image_input = getattr(req, "pil_image", None)
if image_input and not isinstance(image_input, list):
image_input = [image_input]
if image_input:
# If we have an image, we prefill with it
if self.image_processor and self.vae:
def vit_transforms(img):
# SigLIP processor returns dict with pixel_values; we want the tensor
return self.image_processor(images=img, return_tensors="pt").pixel_values[0]
def vae_transforms(img):
if img.mode != "RGB":
img = img.convert("RGB")
# Convert to [-1, 1] tensor (H, W, C) -> (C, H, W)
arr = torch.from_numpy(np.array(img)).float() / 127.5 - 1.0
return arr.permute(2, 0, 1)
# 1. Update VAE
gen_input_vae, newlens_vae, new_rope_vae = self.bagel.prepare_vae_images(
curr_kvlens=gen_context["kv_lens"],
curr_rope=gen_context["ropes"],
images=image_input,
transforms=vae_transforms,
new_token_ids=self.new_token_ids,
)
for k, v in gen_input_vae.items():
if torch.is_tensor(v):
gen_input_vae[k] = v.to(self.device)
# VAE needs bfloat16 to match model strings usually, specifically encode
with torch.autocast(device_type="cuda", enabled=self.device.type == "cuda", dtype=torch.bfloat16):
gen_context["past_key_values"] = self.bagel.forward_cache_update_vae(
self.vae, gen_context["past_key_values"], **gen_input_vae
)
gen_context["kv_lens"] = newlens_vae
gen_context["ropes"] = new_rope_vae
# 2. Update ViT
gen_input_img, newlens_img, new_rope_img = self.bagel.prepare_vit_images(
curr_kvlens=gen_context["kv_lens"],
curr_rope=gen_context["ropes"],
images=image_input,
transforms=vit_transforms,
new_token_ids=self.new_token_ids,
)
for k, v in gen_input_img.items():
if torch.is_tensor(v):
gen_input_img[k] = v.to(self.device)
with torch.autocast(device_type="cuda", enabled=self.device.type == "cuda", dtype=torch.bfloat16):
gen_context["past_key_values"] = self.bagel.forward_cache_update_vit(
gen_context["past_key_values"], **gen_input_img
)
gen_context["kv_lens"] = newlens_img
gen_context["ropes"] = new_rope_img
else:
logger.warning("Image provided but no image processor available.")
if injected_kv is not None and injected_metadata is not None:
logger.info("Using injected KV Cache from remote transfer")
@@ -409,6 +530,19 @@ class BagelPipeline(nn.Module):
yield "bagel." + n
break
# Map connector and vit_pos_embed to `bagel.*`
for pfx in ("connector.", "vit_pos_embed."):
if n.startswith(pfx):
yield "bagel." + n
break
if n.startswith("vit_model."):
yield "bagel." + n # matches self.bagel.vit_model
elif n.startswith("vision_model."):
yield "bagel.vit_model." + n
elif n.startswith("model.vision_model."):
yield "bagel.vit_model." + n[len("model.") :]
def _filtered_weights():
total = 0
kept = 0
@@ -439,6 +573,17 @@ class BagelPipeline(nn.Module):
shapes[cand] = (npos, hdim)
picked = cand
break
# Handle flattened patch embedding for SigLIP
if cand.endswith("embeddings.patch_embedding.weight") and tensor.ndim == 2:
# Checkpoint has (Hidden, C*P*P), model expects (Hidden, C, P, P)
if shapes.get(cand) is not None:
target_shape = shapes[cand]
if tensor.numel() == torch.prod(torch.tensor(target_shape)):
# Reshape tensor to match target
tensor = tensor.view(target_shape)
picked = cand
break
shape_mismatch += 1
# Keep this quiet; shape mismatches are expected for ignored modules.
if picked is not None: