@@ -125,6 +125,90 @@ def _drop_condition(self, condition: Any, neg_condition: Any) -> Tuple[Any, Opti
125125 return condition , keep
126126 raise TypeError (f"Unsupported condition type: { type (condition )} " )
127127
128+ @torch .no_grad ()
129+ def _get_velocity (
130+ self ,
131+ x : torch .Tensor ,
132+ z : torch .Tensor ,
133+ t : torch .Tensor ,
134+ condition : Optional [Any ] = None ,
135+ neg_condition : Optional [Any ] = None ,
136+ ) -> Tuple [Any , torch .Tensor ]:
137+ """Regression target for the flow-map loss, plus the condition it was built from.
138+
139+ Two independent choices:
140+
141+ * ``loss_config.use_cd`` picks the target SOURCE -- the teacher, or the
142+ conditional data velocity.
143+ * ``guidance_fuse_scale`` guides the *prediction* in
144+ ``_compute_mf_loss``, so the target needs no guidance of its own and we only
145+ drop the condition. Otherwise we guide the target too: through the teacher, or
146+ -- without one -- by the net's own cond/uncond pass.
147+ """
148+ fuse_scale = self .config .guidance_fuse_scale
149+ if fuse_scale is not None :
150+ assert fuse_scale > 0 , f"guidance_fuse_scale must be > 0, got { fuse_scale } (None disables fusion)"
151+ condition , _ = self ._drop_condition (condition , neg_condition )
152+
153+ x_t = self .net .noise_scheduler .forward_process (x , z , t )
154+
155+ if self .loss_config .use_cd :
156+ dxt_dt = self .teacher (x_t , t , condition = condition , fwd_pred_type = "flow" )
157+ # Under fusion the target stays unguided, or it would be guided twice.
158+ if self .config .guidance_scale is not None and fuse_scale is None :
159+ guidance_scale = torch .where (
160+ ((t >= self .config .guidance_t_start ) & (t <= self .config .guidance_t_end )),
161+ self .config .guidance_scale ,
162+ 1.0 ,
163+ )
164+ guidance_scale = expand_like (guidance_scale , x_t ).to (dtype = x_t .dtype )
165+ neg_dxt_dt = self .teacher (x_t , t , condition = neg_condition , fwd_pred_type = "flow" )
166+ dxt_dt = dxt_dt + (guidance_scale - 1.0 ) * (dxt_dt - neg_dxt_dt )
167+ else :
168+ dxt_dt = self .net .noise_scheduler .cond_velocity (x = x , eps = z , t = t )
169+
170+ # unconditional score estimation from meanflow eq (19). Skipped under
171+ # prediction-side fusion: that is this same guidance, moved onto the
172+ # prediction, and the dropout it needs already ran above.
173+ if fuse_scale is None and (
174+ self .config .guidance_scale is not None or self .config .guidance_mixture_ratio is not None
175+ ):
176+ # Turn off dropout
177+ self .net .eval ()
178+ neg_dxt_dt = self .net (x_t , t , r = t , condition = neg_condition , fwd_pred_type = "flow" )
179+ guidance_scale = self .config .guidance_scale or 1.0
180+ guidance_scale = torch .where (
181+ ((t >= self .config .guidance_t_start ) & (t <= self .config .guidance_t_end )),
182+ guidance_scale ,
183+ 1.0 ,
184+ )
185+ guidance_scale = expand_like (guidance_scale , x_t ).to (dtype = x_t .dtype )
186+
187+ if self .config .guidance_mixture_ratio is None :
188+ guided_dxt_dt = neg_dxt_dt + guidance_scale * (dxt_dt - neg_dxt_dt )
189+ else :
190+ guidance_mixture_ratio = torch .where (
191+ ((t >= self .config .guidance_t_start ) & (t <= self .config .guidance_t_end )),
192+ self .config .guidance_mixture_ratio ,
193+ 0.0 ,
194+ )
195+ guidance_mixture_ratio = expand_like (guidance_mixture_ratio , x_t ).to (dtype = x_t .dtype )
196+ cond_dxt_dt = self .net (x_t , t , r = t , condition = condition , fwd_pred_type = "flow" )
197+ guided_dxt_dt = (
198+ guidance_scale * dxt_dt
199+ + (1.0 - guidance_scale - guidance_mixture_ratio ) * neg_dxt_dt
200+ + guidance_mixture_ratio * cond_dxt_dt
201+ )
202+
203+ self .net .train ()
204+ condition , keep = self ._drop_condition (condition , neg_condition )
205+ if keep is not None :
206+ # Same subset: a kept sample is conditional + guided, a
207+ # dropped one unconditional + unguided.
208+ dxt_dt = torch .where (expand_like (keep , dxt_dt ), guided_dxt_dt , dxt_dt )
209+
210+ return condition , dxt_dt
211+
128212 def _estimate_jvp_finite_difference (
129213 self ,
130214 net_wrapper : Callable [[torch .Tensor , torch .Tensor , torch .Tensor ], torch .Tensor ],
@@ -489,50 +573,34 @@ def _compute_mf_loss(
489573 z = torch .randn_like (real_data )
490574 x_t = self .net .noise_scheduler .forward_process (real_data , z , t )
491575
492- guidance_fuse_scale = getattr (self .loss_config , "guidance_fuse_scale" , None )
576+ condition , dxt_dt = self ._get_velocity (real_data , z , t , condition = condition , neg_condition = neg_condition )
577+ # prevent JVP to use cached conversions (which can break the computational graph) that were created in the no_grad context of _get_velocity
578+ torch .clear_autocast_cache ()
579+ u_theta_jvp = self ._jvp (x_t , t , r , dxt_dt , condition = condition )
580+ assert not u_theta_jvp .requires_grad , "u_theta_jvp should not require gradients"
581+
582+ # additional forward pass to get u_theta with gradient; see also https://github.com/Gsunshine/py-meanflow?tab=readme-ov-file#note-on-jvp
583+ assert x_t .dtype == real_data .dtype , f"x_t.dtype: { x_t .dtype } , real_data.dtype: { real_data .dtype } "
584+ u_theta = self .net (
585+ x_t ,
586+ t ,
587+ r = r ,
588+ condition = condition ,
589+ fwd_pred_type = "flow" ,
590+ )
591+
592+ guidance_fuse_scale = self .config .guidance_fuse_scale
493593 if guidance_fuse_scale is not None :
494- # Guidance distillation fused on the PREDICTION side: the
495- # conditional output is trained to be the guided flow directly.
496- # The regression target stays the raw data velocity, the
497- # unconditional branch is queried at the SAME (t, r) flow-map
498- # slice, and the effective prediction is
499- # (u_cond + (g - 1) * u_uncond) / g. Text dropout replaces the
500- # condition with neg_condition for a random subset beforehand.
501- g = float (guidance_fuse_scale )
502- assert g > 0 , f"guidance_fuse_scale must be > 0, got { g } (set it to None to disable guidance fusion)"
503- assert (
504- neg_condition is not None
505- ), "guidance_fuse_scale requires neg_condition: the unconditional branch is queried at the same (t, r)"
506- condition , _ = self ._drop_condition (condition , neg_condition )
507- dxt_dt = self .net .noise_scheduler .cond_velocity (x = real_data , eps = z , t = t )
508- torch .clear_autocast_cache ()
509- # dF/dt of the fused prediction: finite difference of the
510- # conditional output divided by g (the unconditional derivative is
511- # dropped).
512- u_theta_jvp = self ._jvp (x_t , t , r , dxt_dt , condition = condition ) / g
513- assert not u_theta_jvp .requires_grad , "u_theta_jvp should not require gradients"
514-
515- assert x_t .dtype == real_data .dtype , f"x_t.dtype: { x_t .dtype } , real_data.dtype: { real_data .dtype } "
516- u_theta = self .net (x_t , t , r = r , condition = condition , fwd_pred_type = "flow" )
594+ # Guidance distillation on the PREDICTION side (see `_get_velocity`): the
595+ # conditional output learns the guided flow directly, so only the prediction
596+ # changes. The uncond branch is queried at the SAME (t, r) flow-map slice,
597+ # giving (u_cond + (g - 1) * u_uncond) / g; dF/dt is then the conditional
598+ # finite difference over g, with the unconditional derivative dropped.
599+ u_theta_jvp = u_theta_jvp / guidance_fuse_scale
517600 with torch .no_grad ():
518601 u_uncond = self .net (x_t , t , r = r , condition = neg_condition , fwd_pred_type = "flow" )
519- u_theta = (u_theta + (g - 1.0 ) * u_uncond ) / g
520- else :
521- condition , dxt_dt = self ._get_velocity (real_data , z , t , condition = condition , neg_condition = neg_condition )
522- # prevent JVP to use cached conversions (which can break the computational graph) that were created in the no_grad context of _get_velocity
523- torch .clear_autocast_cache ()
524- u_theta_jvp = self ._jvp (x_t , t , r , dxt_dt , condition = condition )
525- assert not u_theta_jvp .requires_grad , "u_theta_jvp should not require gradients"
526-
527- # additional forward pass to get u_theta with gradient; see also https://github.com/Gsunshine/py-meanflow?tab=readme-ov-file#note-on-jvp
528- assert x_t .dtype == real_data .dtype , f"x_t.dtype: { x_t .dtype } , real_data.dtype: { real_data .dtype } "
529- u_theta = self .net (
530- x_t ,
531- t ,
532- r = r ,
533- condition = condition ,
534- fwd_pred_type = "flow" ,
535- )
602+ u_theta = (u_theta + (guidance_fuse_scale - 1.0 ) * u_uncond ) / guidance_fuse_scale
603+
536604 mf_loss , tangent , loss_weight , warmup_weight = self ._mf_pred_to_loss (
537605 u_theta = u_theta , u_theta_jvp = u_theta_jvp , x_t = x_t , dxt_dt = dxt_dt , t = t , r = r , iteration = iteration
538606 )
@@ -564,69 +632,6 @@ def __init__(self, config: ModelConfig):
564632 self .config = config
565633 self ._init_flow_map_loss ()
566634
567- @torch .no_grad ()
568- def _get_velocity (
569- self ,
570- x : torch .Tensor ,
571- z : torch .Tensor ,
572- t : torch .Tensor ,
573- condition : Optional [torch .Tensor ] = None ,
574- neg_condition : Optional [torch .Tensor ] = None ,
575- ) -> Tuple [torch .Tensor , torch .Tensor ]:
576- x_t = self .net .noise_scheduler .forward_process (x , z , t )
577-
578- if self .loss_config .use_cd :
579- dxt_dt = self .teacher (x_t , t , condition = condition , fwd_pred_type = "flow" )
580- if self .config .guidance_scale is not None :
581- guidance_scale = torch .where (
582- ((t >= self .config .guidance_t_start ) & (t <= self .config .guidance_t_end )),
583- self .config .guidance_scale ,
584- 1.0 ,
585- )
586- guidance_scale = expand_like (guidance_scale , x_t ).to (dtype = x_t .dtype )
587- neg_dxt_dt = self .teacher (x_t , t , condition = neg_condition , fwd_pred_type = "flow" )
588- dxt_dt = dxt_dt + (guidance_scale - 1.0 ) * (dxt_dt - neg_dxt_dt )
589- else :
590- dxt_dt = self .net .noise_scheduler .cond_velocity (x = x , eps = z , t = t )
591-
592- # unconditional score estimation from meanflow eq (19)
593- if self .config .guidance_scale is not None or self .config .guidance_mixture_ratio is not None :
594- # Turn off dropout
595- self .net .eval ()
596- neg_dxt_dt = self .net (x_t , t , r = t , condition = neg_condition , fwd_pred_type = "flow" )
597- guidance_scale = self .config .guidance_scale or 1.0
598- guidance_scale = torch .where (
599- ((t >= self .config .guidance_t_start ) & (t <= self .config .guidance_t_end )),
600- guidance_scale ,
601- 1.0 ,
602- )
603- guidance_scale = expand_like (guidance_scale , x_t ).to (dtype = x_t .dtype )
604-
605- if self .config .guidance_mixture_ratio is None :
606- guided_dxt_dt = neg_dxt_dt + guidance_scale * (dxt_dt - neg_dxt_dt )
607- else :
608- guidance_mixture_ratio = torch .where (
609- ((t >= self .config .guidance_t_start ) & (t <= self .config .guidance_t_end )),
610- self .config .guidance_mixture_ratio ,
611- 0.0 ,
612- )
613- guidance_mixture_ratio = expand_like (guidance_mixture_ratio , x_t ).to (dtype = x_t .dtype )
614- cond_dxt_dt = self .net (x_t , t , r = t , condition = condition , fwd_pred_type = "flow" )
615- guided_dxt_dt = (
616- guidance_scale * dxt_dt
617- + (1.0 - guidance_scale - guidance_mixture_ratio ) * neg_dxt_dt
618- + guidance_mixture_ratio * cond_dxt_dt
619- )
620-
621- self .net .train ()
622- condition , keep = self ._drop_condition (condition , neg_condition )
623- if keep is not None :
624- # Same subset: a kept sample is conditional + guided, a
625- # dropped one unconditional + unguided.
626- dxt_dt = torch .where (expand_like (keep , dxt_dt ), guided_dxt_dt , dxt_dt )
627-
628- return condition , dxt_dt
629-
630635 def single_train_step (
631636 self , data : Dict [str , Any ], iteration : int
632637 ) -> tuple [dict [str , torch .Tensor ], dict [str , torch .Tensor | Callable ]]:
0 commit comments