Skip to content

Avoid per-step CUDA syncs in the training loop#1608

Open
dxqb wants to merge 1 commit into
Nerogar:masterfrom
dxqb:training-loop-sync-fixes
Open

Avoid per-step CUDA syncs in the training loop#1608
dxqb wants to merge 1 commit into
Nerogar:masterfrom
dxqb:training-loop-sync-fixes

Conversation

@dxqb

@dxqb dxqb commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two independent fixes, each removing a per-step cudaStreamSynchronize that stalled the training loop

Keep VAE batch-norm stats on the train device during training (Flux2 / Ideogram / Ernie): scale_latents reads vae.bn.running_mean/var every step. With latent caching the VAE is offloaded to CPU, so the per-step .to(cuda) was a blocking H2D that synchronized the whole CUDA stream and drained the command buffer once per step.

Read training loss asynchronously: accumulated_loss.item() every update-step drained the GPU at the step boundary. The loss is now copied into a pinned host buffer (AsyncTensorExchange, in modules/util/async_tensor_exchange.py) and read one update-step later - a host read that never touches the training stream, so the CPU can queue the next step's kernels. Loss/tqdm logging and the NaN check lag one update-step; the final step's loss is not logged.

Test plan

  • pre-commit run --all-files passes
  • Launched the affected UI or script and exercised the change
  • Tested with at least one real preset / config when relevant (note which: ____)

AI assistance

  • AI-assisted — I have read every line in this diff and can defend each change

Two independent fixes, each removing a per-step cudaStreamSynchronize
that stalled the training loop (found via multi-step profiling of
FLUX.2 W8A8).

Keep VAE batch-norm stats on the train device during offloaded
training (Flux2 / Ideogram / Ernie): scale_latents reads
vae.bn.running_mean/var every step. With latent caching the VAE is
offloaded to CPU, so the per-step .to(cuda) was a blocking H2D that
synchronized the whole CUDA stream and drained the command buffer
once per step. Pinning the tiny bn buffers to the train device in
setup_train_device (re-applied each setup, so it survives
offload/backup cycles) makes the access device-local. Safe
mixed-device state: the VAE forward never runs while offloaded - only
the bn buffers are read.

Read training loss asynchronously: accumulated_loss.item() every
update-step drained the GPU at the step boundary. The loss is now
copied into a pinned host buffer (AsyncTensorExchange, in
modules/util/async_tensor_exchange.py) and read one update-step
later - a host read that never touches the training stream, so the
CPU can queue the next step's kernels. Loss/tqdm logging and the NaN
check lag one update-step; the final step's loss is not logged.

🤖 Generated with Claude Code
@dxqb dxqb added the preview merged in the preview branch label Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview merged in the preview branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant