Repository navigation
Check where column-split pieces write in a shard - #8922
Conversation
validate() checked destination bounds and overlap only for pieces whose destination is one dense interval, so pieces of a column split could write the same shard columns and pass every check. Check those pieces as boxes in the shard's grid from their metadata, and walk every shard address in validate_coverage() so the full check covers any stride. Signed-off-by: Achyuthan Sivasankar <achyuthan.sivasankar@gmail.com>
There was a problem hiding this comment.
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.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f5af9f49c
ℹ️ 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".
| if _dest_interval(piece) is None: | ||
| raise ValueError(f'Rank {rank} piece {piece} writes outside the shard of shape ' | ||
| f'{tuple(shard_shape)}.') |
There was a problem hiding this comment.
Keep wrapped strided views valid
When a legitimate strided destination starts near the end of a shard row but all of its storage addresses remain in bounds, this branch rejects it. For example, in a (3, 3) shard, an AffinePiece with shape (2, 2), dest_offset=2, and dest_strides=(3, 1) visits flat addresses 2, 3, 5, 6, but _dest_box computes a column stop of 4, _dest_interval returns None, and validate() now raises. Because destinations are arbitrary torch.as_strided views over _flat_buffer, serialized or custom maps using such wrapped views can no longer rebuild; bounds should be checked against the actual flat address range while box coordinates are used only when the view fits the shard grid.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in the latest commit. Bounds for a strided piece now come from its actual lowest and highest shard address, and box coordinates are used only when the view fits the shard grid. A view that wraps a row is left to the address walk in validate_coverage(). Your (3, 3) example is now test_strided_destination_that_wraps_a_shard_row_round_trips.
Bound a strided piece by its lowest and highest shard address, and use box coordinates only when the view fits the shard grid. A view that wraps a row stays valid and is checked by the address walk in validate_coverage(). Signed-off-by: Achyuthan Sivasankar <achyuthan.sivasankar@gmail.com>
| return piece.dest_offset, piece.dest_offset + piece.numel | ||
|
|
||
|
|
||
| def _dest_box(piece, shard_shape): |
There was a problem hiding this comment.
Is these similar to?
DeepSpeed/deepspeed/checkpoint/affine_transfer.py
Lines 93 to 120 in 7501610
Maybe re-use/abstract as common utils.
Also, I think there's some follow-up to optimize (I list in #8885 (comment)). cc @0z5a btw, if you both no bandwidth on it, I'd like to takeover. Thanks.
There was a problem hiding this comment.
I prefer to keep it not block merging. Refactor it later as seperate follow-up.
There was a problem hiding this comment.
Thanks @jinyouzhi! Yes, it's the same idea: _dest_box is a box in a shard's coordinates, and LogicalBox is one in the logical parameter's. +1 to the shared affine_geometry.py you proposed on Slack: affine_transfer.py already imports from affine.py, so a module both import is the right home. Agreed it shouldn't block this PR.
The planner optimizations you listed in #8885 sit in the planner from #8623, so I'm happy for you and @0z5a to take them. One data point for that work, since it overlaps your grid fast path: for CodeGen's fused QKV the planner emits 44 / 88 / 96 copies for TP 2→4 / 4→8 / 2→8 on current master. The counts are flat across hidden sizes, so they're bounded and the plans are exact, but folding in N dimensions as in spec §8.2 gives 40 for 4→8. Same-shape runs at constant offsets aren't stacked into an outer dimension yet.
Happy to share the script.
ParamAffineMap.validate()checks where a piece writes in its shard only when the piece's destination is one dense interval. That check came from #8624. A column split has no such interval: each piece writes a few columns of every shard row. So for fused QKV split along columns, Yuan's o_proj, or anysub_param_map/segmented_map/block_gather_mapwithpartition_dim=1, two pieces can write the same shard columns and pass every check.Reproduction on
master(fused QKV(4, 12)split along columns at TP 2):validate()andvalidate_coverage()both pass.rebuild(extract(F))differs fromFin 8 of 48 elements.extract, so they hold uninitialised memory.Changes
validate()now also checks strided pieces, still from metadata only, without walking elements.validate_coverage(), the full O(n) check, now walks every shard address and requires each one to be written exactly once. Together with the element-count check, that makes "every shard address is written by exactly one element" a complete check, for any stride, including strided pieces that are not boxes.Tests
tests/unit/checkpoint/test_affine_shard_map.py:mastertest_strided_destination_overlap_is_refusedtest_strided_destination_outside_the_shard_is_refusedtest_full_check_refuses_a_destination_collision_of_any_stridevalidate_coverage()refuses a collision that no box can describetest_strided_destination_validation_does_not_enumerate_elementsvalidate()stays metadata-only for a10^9 × 8column splittest_strided_destination_that_wraps_a_shard_row_round_tripsValidation
macOS 26.5 (arm64, CPU), Python 3.9.6, torch 2.8.0,
DS_ACCELERATOR=cpu LOCAL_SIZE=4.test_affine_shard_map.py: 76 passed.test_affine_shard_map.py,test_affine_transfer_plan.py,test_autoep_affine.py,test_autoep_affine_universal.pyandtest_autotp_uc_checkpoint.py: 276 passed, 5 failed. The failures are identical onmaster. The 5 tests build the CPU Adam op, which fails on macOS arm64 (cpuinforeports noflags). They were not run here.ds_to_universal, then a universal load at another TP degree. Runs: TP 2→4 under ZeRO 1 and 2, including an uneven split at TP 4; TP 4→2 under ZeRO 1. Parameters,exp_avgandexp_avg_sqare restored bit for bit, and the resumed losses match the uninterrupted run to within 1.2e-7.yapf0.43.0 andflake8are clean on both files.Refs #8252, #8624.
@delock