cck-0702's picture
Clean commit without binary (image) files
c8c00f0
Raw
History Blame Contribute Delete
11.3 kB
from dataclasses import dataclass
import torch
from torch import Tensor, nn
import numpy as np
from flux.modules.layers import (DoubleStreamBlock, EmbedND, LastLayer,
MLPEmbedder, SingleStreamBlock,
timestep_embedding)
@dataclass
class FluxParams:
in_channels: int
out_channels: int
vec_in_dim: int
context_in_dim: int
hidden_size: int
mlp_ratio: float
num_heads: int
depth: int
depth_single_blocks: int
axes_dim: list[int]
theta: int
qkv_bias: bool
guidance_embed: bool
class Flux(nn.Module):
"""
Transformer model for flow matching on sequences.
"""
def __init__(self, params: FluxParams):
super().__init__()
self.params = params
self.in_channels = params.in_channels
self.out_channels = params.out_channels
if params.hidden_size % params.num_heads != 0:
raise ValueError(
f"Hidden size {params.hidden_size} must be divisible by num_heads {params.num_heads}"
)
pe_dim = params.hidden_size // params.num_heads
if sum(params.axes_dim) != pe_dim:
raise ValueError(f"Got {params.axes_dim} but expected positional dim {pe_dim}")
self.hidden_size = params.hidden_size
self.num_heads = params.num_heads
self.pe_embedder = EmbedND(dim=pe_dim, theta=params.theta, axes_dim=params.axes_dim)
self.img_in = nn.Linear(self.in_channels, self.hidden_size, bias=True)
self.time_in = MLPEmbedder(in_dim=256, hidden_dim=self.hidden_size)
self.vector_in = MLPEmbedder(params.vec_in_dim, self.hidden_size)
self.guidance_in = (
MLPEmbedder(in_dim=256, hidden_dim=self.hidden_size) if params.guidance_embed else nn.Identity()
)
self.txt_in = nn.Linear(params.context_in_dim, self.hidden_size)
self.double_blocks = nn.ModuleList(
[
DoubleStreamBlock(
self.hidden_size,
self.num_heads,
mlp_ratio=params.mlp_ratio,
qkv_bias=params.qkv_bias,
)
for _ in range(params.depth)
]
)
self.single_blocks = nn.ModuleList(
[
SingleStreamBlock(self.hidden_size, self.num_heads, mlp_ratio=params.mlp_ratio)
for _ in range(params.depth_single_blocks)
]
)
self.final_layer = LastLayer(self.hidden_size, 1, self.out_channels)
self._sequential_offload = False
def enable_sequential_cpu_offload(self):
self._sequential_offload = True
def forward(
self,
img: Tensor,
img_ids: Tensor,
txt: Tensor,
txt_ids: Tensor,
timesteps: Tensor,
y: Tensor,
guidance: Tensor | None = None,
info = None,
ref_img: Tensor | None = None, # ← NEW
ref_img_ids: Tensor | None = None, # ← NEW
) -> Tensor:
if img.ndim != 3 or txt.ndim != 3:
raise ValueError("Input img and txt tensors must have 3 dimensions.")
# Ensure inputs match the model's dtype (NF4 can silently upcast to float32)
target_dtype = self.img_in.weight.dtype
img = img.to(target_dtype)
txt = txt.to(target_dtype)
if y.dtype != target_dtype:
y = y.to(target_dtype)
# running on sequences img
img = self.img_in(img)
original_img_seq_len = img.shape[1] # ← REMEMBER: how many original img tokens
vec = self.time_in(timestep_embedding(timesteps, 256))
if self.params.guidance_embed:
if guidance is None:
raise ValueError("Didn't get guidance strength for guidance distilled model.")
vec = vec + self.guidance_in(timestep_embedding(guidance, 256))
vec = vec + self.vector_in(y)
txt = self.txt_in(txt)
# ═══════════════════════════════════════════════════════
# NEW: Concatenate reference tokens into image stream
# ═══════════════════════════════════════════════════════
if ref_img is not None and ref_img_ids is not None:
ref = self.img_in(ref_img) # project reference patches same way
img = torch.cat([img, ref], dim=1)
img_ids = torch.cat([img_ids, ref_img_ids], dim=1)
if ref_img is not None:
print(f"[Flux.forward] Attending to {ref_img.shape[1]} ref tokens + {original_img_seq_len} img tokens")
# ═══════════════════════════════════════════════════════
ids = torch.cat((txt_ids, img_ids), dim=1)
pe = self.pe_embedder(ids)
inject_pe = pe.clone()
if not info['inverse']:
# Defensive clamp: GSAM indices may exceed seq_len for non-16-divisible images
seq_len = pe.shape[2]
# Initialize accumulated lists for tracking all processed IDs
accumulated_target_ids = []
accumulated_ref_ids = []
for artifact_data in info['artifact_data']:
if artifact_data['artifact_type'] == 'addition' and info['addition']:
ref_ids = artifact_data['reference_patch_indices'].copy()
target_ids = artifact_data['target_patch_indices'].copy()
ref_ids = [max(0, min(int(i), seq_len - 1)) for i in ref_ids]
target_ids = [max(0, min(int(i), seq_len - 1)) for i in target_ids]
if len(target_ids) > 0 and len(ref_ids) > 0:
inject_pe[:,:,target_ids,:,:,:] = inject_pe[:,:,ref_ids,:,:,:]
# Accumulate IDs
if info['inject']:
accumulated_target_ids.extend(target_ids)
accumulated_ref_ids.extend(ref_ids)
elif artifact_data['artifact_type'] == 'removal' and info['removal']:
ref_ids = artifact_data['reference_patch_indices'].copy()
target_ids = artifact_data['target_patch_indices'].copy()
ref_ids = [max(0, min(int(i), seq_len - 1)) for i in ref_ids]
target_ids = [max(0, min(int(i), seq_len - 1)) for i in target_ids]
# ref_ids = get_closest_patch_inds(info['patch_h'], info['patch_w'], target_ids, ref_ids)
if len(target_ids) > 0 and len(ref_ids) > 0:
inject_pe[:,:,target_ids,:,:,:] = inject_pe[:,:,ref_ids,:,:,:]
# Accumulate IDs (after target_ids modification)
if info['inject']:
accumulated_target_ids.extend(target_ids)
accumulated_ref_ids.extend(ref_ids)
elif artifact_data['artifact_type'] == 'distortion' and info['distortion']:
ref_ids = artifact_data['reference_patch_indices'].copy()
target_ids = artifact_data['target_patch_indices'].copy()
ref_ids = [max(0, min(int(i), seq_len - 1)) for i in ref_ids]
target_ids = [max(0, min(int(i), seq_len - 1)) for i in target_ids]
if len(ref_ids) == 0:
# For distortion with no reference patches, shuffle target patches
ref_ids = target_ids.copy()
np.random.shuffle(ref_ids)
# Ensure target_ids and ref_ids are different for distortion
if len(target_ids) > 0 and len(ref_ids) > 0:
inject_pe[:,:,target_ids,:,:,:] = inject_pe[:,:,ref_ids,:,:,:]
# Accumulate IDs (after any ref_ids modification)
if info['inject']:
accumulated_target_ids.extend(target_ids)
accumulated_ref_ids.extend(ref_ids)
elif artifact_data['artifact_type'] == 'fusion' and info['fusion']:
ref_ids = artifact_data['reference_patch_indices'].copy()
target_ids = artifact_data['target_patch_indices'].copy()
ref_ids = [max(0, min(int(i), seq_len - 1)) for i in ref_ids]
target_ids = [max(0, min(int(i), seq_len - 1)) for i in target_ids]
# np.random.shuffle(ref_ids)
if len(target_ids) > 0 and len(ref_ids) > 0:
inject_pe[:,:,target_ids,:,:,:] = inject_pe[:,:,ref_ids,:,:,:]
# Accumulate IDs
if info['inject']:
accumulated_target_ids.extend(target_ids)
accumulated_ref_ids.extend(ref_ids)
info['patch_ids'] = accumulated_target_ids
info['patch_ref_ids'] = accumulated_ref_ids
info['timesteps'] = timesteps
if self._sequential_offload:
for block in self.double_blocks:
block = block.to(img.device)
img, txt = block(img=img, txt=txt, vec=vec, pe=inject_pe, info=info)
block = block.cpu()
torch.cuda.empty_cache()
else:
for block in self.double_blocks:
img, txt = block(img=img, txt=txt, vec=vec, pe=inject_pe, info=info)
cnt = 0
img = torch.cat((txt, img), 1)
info['type'] = 'single'
if self._sequential_offload:
for block in self.single_blocks:
block = block.to(img.device)
info['id'] = cnt
if cnt < 19:
img, info = block(img, vec=vec, pe=inject_pe, info=info)
else:
img, info = block(img, vec=vec, pe=pe, info=info)
block = block.cpu()
torch.cuda.empty_cache()
cnt += 1
else:
for block in self.single_blocks:
info['id'] = cnt
if cnt < 19:
img, info = block(img, vec=vec, pe=inject_pe, info=info)
else:
img, info = block(img, vec=vec, pe=pe, info=info)
cnt += 1
# ═══════════════════════════════════════════════════════
# MODIFIED: Extract only ORIGINAL img tokens
# Before: img = img[:, txt.shape[1] :, ...] (gets img + ref)
# After: img = img[:, txt.shape[1] : txt.shape[1] + original_img_seq_len, ...]
# ═══════════════════════════════════════════════════════
img = img[:, txt.shape[1] : txt.shape[1] + original_img_seq_len, ...]
img = self.final_layer(img, vec) # (N, T, patch_size ** 2 * out_channels)
return img, info