1313from .modules import precompute_freqs_cis_2d
1414
1515
16- class SigmaAwareGatePerTokenPerDim (nn .Module ):
16+ class SigmaAwareGate (nn .Module ):
1717 """gate = sigmoid(content_proj(cat[x, lq]) - exp(log_alpha) * sigma); out = x + gate * lq.
1818
1919 Trained init gives ~0.88 gate at sigma=0, ~0.05 at sigma=1.
2020 """
2121
22- def __init__ (self , dim : int , dtype = None , device = None , operations = None ):
22+ def __init__ (self , dim : int , per_token : bool = False , dtype = None , device = None , operations = None ):
2323 super ().__init__ ()
24- self .content_proj = operations .Linear (dim * 2 , dim , dtype = dtype , device = device )
24+ self .content_proj = operations .Linear (dim * 2 , 1 if per_token else dim , dtype = dtype , device = device )
2525 self .log_alpha = nn .Parameter (torch .empty ((), dtype = dtype , device = device ))
2626
2727 def forward (self , x : torch .Tensor , lq : torch .Tensor , sigma : torch .Tensor ) -> torch .Tensor :
@@ -36,15 +36,15 @@ def forward(self, x: torch.Tensor, lq: torch.Tensor, sigma: torch.Tensor) -> tor
3636class ResBlock (nn .Module ):
3737 """Pre-activation ResNet block: GN -> SiLU -> Conv -> GN -> SiLU -> Conv + skip."""
3838
39- def __init__ (self , channels : int , num_groups : int = 4 , dtype = None , device = None , operations = None ):
39+ def __init__ (self , channels : int , num_groups : int = 4 , conv_padding_mode : str = "zeros" , dtype = None , device = None , operations = None ):
4040 super ().__init__ ()
4141 self .block = nn .Sequential (
4242 operations .GroupNorm (num_groups , channels , dtype = dtype , device = device ),
4343 nn .SiLU (),
44- operations .Conv2d (channels , channels , kernel_size = 3 , padding = 1 , dtype = dtype , device = device ),
44+ operations .Conv2d (channels , channels , kernel_size = 3 , padding = 1 , padding_mode = conv_padding_mode , dtype = dtype , device = device ),
4545 operations .GroupNorm (num_groups , channels , dtype = dtype , device = device ),
4646 nn .SiLU (),
47- operations .Conv2d (channels , channels , kernel_size = 3 , padding = 1 , dtype = dtype , device = device ),
47+ operations .Conv2d (channels , channels , kernel_size = 3 , padding = 1 , padding_mode = conv_padding_mode , dtype = dtype , device = device ),
4848 )
4949
5050 def forward (self , x : torch .Tensor ) -> torch .Tensor :
@@ -62,9 +62,13 @@ def __init__(
6262 patch_size : int = 16 ,
6363 sr_scale : int = 4 ,
6464 latent_spatial_down_factor : int = 8 ,
65+ latent_unpatchify_factor : int = 1 ,
6566 num_res_blocks : int = 4 ,
6667 num_outputs : int = 7 ,
6768 interval : int = 2 ,
69+ conv_padding_mode : str = "zeros" ,
70+ gate_per_token : bool = False ,
71+ pit_output : bool = False ,
6872 dtype = None , device = None , operations = None ,
6973 ):
7074 super ().__init__ ()
@@ -74,34 +78,38 @@ def __init__(
7478 self .patch_size = patch_size
7579 self .sr_scale = sr_scale
7680 self .latent_spatial_down_factor = latent_spatial_down_factor
81+ self .latent_unpatchify_factor = latent_unpatchify_factor
7782 self .num_outputs = num_outputs
7883 self .interval = interval
7984
80- z_to_patch_ratio = (sr_scale * latent_spatial_down_factor ) / patch_size
85+ effective_latent_channels = latent_channels // (latent_unpatchify_factor * latent_unpatchify_factor )
86+ effective_spatial_down_factor = latent_spatial_down_factor // latent_unpatchify_factor
87+ z_to_patch_ratio = (sr_scale * effective_spatial_down_factor ) / patch_size
8188 self .z_to_patch_ratio = z_to_patch_ratio
8289 if z_to_patch_ratio >= 1 :
8390 self .latent_fold_factor = 0
84- latent_proj_in_ch = latent_channels
91+ latent_proj_in_ch = effective_latent_channels
8592 else :
8693 fold_factor = int (1 / z_to_patch_ratio )
8794 assert fold_factor * z_to_patch_ratio == 1.0
8895 self .latent_fold_factor = fold_factor
89- latent_proj_in_ch = latent_channels * fold_factor * fold_factor
96+ latent_proj_in_ch = effective_latent_channels * fold_factor * fold_factor
9097
9198 layers = [
92- operations .Conv2d (latent_proj_in_ch , hidden_dim , kernel_size = 3 , padding = 1 , dtype = dtype , device = device ),
99+ operations .Conv2d (latent_proj_in_ch , hidden_dim , kernel_size = 3 , padding = 1 , padding_mode = conv_padding_mode , dtype = dtype , device = device ),
93100 nn .SiLU (),
94- operations .Conv2d (hidden_dim , hidden_dim , kernel_size = 3 , padding = 1 , dtype = dtype , device = device ),
101+ operations .Conv2d (hidden_dim , hidden_dim , kernel_size = 3 , padding = 1 , padding_mode = conv_padding_mode , dtype = dtype , device = device ),
95102 ]
96103 for _ in range (num_res_blocks ):
97- layers .append (ResBlock (hidden_dim , dtype = dtype , device = device , operations = operations ))
104+ layers .append (ResBlock (hidden_dim , conv_padding_mode = conv_padding_mode , dtype = dtype , device = device , operations = operations ))
98105 self .latent_proj = nn .Sequential (* layers )
99106
100107 self .output_heads = nn .ModuleList (
101108 [operations .Linear (hidden_dim , out_dim , dtype = dtype , device = device ) for _ in range (num_outputs )]
102109 )
110+ self .pit_head = operations .Linear (hidden_dim , out_dim , dtype = dtype , device = device ) if pit_output else None
103111 self .gate_modules = nn .ModuleList (
104- [SigmaAwareGatePerTokenPerDim (out_dim , dtype = dtype , device = device , operations = operations )
112+ [SigmaAwareGate (out_dim , per_token = gate_per_token , dtype = dtype , device = device , operations = operations )
105113 for _ in range (num_outputs )]
106114 )
107115
@@ -115,6 +123,11 @@ def gate(self, x: torch.Tensor, lq_feature: torch.Tensor, sigma: torch.Tensor, o
115123 return self .gate_modules [out_idx ](x , lq_feature , sigma )
116124
117125 def _align_latent_to_patch_grid (self , lq_latent : torch .Tensor , pH : int , pW : int ) -> torch .Tensor :
126+ f = self .latent_unpatchify_factor
127+ if f > 1 :
128+ B , C , H , W = lq_latent .shape
129+ lq_latent = lq_latent .reshape (B , C // (f * f ), f , f , H , W )
130+ lq_latent = lq_latent .permute (0 , 1 , 4 , 2 , 5 , 3 ).reshape (B , C // (f * f ), H * f , W * f )
118131 B , z_dim = lq_latent .shape [:2 ]
119132 if self .z_to_patch_ratio >= 1 :
120133 if lq_latent .shape [2 ] != pH or lq_latent .shape [3 ] != pW :
@@ -134,7 +147,10 @@ def forward(self, lq_latent: torch.Tensor, target_pH: int, target_pW: int) -> Li
134147 feat = self ._align_latent_to_patch_grid (lq_latent , target_pH , target_pW )
135148 B , C , H , W = feat .shape
136149 tokens = feat .permute (0 , 2 , 3 , 1 ).contiguous ().view (B , H * W , C )
137- return [head (tokens ) for head in self .output_heads ]
150+ outputs = [head (tokens ) for head in self .output_heads ]
151+ if self .pit_head is not None :
152+ outputs .append (self .pit_head (tokens ))
153+ return outputs
138154
139155
140156class PidNet (PixDiT_T2I ):
@@ -148,6 +164,10 @@ def __init__(
148164 lq_interval : int = 2 ,
149165 sr_scale : int = 4 ,
150166 latent_spatial_down_factor : int = 8 ,
167+ lq_latent_unpatchify_factor : int = 1 ,
168+ lq_conv_padding_mode : str = "zeros" ,
169+ lq_gate_per_token : bool = False ,
170+ pit_lq_inject : bool = False ,
151171 rope_ref_h : int = 1024 , # NTK ref resolution in PIXEL units: 1024px / patch=16 -> grid_ref=64.
152172 rope_ref_w : int = 1024 ,
153173 image_model = None ,
@@ -165,6 +185,8 @@ def _pit_rope_fn(head_dim, h, w, device=None, dtype=torch.float32, **rope_opts):
165185 for blk in self .pixel_blocks :
166186 blk ._rope_fn = _pit_rope_fn
167187
188+ self .pit_lq_inject = pit_lq_inject
189+
168190 num_lq_outputs = (self .patch_depth + lq_interval - 1 ) // lq_interval
169191 self .lq_proj = LQProjection2D (
170192 latent_channels = lq_latent_channels ,
@@ -173,13 +195,20 @@ def _pit_rope_fn(head_dim, h, w, device=None, dtype=torch.float32, **rope_opts):
173195 patch_size = self .patch_size ,
174196 sr_scale = sr_scale ,
175197 latent_spatial_down_factor = latent_spatial_down_factor ,
198+ latent_unpatchify_factor = lq_latent_unpatchify_factor ,
176199 num_res_blocks = lq_num_res_blocks ,
177200 num_outputs = num_lq_outputs ,
178201 interval = lq_interval ,
202+ conv_padding_mode = lq_conv_padding_mode ,
203+ gate_per_token = lq_gate_per_token ,
204+ pit_output = pit_lq_inject ,
179205 dtype = dtype ,
180206 device = device ,
181207 operations = operations ,
182208 )
209+ self .pit_lq_gate = SigmaAwareGate (
210+ self .hidden_size , per_token = lq_gate_per_token , dtype = dtype , device = device , operations = operations
211+ ) if pit_lq_inject else None
183212
184213 def _fetch_patch_pos (self , height , width , device , dtype , ** rope_opts ):
185214 return precompute_freqs_cis_2d (
@@ -197,6 +226,11 @@ def _pre_patch_block(self, s, i, pid_lq_features, pid_degrade_sigma, **kwargs):
197226 return s
198227 return self .lq_proj .gate (s , pid_lq_features [out_idx ], pid_degrade_sigma , out_idx )
199228
229+ def _pre_pixel_blocks (self , s , pid_pit_lq_feature = None , pid_degrade_sigma = None , ** kwargs ):
230+ if pid_pit_lq_feature is None :
231+ return s
232+ return self .pit_lq_gate (s , pid_pit_lq_feature , pid_degrade_sigma )
233+
200234 def _forward (self , x , timesteps , context = None , attention_mask = None , transformer_options = {}, lq_latent = None , degrade_sigma = None , ** kwargs ):
201235 if lq_latent is None :
202236 raise ValueError ("PidNet requires lq_latent — attach via PiDConditioning" )
@@ -216,12 +250,14 @@ def _forward(self, x, timesteps, context=None, attention_mask=None, transformer_
216250 degrade_sigma = degrade_sigma .expand (B ).contiguous ()
217251
218252 lq_features = self .lq_proj (lq_latent = lq_latent .to (x ), target_pH = Hs , target_pW = Ws )
253+ pit_lq_feature = lq_features .pop () if self .pit_lq_inject else None
219254
220255 return super ()._forward (
221256 x , timesteps ,
222257 context = context , attention_mask = attention_mask ,
223258 transformer_options = transformer_options ,
224259 pid_lq_features = lq_features ,
260+ pid_pit_lq_feature = pit_lq_feature ,
225261 pid_degrade_sigma = degrade_sigma ,
226262 ** kwargs ,
227263 )
0 commit comments