Skip to content

Commit e6dad12

Browse files
authored
Merge pull request #2138 from bghira/feature/reflexflow-by-default
ReflexFlow: enable by default when flow-matching scheduled sampling is enabled
2 parents 3a0cb68 + f57b74d commit e6dad12

5 files changed

Lines changed: 78 additions & 3 deletions

File tree

documentation/OPTIONS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ See the [DATALOADER.md](DATALOADER.md#automatic-dataset-oversubscription) guide
691691

692692
- **What**: Enable ReflexFlow-style enhancements (anti-drift + frequency-compensated weighting) during scheduled sampling for flow-matching models.
693693
- **Why**: Reduces exposure bias when rolling out flow-matching models by adding directional regularization and bias-aware loss weighting.
694-
- **Default**: False. Requires `--scheduled_sampling_max_step_offset` > 0.
694+
- **Default**: Auto-enable for flow-matching models when `--scheduled_sampling_max_step_offset` > 0; override with `--scheduled_sampling_reflexflow=false`.
695695

696696
### `--scheduled_sampling_reflexflow_alpha`
697697

documentation/experimental/SCHEDULED_SAMPLING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ The solver used for the rollout generation steps.
6565

6666
For flow-matching models (`--prediction_type flow_matching`), scheduled sampling now supports ReflexFlow-style exposure bias mitigation:
6767

68-
* `scheduled_sampling_reflexflow`: Enable ReflexFlow enhancements during rollout.
68+
* `scheduled_sampling_reflexflow`: Enable ReflexFlow enhancements during rollout (auto-enabled for flow-matching models when scheduled sampling is active; pass `--scheduled_sampling_reflexflow=false` to opt out).
6969
* `scheduled_sampling_reflexflow_alpha`: Scale the exposure-bias-based loss weight (frequency compensation).
7070
* `scheduled_sampling_reflexflow_beta1`: Scale the directional anti-drift regularizer (default 10.0 to mirror the paper).
7171
* `scheduled_sampling_reflexflow_beta2`: Scale the frequency-compensated loss (default 1.0).

simpletuner/helpers/models/common.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ def __init__(self, config: dict, accelerator):
258258
self.setup_training_noise_schedule()
259259
self.diff2flow_bridge = None
260260
self.setup_diff2flow_bridge()
261+
self._maybe_enable_reflexflow_default()
261262
self.assistant_adapter_name = "assistant"
262263
self.assistant_lora_loaded = False
263264

@@ -2428,6 +2429,29 @@ def sample_flow_sigmas(self, batch: dict, state: dict) -> tuple[torch.Tensor, to
24282429
timesteps = sigmas * 1000.0
24292430
return sigmas, timesteps
24302431

2432+
def _maybe_enable_reflexflow_default(self) -> bool:
2433+
"""
2434+
Enable ReflexFlow automatically when scheduled sampling is active on flow-matching models
2435+
and the user did not explicitly set the flag.
2436+
"""
2437+
try:
2438+
offset_value = getattr(self.config, "scheduled_sampling_max_step_offset", 0)
2439+
max_offset = float(offset_value or 0)
2440+
except Exception:
2441+
return False
2442+
2443+
if max_offset <= 0:
2444+
return False
2445+
2446+
if getattr(self.config, "scheduled_sampling_reflexflow", None) is not None:
2447+
return False
2448+
2449+
if self.PREDICTION_TYPE is not PredictionTypes.FLOW_MATCHING:
2450+
return False
2451+
2452+
setattr(self.config, "scheduled_sampling_reflexflow", True)
2453+
return True
2454+
24312455
def prepare_batch(self, batch: dict, state: dict) -> dict:
24322456
"""
24332457
Moves the batch to the proper device/dtype,
@@ -2548,6 +2572,7 @@ def prepare_batch(self, batch: dict, state: dict) -> dict:
25482572
batch["timesteps"],
25492573
).to(device=self.accelerator.device, dtype=self.config.weight_dtype)
25502574

2575+
self._maybe_enable_reflexflow_default()
25512576
if getattr(self.config, "scheduled_sampling_max_step_offset", 0) > 0:
25522577
effective_prob = float(getattr(self.config, "scheduled_sampling_probability", 0.0) or 0.0)
25532578
prob_start = float(getattr(self.config, "scheduled_sampling_prob_start", effective_prob) or effective_prob)

simpletuner/simpletuner_sdk/server/services/field_registry/sections/advanced.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def register_advanced_fields(registry: "FieldRegistry") -> None:
319319
tab="training",
320320
section="loss_functions",
321321
subsection="advanced",
322-
default_value=False,
322+
default_value=None,
323323
help_text="Apply ReflexFlow anti-drift and frequency-compensation weighting during scheduled sampling for flow-matching models.",
324324
tooltip="Adds ADR directional regularization and exposure-bias weighting to rollout samples.",
325325
importance=ImportanceLevel.EXPERIMENTAL,

tests/test_scheduled_sampling_rollout.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,56 @@ def test_flow_matching_rollout_updates_sigmas_and_caches(self):
202202
assert "_reflexflow_biased_pred" in updated
203203

204204

205+
class ReflexFlowDefaultToggleTests(unittest.TestCase):
206+
def _make_model(self, prediction_type, config):
207+
class _StubModel(ModelFoundation):
208+
PREDICTION_TYPE = prediction_type
209+
210+
def __init__(self, cfg):
211+
self.config = cfg
212+
213+
def model_predict(self, prepared_batch, custom_timesteps: list | None = None):
214+
return prepared_batch
215+
216+
def _encode_prompts(self, *args, **kwargs):
217+
return None
218+
219+
def convert_text_embed_for_pipeline(self, text_encoder_output, pooling_encode_output=None):
220+
return text_encoder_output
221+
222+
def convert_negative_text_embed_for_pipeline(self, text_encoder_output, pooling_encode_output=None):
223+
return text_encoder_output
224+
225+
return _StubModel(config)
226+
227+
def test_auto_enables_when_unset_for_flow_matching(self):
228+
config = SimpleNamespace(scheduled_sampling_max_step_offset=3, scheduled_sampling_reflexflow=None)
229+
model = self._make_model(PredictionTypes.FLOW_MATCHING, config)
230+
231+
changed = model._maybe_enable_reflexflow_default()
232+
233+
self.assertTrue(changed)
234+
self.assertTrue(config.scheduled_sampling_reflexflow)
235+
236+
def test_respects_user_opt_out(self):
237+
config = SimpleNamespace(scheduled_sampling_max_step_offset=3, scheduled_sampling_reflexflow=False)
238+
model = self._make_model(PredictionTypes.FLOW_MATCHING, config)
239+
240+
changed = model._maybe_enable_reflexflow_default()
241+
242+
self.assertFalse(changed)
243+
self.assertFalse(config.scheduled_sampling_reflexflow)
244+
245+
def test_skips_non_flow_matching_models(self):
246+
config = SimpleNamespace(scheduled_sampling_max_step_offset=3, scheduled_sampling_reflexflow=None)
247+
model = self._make_model(PredictionTypes.EPSILON, config)
248+
249+
changed = model._maybe_enable_reflexflow_default()
250+
251+
self.assertFalse(changed)
252+
self.assertIsNone(config.scheduled_sampling_reflexflow)
253+
254+
205255
class _FlowLossModel(ModelFoundation):
206256
PREDICTION_TYPE = PredictionTypes.FLOW_MATCHING
207257
NAME = "dummy"

0 commit comments

Comments
 (0)