Skip to content

Phase 1: correctness — every audited defect fixed, 16 xfails flipped#11

Open
AshishKumar4 wants to merge 5 commits into
revamp/phase-0-safety-netfrom
revamp/phase-1-correctness
Open

Phase 1: correctness — every audited defect fixed, 16 xfails flipped#11
AshishKumar4 wants to merge 5 commits into
revamp/phase-0-safety-netfrom
revamp/phase-1-correctness

Conversation

@AshishKumar4

Copy link
Copy Markdown
Owner

Second PR of the revamp series — stacked on #10 (review that first; this diff shows only Phase 1 commits).

What

Every defect from the audit, grouped into 5 commits:

Samplers (fix(samplers)): the step domain was hardcoded to [0,1000] and rescaled internally, so the default start_step was wrong for every continuous schedule — DiffusionInferencePipeline.generate_samples() collapsed to a single no-op step at sigma_min and returned the initial noise. Samplers now work in the schedule's native domain and the defaults are correct by construction. Also: DDPMSampler is the algebraically exact ancestral posterior (old one crashed on batch>1 and ignored strides), DDIM eta uses the paper formula (old crashed on .sqrt() and inflated variance), RK4 runs (jitted over a python callable before), MultiStepDPM resets history per trajectory (the pipeline caches samplers), Heun guards sigma=0, all samplers are 5D/video-safe, broken spacing modes deleted (linear on Karras is rho-spacing).

Schedule presets: training.py silently ignored --noise_schedule (always trained EDM) while inference honored it — a cosine-flagged run trained one convention and sampled another, silently. Both sides now build from one get_diffusion_preset, with a test pinning agreement. Plus: UViT double-Hilbert fix (it trained on a spatial scramble), EDM weight epsilon fix (halved the weight at sigma_min), FourierEmbedding frequencies pinned via numpy (jax 0.5.0's PRNG flip silently changed every model's time conditioning), sha256 arguments_hash (was per-process randomized).

Trainer: training without wandb crashed; non-zero processes crashed (pbar); resume silently zeroed adam moments and restarted lr warmup (restore now goes through orbax with the initialized states as a typed template — opt_state, step counter, EMA all round-trip); the NaN "recovery" that reset params mid-epoch and cosmetically patched loss=1.0 is replaced by a loud failure after 5 bad steps; save() no longer deletes checkpoints it didn't push to the registry.

Models: --dropout_rate is now actually implemented (was threaded everywhere, applied nowhere — sweeps over it measured noise); --flash_attention and EfficientAttention are deleted (no sm_scale → 8x inflated logits, crashed off-TPU, incompatible param tree; a real cudnn/splash path comes with the parallelism PR); dead fields removed; fp32 output heads for bf16 models; parse_config filters stale config keys and resolves dotted function paths instead of silently dropping them.

Verification

16 of Phase 0's 17 strict xfails are flipped to passing tests (the 17th, non-square unpatchify, is fixed by the Phase 2 backbone rewrite). Every sampler now converges against the analytic gaussian oracle, including video shapes and the no-argument default path. Suite: 56 passed, 1 xfailed. Grain iterator checkpointing (mid-epoch data resume) moved into Phase 2's trainer merge so save/load is rewritten once.

🤖 Generated with Claude Code

AshishKumar4 and others added 5 commits July 22, 2026 23:32
…, DDIM eta, RK4, DPM reuse

The sampler step domain was hardcoded to [0, 1000] and rescaled to the
schedule's domain internally, so the default start_step=max_timesteps was
wrong for every continuous schedule: the inference pipeline collapsed to a
single no-op step at sigma_min and returned the initial noise. Steps are
now in the schedule's own native domain and the defaults are correct by
construction. Linear spacing on a Karras schedule is already the EDM rho
spacing, so the broken quadratic/karras/exponential spacing modes
(log(0), duplicate int16 steps) are gone.

Also: DDPMSampler is now the algebraically exact ancestral posterior
(handles batches and arbitrary strides; the posterior-table variant
crashed on batch > 1 and ignored the stride), DDIM eta > 0 uses the paper
formula with the missing direction-term reduction, RK4 no longer jits
over a python callable, MultiStepDPM resets its history per trajectory,
Heun falls back to euler when sigma reaches 0, and every sampler reshapes
rates against the actual sample rank so 5D video works.

All samplers are now verified end to end against an analytic gaussian
oracle denoiser; the corresponding strict xfails are flipped to passing
tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, EDM weights, stable hashing

- training.py silently ignored --noise_schedule and always trained EDM
  while inference honored the flag, so a cosine-flagged run trained one
  convention and sampled another. Both sides now build from a single
  get_diffusion_preset, with a test pinning their agreement.
- UViT applied the hilbert permutation twice on the way in but inverted
  it once on the way out, training on a spatial scramble (SimpleDiT was
  correct; UDiT recomputed what patchify already returns).
- The EDM loss weight's epsilon guard halved the weight at sigma_min
  ((sigma*sd)^2 == 1e-6 exactly); rewritten as 1/sd^2 + 1/sigma^2 which
  needs no guard.
- FourierEmbedding frequencies came from jax's PRNG, which changed
  defaults in 0.5.0 and silently altered every model's time conditioning;
  now fixed numpy values, identical across jax versions.
- arguments_hash used python hash(), randomized per process, so identical
  runs never mapped to the same experiment directory; now sha256.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ling, full state restore, checkpoint deletion

- self.wandb was only assigned when wandb_config was set on process 0, so
  training without wandb (or on any non-zero process) crashed on the first
  logging tick; it is now always initialized and the getattr patches at the
  call sites are gone.
- pbar was unbound on non-zero processes (UnboundLocalError at step 0 of
  any multi-host run since d50040d).
- Checkpoint restore rebuilt the TrainState via create(), silently zeroing
  the adam moments and resetting the optax step counter (restarting lr
  warmup) on every resume. Restore now goes through orbax with the freshly
  initialized states as a typed template, so optimizer state, step counter
  and EMA params all round-trip. The unused param_transforms hook is gone.
- The NaN 'recovery' block reset params and optimizer to a possibly
  epochs-old best_state mid-epoch and patched the loss to 1.0 to keep the
  average looking sane. A diverged run now fails loudly after 5
  consecutive non-finite steps.
- save() deleted the checkpoint it just wrote even when the registry push
  was skipped; deletion now only happens after a successful push.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ntion path, dead flags, bf16 output heads

--dropout_rate was threaded into every DiT-family block and applied
nowhere (any sweep over it measured noise). nn.Dropout now sits on both
residual branches of DiTBlock/SSMDiTBlock/MMDiTBlock with a uniform
train flag across every model, and the trainers thread a dedicated
dropout rng. UViT's variant was never even forwarded, so that field is
gone.

--flash_attention is deleted along with EfficientAttention: the pallas
call passed no sm_scale (inflating logits 8x at head_dim 64 vs the
normal path), crashed on GPU/CPU where the import guard swallowed the
dependency but the call remained, and used a different param tree so
checkpoints weren't interchangeable. On every DiT-family model the flag
was accepted and silently ignored anyway. A proper fused-attention path
(cudnn/splash with identical params) comes with the parallelism work.

Also: dead norm_groups/activation fields removed from the DiT family
(training.py now scopes those args to the unet/uvit which read them),
S5Layer validates its features field, parse_config filters config keys
against the model's actual fields (old runs logged since-removed flags)
and resolves dotted function paths instead of silently dropping them,
the activation string is no longer mutated into a live function object
inside the logged config, DiT/MMDiT/hybrid output heads are fp32 (the
loss is fp32; bf16 heads quantized early-training residuals), the
attention-block FFN no longer silently runs fp32 inside bf16 models,
and jax.tree_map (removed in jax 0.6) is jax.tree.map.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The old example called generate_images(num_images=...) which never
matched the actual signature, and showed a custom sampler with a stale
take_next_step contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant