Skip to content

Fix DeepCompile ZeRO-3 gathered parameter ownership - #8157

Open
tohtana wants to merge 8 commits into
deepspeedai:masterfrom
tohtana:tohtana/deepcompile-z3-gather-ownership
Open

Fix DeepCompile ZeRO-3 gathered parameter ownership#8157
tohtana wants to merge 8 commits into
deepspeedai:masterfrom
tohtana:tohtana/deepcompile-z3-gather-ownership

Conversation

@tohtana

@tohtana tohtana commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

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 #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>
@tohtana tohtana changed the title Fix DeepCompile ZeRO-3 gathered parameter ownership [DC patch 1/4] Fix DeepCompile ZeRO-3 gathered parameter ownership Jul 20, 2026
Signed-off-by: Masahiro Tanaka <mtanaka@anyscale.com>
@tohtana
tohtana marked this pull request as ready for review July 22, 2026 21:31
@tohtana
tohtana requested review from loadams and tjruwase as code owners July 22, 2026 21:31

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4563ff10f8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread deepspeed/compile/z3_eager_fallback.py
Signed-off-by: Masahiro Tanaka <mtanaka@anyscale.com>
for param in self.params:
depth = getattr(param, _GATHERED_PARAM_CONTEXT_DEPTH_ATTR, 0)
setattr(param, _GATHERED_PARAM_CONTEXT_DEPTH_ATTR, depth + 1)
fallback_owner = getattr(param, "_deepcompile_z3_eager_fallback_owner", None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's import and use _FALLBACK_OWNER_ATTR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Fixed.

# official CPU builds 2.8.0+cpu, 2.9.1+cpu, 2.10.0+cpu, 2.11.0+cpu, 2.12.0+cpu, and 2.13.0+cpu.
frame = sys._getframe()
while frame is not None:
if frame.f_globals.get("__name__") == "torch._dynamo.guards":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is "torch._dynamo.guards" prone to change e.g. when torch version changes?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it can change. I could not find a public API for detecting guard evaluation specifically. torch.compiler.is_compiling() returns false in this path. However, it still works even we fail to find torch._dynamo.guards.

If the module path changes, this check returns false and the guard access is treated as a normal eager fallback access. That causes an unnecessary all-gather and may increase communication and memory usage, but it does not skip an all-gather required by actual eager execution.

@tohtana
tohtana force-pushed the tohtana/deepcompile-z3-gather-ownership branch from 85ec78f to 4db398b Compare July 28, 2026 19:53

@pengdurice pengdurice left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General question, if some one does this:

loss = engine(input)
with GatheredParameters([W]):
    log(W.norm())
 engine.backward(loss)

will that cause issue since the GatheredParameters context manager already partitions the parameters when it exits?

Signed-off-by: Masahiro Tanaka <mtanaka@anyscale.com>
@tohtana

tohtana commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

@pengdurice Great catch! Yes, in this pattern, the parameter can be partitioned too early.

I updated the PR branch to address it. Instead of transferring the ownership of the fallback and the gathered parameter, we now records claims of the ownership as a set (it is like a reference count). The parameter is partitioned only after the last claim is released.

Signed-off-by: Masahiro Tanaka <mtanaka@anyscale.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.

2 participants