DeepSpeed
b39bf260 - Keep Yuan's o_proj bias whole on every rank and describe it as replicated (#8857)

Commit
11 days ago
Keep Yuan's o_proj bias whole on every rank and describe it as replicated (#8857) Partitioning Yuan's output projection crashes on a bias. The obvious fix for that would make it silently wrong, so this keeps the bias whole on every rank — and once it is, the bias is an ordinary replicated parameter, which removes the last parameter universal checkpoint conversion refused unconditionally. ### The crash `shard_value_with_share_qk` handles both Yuan projections. On the output-projection path it computes the divided bias into `bias` but returns `sharded_bias`, which that path never fills: ```python else: bias = bias / float(world_size) return torch.nn.Parameter(sharded_weight), torch.nn.Parameter(sharded_bias) # sharded_bias is still [] ``` So any Yuan configuration with a bias on `o_proj` fails at TP ≥ 2 during partitioning, before training starts — `AttributeError: 'list' object has no attribute 'detach'` on torch 2.8.0. It has been there since `8ea995ee1f` (June 2024); no test partitioned that projection with a bias. ### Why the obvious fix is wrong Assigning the divided bias to `sharded_bias` makes the crash go away and the numbers wrong. `Yuan_LinearAllreduce` inherits `LinearAllreduce.forward`, which all-reduces the partial products **first** and adds the bias **once afterwards** — and `LinearAllreduce.gather_params` already says so: the bias is replicated across ranks. A pre-divided bias would come out of the layer as `bias / world_size`. Checked against the unsharded layer at 8 heads TP 2, 8 heads TP 4 and 16 heads TP 4, with the real head selection: | bias treatment | matches the unsharded output | |---|---| | kept whole on every rank (this PR) | exact | | divided by the world size | wrong | | current code | raises | ### What the description becomes With the bias kept whole, `Yuan_LinearAllreduce` publishes it exactly as base `LinearAllreduce` does: `replicated=True`, one piece per rank held by every rank, scale 1. Only the weight follows the shared-QK head selection. That corrects two things I said earlier, in #8706 and in the comment this PR replaces: that this path returned an empty bias — it raises — and that the bias needed a scaled piece. That reasoning came from the divide on the broken line. Once the bias is right, no scale is involved. This was the last unconditional `unsupported_reason` in `layers.py`. The refusals that remain are all conditional on a configuration that cannot be described: no kv head count, a head count the shared-QK pairing cannot divide without two ranks sharing a head, and codegen configurations its own partition asserts against. ### The tests discriminate | corruption | result | |---|---| | original line restored | partition test fails on all 3 configurations | | bias divided by the world size | partition test fails on all 3 configurations | | bias published as unsupported again | description test and the Yuan collection test fail | | original line restored, through the real layer | distributed test raises the original `AttributeError` at TP 2 and 4 | | bias divided by the world size, through the real layer | distributed test fails 33 of 36 output elements | `TestYuanOutputProjectionBias` is the integration test: it builds the injected `Yuan_LinearAllreduce` on a real process group, feeds each rank the activations for its own value heads, and checks the output and both gradients against the unsharded `nn.Linear` at TP 2 and 4 — so it exercises the wrapper's choice of the output-projection branch and the real all-reduce-then-bias order, not a reconstruction of it. `test_yuan_output_projection_keeps_its_bias_whole` is the cheaper unit check over more head counts, using the unsharded layer as its reference. `test_yuan_output_projection_bias_is_described_as_replicated` checks the published map, and that with head counts set the layer has nothing left in `AUTOTP_UNSUPPORTED_PARAMETER_PATTERNS`. `test_collect_records_yuan_layout_as_unsupported` used to require both Yuan biases to be refused; it now separates them — the value projection's bias follows its weight's head selection and is still refused without head counts, while the output projection's is not. ### Validation 250 passed across the affine suite, the resume matrix, the coverage test, the AutoEP affine suite, the transfer-plan suite, `tests/unit/runtime/tensor_parallel/` and `TestYuanOutputProjectionBias`, on CPU/gloo (`DS_ACCELERATOR=cpu LOCAL_SIZE=4`; Apple M4 Pro, macOS 26.5, Python 3.9.6, torch 2.8.0). The distributed test runs as real 2- and 4-process groups — confirmed by making it fail at each world size in turn. Not run on GPU. yapf 0.43.0 and flake8 are clean on the changed files. ### Scope The conv layer at `layers.py` that also divides its bias by the world size is untouched. Whether that is correct depends on where that layer adds the bias relative to its reduction — the case raised in #8252 — and I have not verified it. Related: #8252, #8230, #8575, #8706. cc @delock --------- Signed-off-by: Achyuthan Sivasankar <achyuthan.sivasankar@gmail.com> Co-authored-by: Ma, Guokai <guokai.ma@gmail.com>
Author
Parents
Loading