Skip to content

Sum GPTBigCode's shared key/value gradient across tensor-parallel ranks - #8928

Open
Achyuthan-S wants to merge 1 commit into
deepspeedai:masterfrom
Achyuthan-S:fix-gptbigcode-mqa-kv-grad
Open

Achyuthan-S wants to merge 1 commit into
deepspeedai:masterfrom
Achyuthan-S:fix-gptbigcode-mqa-kv-grad

Conversation

@Achyuthan-S

@Achyuthan-S Achyuthan-S commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #8927.

With AutoTP training, multi-query GPTBigCode's fused c_attn gives every rank its slice of the query rows plus the whole key/value block (_bigcode_type_transpose). Each rank attends with only its own query heads, so the gradient it computes for the shared key/value rows covers those heads alone. Nothing summed those partial gradients. Each rank therefore applied a different update to rows that must stay identical, and training departed from the unsharded model.

Change

fused_LinearLayer now registers a gradient hook on c_attn's weight and bias for the bigcodetype layout. The hook all-reduces (sums) the key/value rows across the tensor-parallel group.

  • Query rows stay per rank.
  • The input gradient is unchanged: the column-parallel backward already sums every rank's contribution to it.
  • The hook is registered only when the TP group has more than one rank and the parameter requires grad.

Test

TestGPTBigCodeMultiQueryTraining in tests/unit/v1/autotp/test_autotp_training.py (world sizes 2 and 4) trains a tiny multi-query GPTBigCode with AutoTP and with plain PyTorch on the same data. It checks three things:

  1. After the first backward, the key/value-row gradients of c_attn (weight and bias) on every rank equal the single-device ones.
  2. Over 4 SGD steps, the losses match single-device training (atol=1e-5).
  3. The key/value rows stay bit-identical across ranks.

On master, the first check fails: 471 of 512 key/value-weight gradient elements differ, by up to 1.4e-2.

The test uses plain SGD with gradient_clipping: 0.0 to keep the comparison clean. DeepSpeed clips gradients to norm 1.0 by default and the reference does not, and Adam turns floating-point noise on gradients that are zero in the unsharded model into visible updates.

Results

The script from #8927 with gradient_clipping: 0.0 and SGD (lr 0.5), 6 steps, compared with single-device training:

TP ZeRO Max loss gap Max cross-rank diff of the K/V rows
master 2 0, 1, 2 1.2e-4 9.1e-3
master 4 0, 1, 2 1.9e-4 5.9e-3
this PR 2 0, 1, 2 0 0
this PR 4 0, 1, 2 4.8e-7 0

Validation

macOS 26.5 (arm64, CPU), Python 3.9.6, torch 2.8.0, transformers 4.44.2, DS_ACCELERATOR=cpu LOCAL_SIZE=4.

  • The new test passes at world sizes 2 and 4, and fails on master.
  • test_autotp_training.py, test_autotp_custom_patterns.py, test_affine_shard_map.py and test_autotp_uc_checkpoint.py: 146 passed, 42 failed, 2 skipped.
    • master: 145 passed and the same 42 failures.
    • All 42 are KeyError: 'flags' from building the CPU Adam op on macOS arm64.
  • yapf 0.43.0 is clean. flake8 is clean on the changed lines; the 3 F824 warnings in layers.py are on master too and come from a newer pyflakes than the pinned 5.0.4.
  • Not run on GPU. The change is in which gradients are summed, not in the communication backend.

Not covered here

  • Gradient clipping. With clipping active (DeepSpeed's default is 1.0), the global gradient norm counts the replicated K/V rows once per TP rank, because the TP norm decides per parameter whether to count it, and c_attn is marked as sharded. The clipping coefficient is therefore slightly too small. On the repro model at TP 2 with clipping on, the loss gap against single-device training is 3.3e-5 with this PR and 5.3e-4 on master. Counting replicated rows once needs the norm code in runtime/utils.py, both ZeRO stages, bf16_optimizer.py and fp16/fused_optimizer.py to know about them, which I'd rather do as a follow-up.
  • multi_query=False GPTBigCode still fails in the forward pass under AutoTP (the "Related" note in [BUG] AutoTP training of multi-query GPTBigCode diverges from single-device training at TP > 1 #8927). That is a separate change.
  • The new test does not exercise ZeRO-3 or torch.compile.

With multi-query attention every rank holds the whole key/value block of the fused c_attn but attends with only its own query heads, so its gradient for those rows is partial. Nothing summed them, so the ranks applied different updates to rows that must stay identical and training departed from the unsharded model. All-reduce the gradient of those rows in c_attn's weight and bias across the tensor-parallel group.

Fixes deepspeedai#8927

Signed-off-by: Achyuthan Sivasankar <achyuthan.sivasankar@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 27, 2026 18:21
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-27T18:29:12.020739Z 0bdd7b7 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@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: 0bdd7b73ac

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


def sum_kv_rows(grad):
grad = grad.clone()
dist.all_reduce(grad.narrow(0, kv_start, grad.shape[0] - kv_start), group=group)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Count replicated K/V rows once during clipping

With the default positive gradient_clipping (1.0), this new all-reduce leaves the full summed K/V gradient in every TP copy. config_tp_params marks the entire fused weight and bias as model-parallel, so clip_grad_norm_ and the ZeRO 1/2/3 norm routines include each local tensor and then sum norms across the model-parallel group; consequently the logical K/V squared norm is counted tp_world_size times while the sharded query rows are counted once. Whenever clipping activates, the coefficient is therefore too small and AutoTP updates no longer match the unsharded model; the added test explicitly sets clipping to 0.0, so it cannot detect this default-path numerical regression. The replicated segment needs to be excluded on all but one TP rank (without excluding each rank's query segment) when computing the norm.

AGENTS.md reference: AGENTS.md:L37-L38

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, verified. With the default clipping at TP 2, the global norm now counts the replicated K/V rows on every rank: 1.5416 against the true 1.5382 at step 0 on the repro model, so when clipping activates the coefficient is slightly too small. With clipping on, the loss gap against single-device training is 3.3e-5 with this PR versus 5.3e-4 on master, so the default path is much closer but not exact. On master the norm wasn't exact for these rows either: it summed each rank's partial-gradient norm instead of taking the norm of their sum.

Counting rows that are replicated inside a sharded parameter once needs the TP grad-norm code to know about them. That check (is_model_parallel_parameter(p) or mp_rank == 0) is decided per parameter in runtime/utils.py, both ZeRO stages, bf16_optimizer.py and fp16/fused_optimizer.py, so I'd rather do it as a follow-up than widen this fix into the core optimizer paths. I've noted the limitation in the description.

@delock
delock self-requested a review October 28, 2026 01:18

This branch has not been deployed

No deployments
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.

[BUG] AutoTP training of multi-query GPTBigCode diverges from single-device training at TP > 1

2 participants