Skip to content

[Performance] Compile the DreamerV3 learner losses and make the two-hot decode reduction-safe - #4290

Open
theap06 wants to merge 4 commits into
pytorch:mainfrom
theap06:dreamer-v3-learner-perf
Open

[Performance] Compile the DreamerV3 learner losses and make the two-hot decode reduction-safe#4290
theap06 wants to merge 4 commits into
pytorch:mainfrom
theap06:dreamer-v3-learner-perf

Conversation

@theap06

@theap06 theap06 commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add optimization.compile_learner to the DreamerV3 example: losses compiles the encoder, decoder, reward, continuation, actor and value networks together with the value and replay-value losses (numerically identical to eager); all additionally compiles the actor loss with its imagination rollout
  • enable compile_learner=losses in the --fast reproduction
  • make two_hot_decode exact for uniform probabilities under any reduction order by pairing mirrored bins through their probability difference
  • cast the RSSM scan carry back to the incoming dtypes so the compiled scan can be traced by an outer torch.compile under bf16 autocast
  • regression tests for both library changes

Why

After #4216 the fast path spends 95% of training wall time inside the captured learner update, and that update launches about 9,700 kernels with a 3.9 us mean. The compiled RSSM scan is already fused; the loss math around it and the imagination rollout still run as eager kernel streams. Compiling those regions removes most of the launches.

Measured on one NVIDIA GB10 (PyTorch 2.13.0, CUDA 13.0), DMC Walker preset, batch 16 x 64, BF16 compute, same protocol as benchmarks/ad_hoc/bench_dreamer_v3_learner.py:

Learner update median ms vs fast path
Fast path (compiled scan + CUDA graph) 27.8 baseline
+ compile_learner=losses 23.1 -17%
+ compile_learner=all 17.2 -38%
Upstream JAX DreamerV3, same GPU, same model 23.4

Unmodified train.py, 20,000 Walker steps on the same GPU: 33.5 updates/s for the fast path, 43.4 with losses (+30%), 52.0 with all (+55%).

Two bugs surfaced when compiling these regions:

  • two_hot_decode relied on a fixed summation order of mirrored products so that uniform probabilities cancel to exactly zero over the +-4.85e8 symexp support. Inductor's fused reduction reorders it and zero logits decoded to 0.154 instead of 0, which shifted the imagined rewards and lambda targets at initialization. The new form (p_upper - p_lower) * b_upper + p_lower * (b_upper + b_lower) is exact under any order and general for any support.
  • Under bf16 autocast the RSSM scan body returned bf16 state and belief while the initial carry was fp32. Dynamo's higher-order scan rejects that mismatch whenever an outer torch.compile inlines the compiled scan, which is what compiling the model loss does.

The actor network object is shared between the actor loss and the real-world policy, so with compile_learner set the collector and evaluation policies run the compiled actor MLP for their own batch shapes (dynamic=False, one graph per shape); the policy's encoder copy stays eager. This is the configuration the numbers above were measured with.

all moves the imagination's random draws inside the compiled region, like the scan backend, so it is not on by default; its actor loss after 60 updates differs from eager by float32 rounding at the tail bins (about -0.05 vs -0.0002). A full Walker learning curve with all is still owed before it becomes part of --fast.

…ot decode reduction-safe

Add optimization.compile_learner to the DreamerV3 example. "losses" compiles
the encoder, decoder, reward, continuation, actor and value networks together
with the value and replay-value losses, which is numerically identical to
eager; "all" also compiles the actor loss with its imagination rollout. The
--fast reproduction now enables "losses". On one GB10 the captured learner
update goes from 27.8 ms to 23.1 ms ("losses") and 17.2 ms ("all"), and the
training script from 33.5 to 43.4 and 52.0 updates per second.

Two library changes were needed for the compiled regions to be correct:

- two_hot_decode pairs mirrored bins through their probability difference
  before summing. The previous mirrored-pair sum was only exact for uniform
  probabilities in a fixed reduction order, and inductor's fused reduction
  broke that cancellation on GPU: zero logits decoded to a reward of 0.154
  instead of 0 at initialization, which shifted the first imagined targets.
- RSSMRolloutV3._scan casts the carry returned by each step to the incoming
  carry dtypes. Under bf16 autocast the body returned bf16 state and belief
  while the initial carry was fp32, and the higher-order scan rejected the
  mismatch whenever an outer torch.compile inlined the compiled scan.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MbCsKrmHN55DtBCgJc2iHb
@pytorch-bot

pytorch-bot Bot commented Sep 7, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/rl/4290

Note: Links to docs will display an error until the docs builds have been completed.

✅ No Failures

As of commit 09d4248 with merge base bedcc83 (image):
💚 Looks good so far! There are no failures yet. 💚

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 7, 2026
theap06 and others added 2 commits September 7, 2026 16:03
…or policy eager

The reproduction-modes test pins the exact overrides that --fast forwards to
benchmark.py; add the new compile_learner=losses entry so the tests-cpu jobs
pass again. Compile the learner after the real-world actor is built so the
behavior policy keeps the eager networks it was measured with, move the scan
carry note into the _scan docstring, and widen the decode tolerance for the
GPU runners.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MbCsKrmHN55DtBCgJc2iHb
The MPS inductor backend cannot compile the random draws that the compiled
actor loss traces; `losses` works there, `all` does not.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MbCsKrmHN55DtBCgJc2iHb
@theap06
theap06 requested a review from vmoens September 8, 2026 03:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Integrations/torch_geometric Integrations Modules Objectives Performance Performance issue or suggestion for improvement sota-implementations/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant