-
Notifications
You must be signed in to change notification settings - Fork 104
Introduce reduce-scatter algo #866
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Binyang Li (Binyang2014)
wants to merge
17
commits into
main
Choose a base branch
from
binyli/allgather
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
d690896
update for nvml
421f02c
allgather support
7a5a110
Merge branch 'main' into binyli/allgather
Binyang2014 6df3357
WIP
ff803bd
Format multinode allgather for Black
Copilot 8e25731
address the comments
a6a78ae
WIP
Binyang2014 c5df28c
Proxy FIFO latency improvement
chhwang bdf9fd8
update
Binyang2014 5968b5e
WIP
Binyang2014 2a843fa
Revert enhanced proxy FIFO changes
Binyang2014 cefe801
update review comment
Binyang2014 7527156
WIP
Binyang2014 0abd5b5
Merge branch 'main' into binyli/allgather
Binyang2014 9ff8ad3
add reset API for executor
Binyang2014 1659db2
Merge branch 'main' into binyli/allgather
Binyang2014 7d60070
Merge branch 'main' into binyli/allgather
Binyang2014 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
194 changes: 194 additions & 0 deletions
194
python/mscclpp/default_algos/reducescatter_multi_nodes.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| # Copyright (c) Microsoft Corporation. | ||
| # Licensed under the MIT License. | ||
|
|
||
| """Hierarchical multi-node ReduceScatter for the low-latency packet protocol.""" | ||
|
|
||
| from mscclpp.language.channel import MemoryChannel, PortChannel | ||
| from mscclpp.language.collectives import ReduceScatter | ||
| from mscclpp.language.program import CollectiveProgram | ||
| from mscclpp.language.rank import Buffer, Rank | ||
| from mscclpp.language.thread_block_group import ThreadBlockGroup | ||
| from mscclpp.language.utils import AlgoSpec | ||
|
|
||
|
|
||
| def reducescatter_multi_nodes( | ||
| spec: AlgoSpec, | ||
| thread_block_group_size: int = 1, | ||
| ) -> CollectiveProgram: | ||
| """Build a hierarchical ReduceScatter across nodes and local GPUs.""" | ||
| if not isinstance(spec.collective, ReduceScatter): | ||
| raise ValueError("reducescatter_multi_nodes requires a ReduceScatter collective") | ||
| if spec.protocol != "LL": | ||
| raise ValueError("reducescatter_multi_nodes requires protocol='LL'") | ||
| if spec.world_size % spec.nranks_per_node != 0: | ||
| raise ValueError("world_size must be divisible by nranks_per_node") | ||
| if spec.collective.chunk_factor != 1: | ||
| raise ValueError("reducescatter_multi_nodes requires chunk_factor=1") | ||
| if not spec.in_place or not spec.collective.inplace: | ||
| raise ValueError("reducescatter_multi_nodes requires in-place buffers") | ||
| if thread_block_group_size <= 0: | ||
| raise ValueError("thread_block_group_size must be positive") | ||
|
|
||
| num_nodes = spec.world_size // spec.nranks_per_node | ||
| gpus_per_node = spec.nranks_per_node | ||
| total_gpus = spec.world_size | ||
|
|
||
| with CollectiveProgram.from_spec(spec) as prog: | ||
| local_receive_slots = (gpus_per_node - 1) * num_nodes | ||
| local_send_offset = local_receive_slots | ||
| remote_receive_offset = local_send_offset + num_nodes | ||
| local_owner_offset = remote_receive_offset + num_nodes - 1 | ||
| scratch_slots = local_owner_offset + 1 | ||
| scratch_buffers = [Buffer(rank, scratch_slots) for rank in range(total_gpus)] | ||
| logical_thread_blocks = (gpus_per_node - 1) + num_nodes | ||
| thread_block_groups = [ | ||
| ThreadBlockGroup( | ||
| tb_list=[ | ||
| logical_block * thread_block_group_size + group_offset | ||
| for group_offset in range(thread_block_group_size) | ||
| ] | ||
| ) | ||
| for logical_block in range(logical_thread_blocks) | ||
| ] | ||
|
|
||
| intra_node_channels: dict[tuple[int, int], MemoryChannel] = {} | ||
| for node_id in range(num_nodes): | ||
| for src_local_rank in range(gpus_per_node): | ||
| for dst_local_rank in range(gpus_per_node): | ||
| if src_local_rank == dst_local_rank: | ||
| continue | ||
| src_rank = src_local_rank + node_id * gpus_per_node | ||
| dst_rank = dst_local_rank + node_id * gpus_per_node | ||
| intra_node_channels[(dst_rank, src_rank)] = MemoryChannel( | ||
| dst_rank, | ||
| src_rank, | ||
| ) | ||
|
|
||
| inter_node_channels: dict[tuple[int, int], PortChannel] = {} | ||
| for reducer_local_rank in range(gpus_per_node): | ||
| for chunk_offset in range(num_nodes): | ||
| owner_rank = reducer_local_rank * num_nodes + chunk_offset | ||
| owner_node_id = owner_rank // gpus_per_node | ||
| for src_node_id in range(num_nodes): | ||
| if src_node_id == owner_node_id: | ||
| continue | ||
| src_rank = reducer_local_rank + src_node_id * gpus_per_node | ||
| inter_node_channels[(owner_rank, src_rank)] = PortChannel( | ||
| owner_rank, | ||
| src_rank, | ||
| ) | ||
|
|
||
| # Each local GPU reduces one contiguous M / gpus_per_node group. Exchange | ||
| # those groups with one packet operation per local peer. | ||
| for node_id in range(num_nodes): | ||
| for src_local_rank in range(gpus_per_node): | ||
| src_rank = src_local_rank + node_id * gpus_per_node | ||
| src_input = Rank(src_rank).get_input_buffer() | ||
| for dst_local_rank in range(gpus_per_node): | ||
| if src_local_rank == dst_local_rank: | ||
| continue | ||
| dst_rank = dst_local_rank + node_id * gpus_per_node | ||
| local_peer_slot = src_local_rank if src_local_rank < dst_local_rank else src_local_rank - 1 | ||
| dst_peer_slot = dst_local_rank - 1 if src_local_rank < dst_local_rank else dst_local_rank | ||
| chunk_index = dst_local_rank * num_nodes | ||
| scratch_slot = local_peer_slot * num_nodes | ||
| intra_node_channels[(dst_rank, src_rank)].put_packets( | ||
| scratch_buffers[dst_rank][scratch_slot : scratch_slot + num_nodes], | ||
| src_input[chunk_index : chunk_index + num_nodes], | ||
| tb_group=thread_block_groups[dst_peer_slot], | ||
| ) | ||
|
|
||
| # Reduce each contiguous group locally. Remote nodes send their partials | ||
| # directly to the standard owner while the owner node transfers its one | ||
| # local partial over NVLink. | ||
| local_reduce_offset = gpus_per_node - 1 | ||
| for src_node_id in range(num_nodes): | ||
| for reducer_local_rank in range(gpus_per_node): | ||
| src_rank = reducer_local_rank + src_node_id * gpus_per_node | ||
| rank = Rank(src_rank) | ||
| input_buffer = rank.get_input_buffer() | ||
|
|
||
| for chunk_offset in range(num_nodes): | ||
| thread_block_group = thread_block_groups[local_reduce_offset + chunk_offset] | ||
| chunk_index = reducer_local_rank * num_nodes + chunk_offset | ||
| owner_rank = chunk_index | ||
| owner_node_id = owner_rank // gpus_per_node | ||
| local_packets = [] | ||
| for peer_local_rank in range(gpus_per_node): | ||
| if peer_local_rank == reducer_local_rank: | ||
| continue | ||
| local_peer_slot = ( | ||
| peer_local_rank if peer_local_rank < reducer_local_rank else peer_local_rank - 1 | ||
| ) | ||
| scratch_slot = local_peer_slot * num_nodes + chunk_offset | ||
| local_packets.append(scratch_buffers[src_rank][scratch_slot : scratch_slot + 1]) | ||
|
|
||
| local_reduced_chunk = input_buffer[chunk_index : chunk_index + 1] | ||
| if local_packets: | ||
| rank.reduce( | ||
| local_reduced_chunk, | ||
| local_packets, | ||
| tb_group=thread_block_group, | ||
| packet=True, | ||
| ) | ||
|
|
||
| if src_node_id == owner_node_id: | ||
| if src_rank != owner_rank: | ||
| intra_node_channels[(owner_rank, src_rank)].put_packets( | ||
| scratch_buffers[owner_rank][local_owner_offset : local_owner_offset + 1], | ||
| local_reduced_chunk, | ||
| tb_group=thread_block_group, | ||
| ) | ||
| continue | ||
|
|
||
| local_packet_slot = local_send_offset + chunk_offset | ||
| rank.copy_packets( | ||
| scratch_buffers[src_rank][local_packet_slot : local_packet_slot + 1], | ||
| local_reduced_chunk, | ||
| tb_group=thread_block_group, | ||
| ) | ||
| remote_node_slot = src_node_id if src_node_id < owner_node_id else src_node_id - 1 | ||
| inter_node_channels[(owner_rank, src_rank)].read_put_packets( | ||
| scratch_buffers[owner_rank][ | ||
| remote_receive_offset + remote_node_slot : remote_receive_offset + remote_node_slot + 1 | ||
| ], | ||
| scratch_buffers[src_rank][local_packet_slot : local_packet_slot + 1], | ||
| tb_group=thread_block_group, | ||
| ) | ||
|
|
||
| if num_nodes == 1: | ||
| return prog | ||
|
|
||
| # Every rank receives one standard shard. The owner-node handoff and | ||
| # direct IB transfers from the other nodes can progress concurrently. | ||
| for owner_rank in range(total_gpus): | ||
| owner = Rank(owner_rank) | ||
| owner_input = owner.get_input_buffer() | ||
| owner_node_id = owner_rank // gpus_per_node | ||
| reducer_local_rank = owner_rank // num_nodes | ||
| chunk_offset = owner_rank % num_nodes | ||
| reducer_rank = reducer_local_rank + owner_node_id * gpus_per_node | ||
| thread_block_group = thread_block_groups[local_reduce_offset + chunk_offset] | ||
| owner_chunk = owner_input[owner_rank : owner_rank + 1] | ||
|
|
||
| if reducer_rank != owner_rank: | ||
| owner.unpack_packets( | ||
| owner_chunk, | ||
| scratch_buffers[owner_rank][local_owner_offset : local_owner_offset + 1], | ||
| tb_group=thread_block_group, | ||
| ) | ||
|
|
||
| remote_packets = [ | ||
| scratch_buffers[owner_rank][ | ||
| remote_receive_offset + remote_node_slot : remote_receive_offset + remote_node_slot + 1 | ||
| ] | ||
| for remote_node_slot in range(num_nodes - 1) | ||
| ] | ||
| owner.reduce( | ||
| owner_chunk, | ||
| remote_packets, | ||
| tb_group=thread_block_group, | ||
| packet=True, | ||
| ) | ||
|
|
||
| return prog |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.