T

Diffusers
Safetensors
T
File size: 14,908 Bytes
964f845
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
"""
VLM-Adapter Attention Processor for InstructPix2Pix

Adapted from IP-Adapter's attention processor to inject VLM tokens from frozen LLaVA
into InstructPix2Pix UNet cross-attention layers.

Key differences from standard attention:
- Concatenates CLIP text embeddings with VLM tokens: [clip_text, vlm_tokens]
- Separate K/V projections for VLM tokens (to_k_vlm, to_v_vlm)
- Only injects to attn2 (cross-attention) in mid_block + late up_blocks
"""

import torch
import torch.nn as nn
import torch.nn.functional as F


class LoRALinear(nn.Module):
    """LoRA (Low-Rank Adaptation) Linear layer"""
    def __init__(self, in_features, out_features, rank=8, alpha=16.0):
        super().__init__()
        self.rank = rank
        self.alpha = alpha
        self.scaling = alpha / rank
        
        # LoRA matrices
        self.lora_A = nn.Linear(in_features, rank, bias=False)
        self.lora_B = nn.Linear(rank, out_features, bias=False)
        
        # Initialize
        nn.init.kaiming_uniform_(self.lora_A.weight, a=5**0.5)
        nn.init.zeros_(self.lora_B.weight)
    
    def forward(self, x):
        return self.lora_B(self.lora_A(x)) * self.scaling


class AttnProcessor(nn.Module):
    """
    Default processor for self-attention (attn1) - no changes
    """
    def __init__(self):
        super().__init__()

    def __call__(
        self,
        attn,
        hidden_states,
        encoder_hidden_states=None,
        attention_mask=None,
        temb=None,
    ):
        # Ensure inputs match attention weight dtype (fp16 under AMP)
        target_dtype = attn.to_q.weight.dtype
        residual = hidden_states
        hidden_states = hidden_states.to(dtype=target_dtype)

        if attn.spatial_norm is not None:
            hidden_states = attn.spatial_norm(hidden_states, temb)

        input_ndim = hidden_states.ndim

        if input_ndim == 4:
            batch_size, channel, height, width = hidden_states.shape
            hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2)

        batch_size, sequence_length, _ = (
            hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
        )
        
        if attention_mask is not None:
            attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)

        if attn.group_norm is not None:
            hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)

        query = attn.to_q(hidden_states)

        if encoder_hidden_states is None:
            encoder_hidden_states = hidden_states
        elif attn.norm_cross:
            encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)

        key = attn.to_k(encoder_hidden_states)
        value = attn.to_v(encoder_hidden_states)

        query = attn.head_to_batch_dim(query)
        key = attn.head_to_batch_dim(key)
        value = attn.head_to_batch_dim(value)

        attention_probs = attn.get_attention_scores(query, key, attention_mask)
        hidden_states = torch.bmm(attention_probs, value)
        hidden_states = attn.batch_to_head_dim(hidden_states)

        # linear proj
        hidden_states = attn.to_out[0](hidden_states)
        # dropout
        hidden_states = attn.to_out[1](hidden_states)

        if input_ndim == 4:
            hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width)

        if attn.residual_connection:
            hidden_states = hidden_states + residual

        hidden_states = hidden_states / attn.rescale_output_factor

        return hidden_states


class VLMAttnProcessor(nn.Module):
    """
    Attention processor for VLM-Adapter injection into cross-attention (attn2).
    
    Similar to IP-Adapter, but injects VLM tokens from frozen LLaVA instead of CLIP image features.
    
    Args:
        hidden_size: Hidden size of the attention layer (e.g., 1280)
        cross_attention_dim: Dimension of cross-attention embeddings (e.g., 768 for CLIP)
        scale: Scaling factor for VLM influence (default: 1.0)
        num_tokens: Number of VLM tokens (default: 16, matching resampler output)
    """
    def __init__(
        self, 
        hidden_size, 
        cross_attention_dim=None, 
        scale=1.0, 
        num_tokens=16,
        lora_rank=8,  # kept for API compatibility; not used in base Linear path
        lora_alpha=16.0  # kept for API compatibility; not used in base Linear path
    ):
        super().__init__()

        self.hidden_size = hidden_size
        self.cross_attention_dim = cross_attention_dim
        self.scale = scale
        self.num_tokens = num_tokens

        # Separate K/V projections for VLM tokens using BASE Linear (stable path)
        # VLM tokens from ELLA have dimension cross_attention_dim (768 for CLIP)
        in_dim = cross_attention_dim or hidden_size
        self.to_k_vlm = nn.Linear(in_dim, hidden_size, bias=False)
        self.to_v_vlm = nn.Linear(in_dim, hidden_size, bias=False)
        
        # Initialize VLM projection layers with Xavier/Glorot initialization
        # This is better for cross-attention projections than default Kaiming
        nn.init.xavier_uniform_(self.to_k_vlm.weight)
        nn.init.xavier_uniform_(self.to_v_vlm.weight)
        
        # Gentle initialization scaling for stable starts
        # Scale down initial weights by 0.8 to prevent early instability
        with torch.no_grad():
            self.to_k_vlm.weight.mul_(0.8)
            self.to_v_vlm.weight.mul_(0.8)
        
        # Per-block learnable gate for VLM influence
        # Initialize to 1.0 (equal text/VLM influence)
        self.vlm_gate = nn.Parameter(torch.ones(1))

    def __call__(
        self,
        attn,
        hidden_states,
        encoder_hidden_states=None,
        attention_mask=None,
        temb=None,
    ):
        # Determine target dtype from attention weights (fp16 under AMP)
        target_dtype = attn.to_q.weight.dtype
        residual = hidden_states

        if attn.spatial_norm is not None:
            hidden_states = attn.spatial_norm(hidden_states, temb)

        input_ndim = hidden_states.ndim

        if input_ndim == 4:
            batch_size, channel, height, width = hidden_states.shape
            hidden_states = hidden_states.view(batch_size, channel, height * width).transpose(1, 2)

        batch_size, sequence_length, _ = (
            hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
        )

        if attention_mask is not None:
            attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)

        if attn.group_norm is not None:
            hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)

        # Ensure hidden_states matches dtype before projection
        hidden_states = hidden_states.to(dtype=target_dtype)
        query = attn.to_q(hidden_states)

        if encoder_hidden_states is None:
            encoder_hidden_states = hidden_states
        else:
            encoder_hidden_states = encoder_hidden_states.to(dtype=target_dtype)
            # Split CLIP text embeddings and VLM tokens
            # Format: [clip_text (77 tokens), vlm_tokens (num_tokens)]
            end_pos = encoder_hidden_states.shape[1] - self.num_tokens
            clip_hidden_states, vlm_hidden_states = (
                encoder_hidden_states[:, :end_pos, :],      # CLIP text
                encoder_hidden_states[:, end_pos:, :],       # VLM tokens
            )
            
            if attn.norm_cross:
                clip_hidden_states = attn.norm_encoder_hidden_states(clip_hidden_states)

        # Ensure dtypes still match attention weights (use UNet's dtype)
        clip_hidden_states = clip_hidden_states.to(dtype=target_dtype)
        # Cast VLM inputs to the dtype expected by its projection weights to avoid F.linear dtype mismatches
        vlm_dtype = self.to_k_vlm.weight.dtype
        vlm_hidden_states = vlm_hidden_states.to(dtype=vlm_dtype)

        # Standard K/V from CLIP text
        key = attn.to_k(clip_hidden_states)
        value = attn.to_v(clip_hidden_states)

        # Separate K/V from VLM tokens (ensure dtype consistency at input & outputs)
        # Safety: enforce token feature dimension is 768 (or configured cross_attention_dim)
        expected_dim = self.cross_attention_dim or self.hidden_size
        if vlm_hidden_states.shape[-1] != expected_dim:
            raise RuntimeError(
                f"VLM token dim mismatch: expected {expected_dim}, got {vlm_hidden_states.shape[-1]}"
            )
        vlm_key = self.to_k_vlm(vlm_hidden_states)
        vlm_value = self.to_v_vlm(vlm_hidden_states)

        # Unify all projections to a single dtype for subsequent attention math
        attn_dtype = query.dtype
        key = key.to(dtype=attn_dtype)
        value = value.to(dtype=attn_dtype)
        vlm_key = vlm_key.to(dtype=attn_dtype)
        vlm_value = vlm_value.to(dtype=attn_dtype)

        # Reshape for multi-head attention
        query = attn.head_to_batch_dim(query)
        key = attn.head_to_batch_dim(key)
        value = attn.head_to_batch_dim(value)
        vlm_key = attn.head_to_batch_dim(vlm_key)
        vlm_value = attn.head_to_batch_dim(vlm_value)

        # Attention with CLIP text (standard path)
        attention_probs = attn.get_attention_scores(query, key, attention_mask)
        text_hidden_states = torch.bmm(attention_probs, value)

        # Attention with VLM tokens (IP-Adapter style)
        vlm_attention_probs = attn.get_attention_scores(query, vlm_key, None)
        vlm_hidden_states = torch.bmm(vlm_attention_probs, vlm_value)

        # RMSNorm both branches before mixing to prevent norm drift
        def rms_norm(x, eps=1e-6):
            """Root Mean Square Normalization"""
            norm = torch.sqrt(torch.mean(x * x, dim=-1, keepdim=True) + eps)
            return x / norm

        # Normalize both branches
        text_hidden_states = rms_norm(text_hidden_states)
        vlm_hidden_states = rms_norm(vlm_hidden_states)

        # Combine: normalized text + learnable-gated normalized VLM
        hidden_states = text_hidden_states + self.vlm_gate * vlm_hidden_states

        hidden_states = attn.batch_to_head_dim(hidden_states)

        # linear proj
        hidden_states = attn.to_out[0](hidden_states)
        # dropout
        hidden_states = attn.to_out[1](hidden_states)

        if input_ndim == 4:
            hidden_states = hidden_states.transpose(-1, -2).reshape(batch_size, channel, height, width)

        if attn.residual_connection:
            hidden_states = hidden_states + residual.to(dtype=hidden_states.dtype)

        hidden_states = hidden_states / attn.rescale_output_factor

        return hidden_states
    
    def get_gate_value(self):
        """Get current VLM gate value for logging"""
        return self.vlm_gate.item()


def init_vlm_adapter_modules(unet, target_blocks=None, scale=1.0, num_tokens=16, lora_rank=8, lora_alpha=16.0):
    """
    Initialize VLM-Adapter modules in the UNet.
    
    Injects VLMAttnProcessor into cross-attention layers (attn2) of specified blocks.
    Following IP-Adapter best practices: mid_block + late up_blocks.
    
    Args:
        unet: The InstructPix2Pix UNet model
        target_blocks: List of blocks to inject (e.g., ["mid_block", "up_blocks.1", "up_blocks.2", "up_blocks.3"])
                      If None, defaults to IP-Adapter style (mid + late up)
        scale: Scaling factor for VLM influence
        num_tokens: Number of VLM tokens from resampler
    
    Returns:
        adapter_modules: ModuleList of VLM adapter parameters (to_k_vlm, to_v_vlm)
    """
    if target_blocks is None:
        target_blocks = ["mid_block", "up_blocks.1", "up_blocks.2", "up_blocks.3"]
    
    print(f"Initializing VLM-Adapter modules in blocks: {target_blocks}")
    
    attn_procs = {}
    unet_sd = unet.state_dict()
    
    for name in unet.attn_processors.keys():
        # Check if this is cross-attention (attn2)
        cross_attention_dim = None if name.endswith("attn1.processor") else unet.config.cross_attention_dim
        
        # Determine block type
        if name.startswith("mid_block"):
            block_name = "mid_block"
            hidden_size = unet.config.block_out_channels[-1]
        elif name.startswith("up_blocks"):
            block_id = int(name[len("up_blocks.")])
            block_name = f"up_blocks.{block_id}"
            hidden_size = list(reversed(unet.config.block_out_channels))[block_id]
        elif name.startswith("down_blocks"):
            block_id = int(name[len("down_blocks.")])
            block_name = f"down_blocks.{block_id}"
            hidden_size = unet.config.block_out_channels[block_id]
        else:
            block_name = "other"
            hidden_size = None
        
        # Use VLMAttnProcessor for target cross-attention blocks
        if cross_attention_dim is not None and block_name in target_blocks:
            print(f"  ✓ Injecting VLM-Adapter to: {name} (hidden_size={hidden_size})")
            
            # Create VLM processor
            attn_procs[name] = VLMAttnProcessor(
                hidden_size=hidden_size,
                cross_attention_dim=cross_attention_dim,
                scale=scale,
                num_tokens=num_tokens,
                lora_rank=lora_rank,
                lora_alpha=lora_alpha
            )
            
            # Initialize base K/V with original text K/V weights as a sensible start
            layer_name = name.split(".processor")[0]
            if layer_name + ".to_k.weight" in unet_sd:
                orig_k = unet_sd[layer_name + ".to_k.weight"].clone()
                orig_v = unet_sd[layer_name + ".to_v.weight"].clone()
                # If dims match, copy; otherwise keep default init
                if attn_procs[name].to_k_vlm.weight.shape == orig_k.shape:
                    attn_procs[name].to_k_vlm.weight.data.copy_(orig_k)
                if attn_procs[name].to_v_vlm.weight.shape == orig_v.shape:
                    attn_procs[name].to_v_vlm.weight.data.copy_(orig_v)
        else:
            # Use default processor for self-attention and non-target blocks
            attn_procs[name] = AttnProcessor()
    
    unet.set_attn_processor(attn_procs)
    
    # Collect VLM adapter modules for training
    adapter_modules = torch.nn.ModuleList([
        proc for proc in unet.attn_processors.values() 
        if isinstance(proc, VLMAttnProcessor)
    ])
    
    print(f"✓ Initialized {len(adapter_modules)} VLM-Adapter modules")
    
    return adapter_modules


if __name__ == "__main__":
    print("VLM Attention Processor module")
    print("This module provides attention processors for VLM-guided InstructPix2Pix")