| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
There was a problem hiding this comment.
Here are some automated review suggestions for this pull request.
Reviewed commit: 04bf85696a
ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Sorry, something went wrong.
| for p in self.module.parameters(): | ||
| if hasattr(p, "ds_status") and p.ds_status == ZeroParamStatus.AVAILABLE and not p.ds_persist: | ||
| p.partition() |
There was a problem hiding this comment.
Don't free eager-gathered weights before backward
When a Dynamo-skipped frame executes eagerly and touches a ZeRO-3 parameter whose backward needs the weight (for example a Linear after an embedding, where grad-input must be computed), this loop immediately calls p.partition() after forward. In DeepCompile the ZeRO module backward hooks have been removed, and deepcompile_backward_prologue() only starts the compiled runtime, so there is no eager fallback to re-gather that saved weight before the eager autograd node runs; backward will see the released [0] parameter/storage or compute from invalid state. The fallback-gathered params need to stay available until their eager backward use has completed, or get a matching backward-time gather/release path.
Useful? React with 👍 / 👎.
Sorry, something went wrong.
| if _dc_z3_eager_fallback: | ||
| for p in self.module.parameters(): | ||
| if hasattr(p, "ds_status") and p.ds_status == ZeroParamStatus.AVAILABLE and not p.ds_persist: | ||
| p.partition() |
There was a problem hiding this comment.
Thanks for fixing this!
One question: I was wondering why we need to walk through all parameters and free those that are still gathered at this point? Does it mean the parameters gathered outside the compiled graphs are all alive till this point? If so, it can increase the peak GPU memory usage, which can hurt training efficiency in some cases.
Sorry, something went wrong.
|
Thanks @XAheli for digging into this! The root-cause analysis is correct, and keeping ZeROOrderedDict as an eager fallback guarded by torch.compiler.is_compiling() is the right direction. But I think we still need some rework. One issue is the post-forward release loop just moves the failure into backward (this confirms the earlier P1 review comment). With DeepCompile actually enabled and the parameters not persistent (stage3_param_persistence_threshold: 0), training still crashes in engine.backward(). RuntimeError: The size of tensor a (0) must match the size of tensor b (384) at non-singleton dimension 1 Autograd saves leaf parameters by reference and reads param.data at backward time; ZeRO-3's free_param swaps .data to an empty tensor. In eager ZeRO-3 the pre-backward hooks swap the data back, but DeepCompile removed those hooks, so partitioning every AVAILABLE non-persistent param right after forward frees exactly the weights the skipped frame's eager autograd nodes still need. Another issue is that this PR releases gathered parameters based on global ds_status state that other components also manage. For example, selective_gather keeps chosen parameters gathered via the C++-side registry without updating Python's ds_persist, so the sweep's not p.ds_persist guard cannot exclude them. This kind of interference is reproducible: with DeepCompile actually enabled, the regression test fails on this branch with KeyError: wait_allgather_ds_param__arg0_1_0 (master fails with the expected 'weight' must be 2-D), and the failure disappears once the sweep is replaced by tracked-set release. Your tests didn't catch these issues because test_deepcompile_skipped_frame.py never activates DeepCompile (ds_config_z3.json doesn't set "compile": {"deepcompile": true}). Also, all its parameters are below the default persistence threshold (100000 elements). So the release loop excludes them even when DeepCompile is on. The test passes on master without your fix. To make this concrete, I've opened a PR against your branch implementing the rework: XAheli#1. In summary it:
On @eternalNight's question: This still increases the peak memory, though the cost is now bounded to the fallback-gathered set. But I think it would avoid errors and keep correctness. Can you take a look at the rework, adjust it as needed, and merge it into your branch if it looks reasonable? (if you'd prefer to address these issues in your own way, that works just as well) Either way, once the backward-safe release Thanks again for working on this! |
Sorry, something went wrong.
|
@tohtana @eternalNight thanks a lot for the detailed review :) I'll take a deeper look and push the changes soon! |
Sorry, something went wrong.
|
Hi @XAheli, just following up here. I’m thinking of using the repair PR I opened against your branch to unblock this: If you’d like to make the changes yourself or prefer a different approach, please let me know. Otherwise I’ll proceed with this repair path so we can complete this PR. |
Sorry, something went wrong.
|
Hello @tohtana ! Apologies for the delay, I'll be pushing the changes by eod. |
Sorry, something went wrong.
…epspeedai#7942) Rework based on review: release gathered params after backward (not forward), track by ds_id instead of global sweep, skip dead register_external_parameter, activate DeepCompile in test config. Co-authored-by: Masahiro Tanaka <mtanaka@anyscale.com> Signed-off-by: ahpoddar <ahpoddar@redhat.com>
|
@tohtana Thanks again for the review and the repair PR :) I've reworked the fix to incorporate your approach. I confirmed that post-forward partition corrupts backward. param.data = shard between forward and backward breaks autograd because it saves leaf parameters by reference and reads param.data at backward time. Simple repro: p = torch.nn.Parameter(torch.randn(4, 4))
x = torch.randn(4, 4, requires_grad=True)
y = F.linear(x, p)
p.data = torch.zeros(2) # simulate partition
y.sum().backward() # RuntimeError: size mismatchThe fix now releases gathered params in a post-backward hook (deepcompile_backward_epilogue) instead of after forward. Only the params the fallback actually gathered are tracked and released. There's no global ds_status sweep. register_external_parameter is skipped in DeepCompile mode, since the module hooks that consume _external_params are removed by init_z3(). Tests now activates DeepCompile with the new config (ds_config_z3_deepcompile_no_persist.json) sets "deepcompile": true with stage3_param_persistence_threshold: 0. The model uses vocab_size=384, hidden=384 so all params exceed the 100k threshold. The test also asserts fallback_stats.total_gathered_params > 0 to confirm the fallback path was exercised. Uses @torch.compiler.disable instead of graph_break() in a loop for deterministic behavior. Validated all on 2× H200
One thing I want to flag is that Qwen2 MoE + DeepCompile + persistence_threshold=0 hits a KeyError: wait_allgather_ds_param__arg0_1_0 in list_schedule.py:get_last_uses(). I tested this with current approach (no _original_parameters restoration) and it's the same error, so it's not from this PR. On upstream without the fix it's masked by the earlier 'weight' must be 2-D crash at the embedding lookup. To me looks like a separate graph scheduling issue with MoE model structure when all persistence is disabled. Happy to dig into that if useful! |
Sorry, something went wrong.
There was a problem hiding this comment.
Hi @XAheli,
Thank you for the update! I also confirmed this fix works on my environment. This is a significant improvement of DeepCompile. I appreciate your contribution.
Sorry, something went wrong.
## Problem DeepCompile inserts ZeRO-3 parameter all-gather and release operations into compiled graphs. When Dynamo skips a frame because of a graph break, however, that frame executes eagerly and does not run those graph operations. The eager fallback introduced in deepspeedai#8059 handles this case by all-gathering a partitioned parameter when the skipped frame accesses it through `ZeROOrderedDict`. The fallback is enabled around `DeepSpeedEngine.forward()`. Dynamo guard evaluation occurs inside that outer forward context and also resolves parameters through `ZeROOrderedDict`, while `torch.compiler.is_compiling()` is false. The fallback could therefore mistake a guard lookup for actual eager execution and unnecessarily all-gather the parameter. Parameters gathered by the fallback are normally partitioned after backward, but that cleanup does not run when backward is skipped. A fallback-gathered parameter may also be passed to an explicit `GatheredParameters` context, which must keep the full tensor available until the context exits. ## Why it matters These cases require different behavior: - Dynamo guard evaluation should not trigger an all-gather. - A parameter gathered for an eagerly executed frame must remain available through backward and then be partitioned. - If backward does not run, a leftover full parameter must be partitioned before the next outermost forward. - A parameter covered by `GatheredParameters` must remain fully gathered until that context exits. Without distinguishing these cases, a full parameter can remain allocated into a later forward, or fallback cleanup can partition it while a `GatheredParameters` block is still using it. ## Solution This PR: - detects parameter access during Dynamo guard evaluation and skips the eager fallback all-gather; - partitions leftover nonpersistent full parameters before the next outermost forward when the normal post-backward cleanup did not run; - removes a parameter from fallback cleanup when it is passed to `GatheredParameters`, so that context alone partitions it on exit; - restores the `GatheredParameters` state even when context exit raises; and - rejects nested `GatheredParameters` contexts that overlap on the same parameter, while continuing to allow nesting over disjoint parameter sets. --------- Signed-off-by: Masahiro Tanaka <mtanaka@anyscale.com>
| Back | FazBrowse Home | New Git URL |
Fixes #7942
Root cause: When init_z3() initializes DeepCompile it removes all three parameter-gathering mechanisms (ZeROOrderedDict, module hooks, engine forward hooks) and relies entirely on compiled FX graph ops for allgather/release. but torch._dynamo may skip entire frames when it detects graph breaks in for/while loops. Skipped frames execute eagerly with no gathering mechanism, so parameters stay partitioned at shape [0].
Testing
Validated on 2× H200 with ZeRO3 + DeepCompile:
Test plan
cc @tohtana @eternalNight