Repository navigation
Conversation
|
This PR has been automatically converted to draft because all PRs must start as drafts. When you are ready for review, click Ready for Review to begin the review process. This will:
See the contribution guide for more details. |
cda8205 to
66317f5
Compare
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
55c9152 to
fdc503b
Compare
…1F1B Signed-off-by: Xiaoda Zhang <xiaodaz@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fdc503b to
81ad176
Compare
svcnvidia-nemo-ci
left a comment
There was a problem hiding this comment.
The regression test silently skips optimizer updates on both replay-only iterations, weakening its training-equivalence check. Its reset helper also violates the repository's explicit string-quote convention.
I inspected both changed files and the affected stream, capture and optimizer paths. I didn't execute GPU tests.
Note: formal approval is not available for this request, so this review is posted as a comment only.
Review generated by codex. This was a full review. Request a full review with /review reset=true.
| ) | ||
| losses = [] | ||
| for microbatches in steps: | ||
| optimizer.zero_grad() |
There was a problem hiding this comment.
[P2] Preserve gradient bindings across graph replays
On iterations three and four, optimizer.zero_grad() clears the optimizer-facing .grad attributes to None. Graph replay runs GPU work without rerunning the Python schedule, so it doesn't repeat _install_sharded_grads. SGD therefore skips every parameter even though captured kernels still write the persistent gradient buffers. Both schedule variants freeze identically, allowing their final parameter comparisons to pass without checking continued training. FullyShardedOptimizer already handles this exact contract, but this test uses raw SGD.
Replace this line with:
| optimizer.zero_grad() | |
| optimizer.zero_grad(set_to_none=False) |
This preserves the gradient views installed during warmup and zeros their persistent storage before capture and each replay. Retain the eager warmup, both schedule variants, successful-capture assertion, and bitwise loss/parameter comparisons.
Proposed acceptance checks: assert optimizer-facing gradients are non-None immediately before every optimizer.step(), including both replay-only iterations; snapshot local parameter shards and verify at least one nonempty shard changes on each replay-only update. Keep the existing rtol=0, atol=0 comparisons. Removing the production stream fix should still make the overlap variant fail during capture. These checks are proposed; I haven't executed them.
|
|
||
| def _reset_full_cuda_graph_state(): | ||
| """Drop the globally stored full-iteration graph so the next run captures its own.""" | ||
| FullCudaGraphWrapper.curr_iteration = {'training': 0, 'validation': 0} |
There was a problem hiding this comment.
[P3] Use double quotes in the new reset helper
All four new reset assignments use single-quoted keys, contrary to the protected style guide's explicit double-quote requirement. This is a review-only convention: Black preserves string quotes, so formatting won't correct it.
Replace the four assignments in this helper with:
FullCudaGraphWrapper.curr_iteration = {"training": 0, "validation": 0}
FullCudaGraphWrapper.cuda_graph = {"training": None, "validation": None}
FullCudaGraphWrapper.result = {"training": None, "validation": None}
StaticBufferLoader.static_buffers = {"training": [], "validation": []}Preserve both stage keys, their values and the assignment order. Acceptance is static inspection: all eight key literals use double quotes, with reset behavior unchanged.
Signed-off-by: Ajay Balasa <abalasa@nvidia.com>
|
/ok to test 3505954 |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Xiaoda Zhang <xiaodaz@nvidia.com>
Changed |
Signed-off-by: Ajay Balasa <abalasa@nvidia.com>
|
/ok to test 46c5375 |
| # allocates on a stream outside the capture, which is illegal during capture. | ||
| context = module.context | ||
| context.inner_reduction_stream.wait_stream(context.current_stream()) | ||
| module._reduce_gradient_groups() |
There was a problem hiding this comment.
My understanding is that this fix makes the RS stream wait on the compute stream via inner_reduction_stream.wait_stream before _reduce_gradient_groups allocates the gradient buffer, allowing CUDA graph capture to include inner_reduction_stream. Is that correct? 🤔
If so, I’d suggest moving this wait to a dedicated point near the start of the operation, so that it enables CUDA Graph capture without being issued too frequently. For example, could we place it immediately after context.ensure_finalized() here?
There was a problem hiding this comment.
Done. Thanks for this suggestion. I moved the wait into microbatch() right after context.ensure_finalized(). It now runs once per microbatch scope
… scope Signed-off-by: Xiaoda Zhang <xiaodaz@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| context.ensure_finalized() | ||
| # Join the reduction stream to any active CUDA-graph capture before gradient reduction. | ||
| # Needed by schedules that bypass FsdpModule.pre_backward, such as combined 1F1B. | ||
| context.inner_reduction_stream.wait_stream(context.current_stream()) |
There was a problem hiding this comment.
Should we fix the 1f1b hooks instead?
- microbatch (being renamed to finalize_gradients in [MFSDP] Rename the microbatch scope to finalize_gradients #9000) having to deal with pre-backward violates SRP.
- The non-1f1b path shouldn't have to doubly wait.
- The scope is optional technically speaking.
In addition, we could wrap
context.pre_forward, symmetric to context.post_backward, so the code stays SLAP (single level of abstraction).
In addition, could this disable any overlapping? Currently, the wait is done after forward; with this PR, before.
There was a problem hiding this comment.
Reverted the microbatch() change. The root block of FsdpModule.pre_backward now goes through FsdpContext.pre_backward(), and the combined-1F1B backward pre-hook calls it for the root. The default path waits once again, and the fork is back at backward start, so overlap is unchanged
Move the root's reduction-stream fork into FsdpContext.pre_backward() and call it from the combined-1F1B backward pre-hook instead of microbatch(). Signed-off-by: Xiaoda Zhang <xiaodaz@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| if owner.is_root(): | ||
| owner.context.pre_backward() |
There was a problem hiding this comment.
Optional: could we get rid of the is_root check?
is_root is needed in default mode because FsdpModule.pre_backward can operate on a nested FsdpModule.
For 1f1b, I expect the scheduler knows exactly the schedule and the model structure so it only needs to FsdpContext.pre_backward once at the right place.
There was a problem hiding this comment.
Could we move the test to tests/unit_tests/a2a_overlap?
This may be a philosophical distinction, but I view combined_1f1b + MFSDP support as adding MFSDP support to combined_1f1b, rather than adding combined_1f1b support to MFSDP. In that model, combined_1f1b is a user of MFSDP with register_hooks=False, so its specific tests should live alongside the other 1f1b tests.
What does this PR do?
With
overlap_moe_expert_parallel_comm=True, MFSDP v2's combined 1F1B scheduler bypassesFsdpModule.pre_backward. That hook is what makes the gradient-reduction stream wait on the current stream. Without that dependency, capturing a full-iteration CUDA graph fails: the first gradient reduction runs on a stream outside the capture and raisescudaErrorStreamCaptureIsolation._module_post_backward_hooknow makesinner_reduction_streamwait on the current stream before reducing gradients.Test
test_mcore_moe.py::test_ep_overlap_full_iteration_cuda_graph_matches_default_schedulebuilds a 2-layer MoE GPTModel (EP=2, drop-padding alltoall) wrapped in FullyShardedDataParallel v2. It captures a full-iteration CUDA graph with and withoutoverlap_moe_expert_parallel_comm, then requires bitwise-equal losses and parameters.