@@ -59,9 +59,9 @@ class FlowMapLossMixin:
5959 training objective; distribution-matching methods can co-train it alongside
6060 their own objective.
6161
62- The host class must provide ``net``, ``device``, ``config``,
63- ``precision_amp``, a ``_get_velocity`` implementation , and call
64- ``_init_flow_map_loss`` from its ``__init__``.
62+ The host class must provide ``net``, ``device``, ``config`` and ``precision_amp``
63+ (plus ``teacher`` when ``loss_config.use_cd``) , and call ``_init_flow_map_loss``
64+ from its ``__init__``.
6565 """
6666
6767 def _init_flow_map_loss (self ) -> None :
@@ -70,7 +70,8 @@ def _init_flow_map_loss(self) -> None:
7070 self .sample_r_cfg = self .config .sample_r_cfg
7171 self .loss_config = self .config .loss_config
7272
73- # Precision for JVP
73+ # Drop the JVP autocast when it matches the outer one, since a nested region in the
74+ # same dtype is a no-op.
7475 if self .config .precision_amp_jvp is None or self .config .precision_amp_jvp == self .precision_amp :
7576 self .precision_amp_jvp = None
7677 else :
@@ -89,28 +90,29 @@ def _init_flow_map_loss(self) -> None:
8990 grid = shift * grid / (1 + (shift - 1 ) * grid )
9091 self ._timestep_weight_scale = float (num_steps / self ._timestep_weight_raw (grid ).sum ())
9192
92- def _drop_condition (self , condition : Any , neg_condition : Any ) -> Tuple [Any , Optional [torch .Tensor ]]:
93+ def _drop_condition (
94+ self , condition : Any , neg_condition : Any , batch_size : int , device : torch .device
95+ ) -> Tuple [Any , torch .Tensor ]:
9396 """Replace the condition with neg_condition for a per-sample subset.
9497
95- Returns ``(condition, keep)``; ``keep`` is the ``[B]`` bool mask (None if
96- no dropout), so callers can reuse the same subset. Keys in
97- ``cond_keys_no_dropout`` are never replaced.
98+ Returns ``(condition, keep)``; ``keep`` is the ``[B]`` bool mask of the
99+ samples that stayed conditional, so callers can reuse the same subset.
98100
99101 ``deterministic_buckets`` decides whether an index carries bucket
100102 information: if so the buckets are cut on the GLOBAL index and an
101103 index-based rule would only hit flow matching on rank 0, so draw per
102104 sample; otherwise drop the first ``num_to_drop``.
103105 """
106+ # Dropout disabled, or no negative condition to swap in: every sample
107+ # stays conditional.
104108 if self .config .cond_dropout_prob is None or neg_condition is None :
105- return condition , None
109+ return condition , torch . ones ( batch_size , dtype = torch . bool , device = device )
106110
107- ref = neg_condition if isinstance (neg_condition , torch .Tensor ) else next (iter (neg_condition .values ()))
108- batch_size = ref .shape [0 ]
109111 if self .sample_t_cfg .deterministic_buckets :
110- keep = torch .rand (batch_size , device = ref . device ) >= self .config .cond_dropout_prob
112+ keep = torch .rand (batch_size , device = device ) >= self .config .cond_dropout_prob
111113 else :
112- num_to_drop = (torch .rand (batch_size , device = ref . device ) < self .config .cond_dropout_prob ).sum ()
113- keep = torch .arange (batch_size , device = ref . device ) >= num_to_drop
114+ num_to_drop = (torch .rand (batch_size , device = device ) < self .config .cond_dropout_prob ).sum ()
115+ keep = torch .arange (batch_size , device = device ) >= num_to_drop
114116
115117 if isinstance (condition , torch .Tensor ):
116118 return torch .where (expand_like (keep , condition ), condition , neg_condition ), keep
@@ -133,8 +135,9 @@ def _get_velocity(
133135 t : torch .Tensor ,
134136 condition : Optional [Any ] = None ,
135137 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+ ) -> Tuple [Any , torch .Tensor , torch .Tensor ]:
139+ """Regression target for the flow-map loss, the condition it was built from,
140+ and the ``[B]`` mask of samples that stayed conditional.
138141
139142 Two independent choices:
140143
@@ -148,7 +151,9 @@ def _get_velocity(
148151 fuse_scale = self .config .guidance_fuse_scale
149152 if fuse_scale is not None :
150153 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 )
154+ condition , keep = self ._drop_condition (condition , neg_condition , x .shape [0 ], x .device )
155+ else :
156+ keep = torch .ones (x .shape [0 ], dtype = torch .bool , device = x .device )
152157
153158 x_t = self .net .noise_scheduler .forward_process (x , z , t )
154159
@@ -201,13 +206,12 @@ def _get_velocity(
201206 )
202207
203208 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+ condition , keep = self ._drop_condition (condition , neg_condition , x_t .shape [0 ], x_t .device )
210+ # Same subset: a kept sample is conditional + guided, a dropped one
211+ # unconditional + unguided.
212+ dxt_dt = torch .where (expand_like (keep , dxt_dt ), guided_dxt_dt , dxt_dt )
209213
210- return condition , dxt_dt
214+ return condition , dxt_dt , keep
211215
212216 def _estimate_jvp_finite_difference (
213217 self ,
@@ -573,7 +577,7 @@ def _compute_mf_loss(
573577 z = torch .randn_like (real_data )
574578 x_t = self .net .noise_scheduler .forward_process (real_data , z , t )
575579
576- condition , dxt_dt = self ._get_velocity (real_data , z , t , condition = condition , neg_condition = neg_condition )
580+ condition , dxt_dt , keep = self ._get_velocity (real_data , z , t , condition = condition , neg_condition = neg_condition )
577581 # prevent JVP to use cached conversions (which can break the computational graph) that were created in the no_grad context of _get_velocity
578582 torch .clear_autocast_cache ()
579583 u_theta_jvp = self ._jvp (x_t , t , r , dxt_dt , condition = condition )
@@ -594,9 +598,10 @@ def _compute_mf_loss(
594598 # Guidance distillation on the PREDICTION side (see `_get_velocity`): the
595599 # conditional output learns the guided flow directly, so only the prediction
596600 # 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
601+ # giving (u_cond + (g - 1) * u_uncond) / g; dF/dt is then the finite
602+ # difference over g on conditional samples, with the unconditional
603+ # derivative dropped.
604+ u_theta_jvp = torch .where (expand_like (keep , u_theta_jvp ), u_theta_jvp / guidance_fuse_scale , u_theta_jvp )
600605 with torch .no_grad ():
601606 u_uncond = self .net (x_t , t , r = r , condition = neg_condition , fwd_pred_type = "flow" )
602607 u_theta = (u_theta + (guidance_fuse_scale - 1.0 ) * u_uncond ) / guidance_fuse_scale
0 commit comments