-
Notifications
You must be signed in to change notification settings - Fork 4.9k
DeepSpeed Comm. Backend v1 #1985
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
Merged
Merged
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
867a853
Staging comms v1 (#301)
Quentin-Anthony c93fcfe
Delete stage1.py
awan-10 7f8ca01
Delete distributed.py
awan-10 977ee32
revert deepspeed/__init__.py logging calls
Quentin-Anthony 68eb9f4
Delete test.py
Quentin-Anthony 54796bb
Update comments and move custom comm ops to internal functions
Quentin-Anthony c06c72d
Merge branch 'staging-comms-next' of https://github.com/microsoft/Dee…
Quentin-Anthony f070a0c
Remove unnecessary print and update backend description
Quentin-Anthony 9976681
Relax assertion to allow Megatron-DeepSpeed MoE to use ZeRO 1
Quentin-Anthony 09063a3
Simplify ZeRO stage 1 check for previous commit
Quentin-Anthony 656b415
Remove misleading world_size prints
Quentin-Anthony 859c5f8
Remove custom backend references
Quentin-Anthony 4d710b4
Merge branch 'master' into staging-comms-next
jeffra 108064e
Delete nccl.cpp
Quentin-Anthony ce5001f
Delete alltoall.py
Quentin-Anthony 73afaa8
Delete runall.py
Quentin-Anthony e51021f
Delete all_reduce.py
Quentin-Anthony 6678344
Merge branch 'master' into staging-comms-next
jeffra 604eae7
Add isend/irecv comm ops
Quentin-Anthony 2dd23f3
Add pre-commit check and clean up references to torch.dist in comments
Quentin-Anthony b902744
Updated pre-commit error
Quentin-Anthony 3c5e065
Clean up comm.py logic
Quentin-Anthony 971c349
Added comm constants file to avoid raw string assignment
Quentin-Anthony 5e0c8c1
Revert "Relax assertion to allow Megatron-DeepSpeed MoE to use ZeRO 1"
Quentin-Anthony 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
Large diffs are not rendered by default.
Oops, something went wrong.
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import torch | ||
| from .utils import * | ||
| import deepspeed.utils as utils | ||
|
|
||
| supported_torch_version = False | ||
|
|
||
| # See more details at: https://github.com/pytorch/pytorch/pull/48767 | ||
| # The PG API in torch versions lesser than 1.8 are different so it is | ||
| # non-trivial to support both in the same API. We will just use the | ||
| # DS comm. backend in deepspeed/comm/comm.py if torch version if 1.8+. | ||
|
|
||
| if older_torch(): | ||
| # Add custom deepspeed torch comm functions here since we can't import deepspeed.comm | ||
| # NOTE: We can't call torch.distributed directly here. Current hack is to import functions before calling them. | ||
| supported_torch_version = False | ||
| from torch.distributed import * | ||
|
|
||
| def get_world_group(): | ||
| return group.WORLD | ||
|
|
||
| def get_global_rank(group, group_rank): | ||
| from torch.distributed.distributed_c10d import _get_global_rank | ||
| return _get_global_rank(group, group_rank) | ||
|
|
||
| def allgather_fn(output_tensor, input_tensor, group, async_op): | ||
| from torch.distributed import all_gather, get_world_size | ||
| from torch import chunk | ||
| output_tensors = list(chunk(output_tensor, get_world_size(group))) | ||
| return all_gather(output_tensors, input_tensor, group=group, async_op=True) | ||
|
|
||
| def reduce_scatter_fn(output_tensor, input_tensor, group): | ||
| from torch.distributed import reduce_scatter, get_world_size | ||
| from torch import chunk | ||
| input_tensor_lst = list(chunk(input_tensor, get_world_size(group))) | ||
| return reduce_scatter(output_tensor, input_tensor_lst, group=group) | ||
|
|
||
| else: | ||
| supported_torch_version = True | ||
| from .comm import * |
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,40 @@ | ||
| ''' DeepSpeed Communication Backend | ||
|
|
||
| # DS Backends -- Direct C/Ops | ||
|
awan-10 marked this conversation as resolved.
Outdated
|
||
| - NCCL -- future default | ||
| - MPI -- extra performance | ||
| - RCCL -- maybe for AMD | ||
| - GLOO -- N/A -- use via torch | ||
|
|
||
| # via torch.distributed | ||
| - T-NCCL -- default -- will work for AMD as well | ||
| - T-GLOO -- choose for cpu/testing without GPUs | ||
| - T-MPI -- works but not commonly used | ||
| ''' | ||
| ''' DS Backend can be the base class | ||
| -- NcclBackend, MpiBackend, and TorchBackend are the main subclasses we expect for now | ||
| ''' | ||
|
|
||
|
|
||
| class Backend(object): | ||
| def __init__(self, name='backend', rank=0, size=1): | ||
| self.name = name | ||
| # The world size and rank of the world process group | ||
| self.world_group = None | ||
| self.world_size = rank | ||
| self.world_rank = size | ||
| # Single process group (pg) implementation for now but keep a list for future | ||
| self.process_groups = [] | ||
| self.initialized = False | ||
|
|
||
| def is_initialized(self): | ||
| return self.initialized | ||
|
|
||
| def new_group(self): | ||
| # create a new pg and add it to pg list | ||
| pass | ||
|
|
||
| def init_process_group(self): | ||
| # subclasses will initialize them fully | ||
| # - initialize a default world process group and add it to pg list | ||
| self.initialized = True | ||
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,183 @@ | ||
| import torch | ||
| import time | ||
| import argparse | ||
| import os | ||
| import deepspeed | ||
|
|
||
| # dist is global and functions can set them to switch between deepspeed and torch | ||
| dist = None | ||
|
|
||
| DEBUG = False | ||
|
|
||
|
|
||
| def print_rank_0(message): | ||
| if dist.get_rank() == 0: | ||
| print(message) | ||
|
|
||
|
|
||
| def collective_fn(collective, input, output, async_op): | ||
| if collective == "alltoall": | ||
| dist.all_to_all_single(output, input, async_op=async_op) | ||
| elif collective == "allreduce": | ||
| dist.all_reduce(input, async_op=async_op) | ||
| else: | ||
| print_rank_0(f"collective {collective} not supported yet") | ||
| exit(0) | ||
|
|
||
|
|
||
| def get_bw(collective, size, duration, n): | ||
| tput = 0 | ||
| busbw = 0 | ||
| if collective == "alltoall": | ||
| tput = (size / duration) * 8 | ||
| busbw = (size / duration) * ((n - 1) / n) * 8 | ||
| elif collective == "allreduce": | ||
| tput = (size * 2 / duration) * 8 | ||
| busbw = (size / duration) * (2 * (n - 1) / n) * 8 | ||
| else: | ||
| print_rank_0("wrong collective specified") | ||
| exit(0) | ||
| return tput, busbw | ||
|
|
||
|
|
||
| def timed_benchmark(input, output, args, collective): | ||
| dist.barrier() | ||
| torch.cuda.synchronize() | ||
|
|
||
| # Warmup, establish connections, etc. | ||
| for i in range(args.warmup): | ||
| collective_fn(collective, input, output, async_op=args.async_op) | ||
|
|
||
| dist.barrier() | ||
| torch.cuda.synchronize() | ||
|
|
||
| # time the actual collective trials times and average it | ||
| pre = time.perf_counter() | ||
| for i in range(args.trials): | ||
| collective_fn(collective, input, output, async_op=args.async_op) | ||
| torch.cuda.synchronize() | ||
| duration = time.perf_counter() - pre | ||
|
|
||
| # maintain and clean performance data | ||
| duration = duration / args.trials | ||
| size = int(input.shape[0]) * 4 | ||
| n = dist.get_world_size() | ||
| tput, busbw = get_bw(collective, size, duration, n) | ||
|
|
||
| duration_ms = duration * 1e3 | ||
| duration_us = duration * 1e6 | ||
|
|
||
| desc = f'{input.shape[0]}x{4}' | ||
|
|
||
| if args.bw_unit == 'Gbps': | ||
| tput = f'{tput / 1e9:.3f}' | ||
| busbw = f'{busbw /1e9:.3f}' | ||
| elif args.bw_unit == 'GBps': | ||
| tput = f'{tput/8 / 1e9:.3f}' | ||
| busbw = f'{busbw/8 /1e9:.3f}' | ||
|
|
||
| if duration_us < 1e3: | ||
| duration = f'{duration_us:.3f} us' | ||
| else: | ||
| duration = f'{duration_ms:.3f} ms' | ||
|
|
||
| print_rank_0(f"{size:<20} {desc:25s} {duration:20s} {tput:20s} {busbw:20s}") | ||
|
|
||
|
|
||
| def test_correctness(input, output, args, collective): | ||
| world_size = dist.get_world_size() | ||
| global_rank = dist.get_rank() | ||
|
|
||
| for i in range(world_size): | ||
| if i == global_rank: | ||
| print(f"Before AllToAll Input List at rank {global_rank}: {input}") | ||
| dist.barrier() | ||
|
|
||
| collective_fn(collective, input, output, async_op=args.async_op) | ||
|
|
||
| torch.cuda.synchronize() | ||
| dist.barrier() | ||
|
|
||
| for i in range(world_size): | ||
| if i == global_rank: | ||
| print(f"AllToAll Results at rank {global_rank}: {output}") | ||
| dist.barrier() | ||
|
|
||
|
|
||
| def init_distributed(backend): | ||
| global dist | ||
| import torch.distributed as dist | ||
| deepspeed.init_distributed(dist_backend=backend) | ||
| local_rank = int(os.environ['LOCAL_RANK']) | ||
| torch.cuda.set_device(local_rank) | ||
|
|
||
|
|
||
| def init_deepspeed_comm(): | ||
| # TODO: Add code to initialize ds comm backend | ||
| pass | ||
|
|
||
|
|
||
| def init_processes(local_rank, args, backend='nccl'): | ||
| if backend == 'deepspeed': | ||
| init_deepspeed_comm() | ||
| elif backend == 'nccl': | ||
| init_distributed(backend) | ||
|
|
||
| N = dist.get_world_size() | ||
|
|
||
| M_LIST = [] | ||
| for x in (2**p for p in range(1, args.maxsize)): | ||
| M_LIST.append(x) | ||
|
|
||
| # List of benchmarks | ||
| collectives = ['alltoall', 'allreduce'] | ||
|
|
||
| # Run all collectives | ||
| for collective in collectives: | ||
| world_size = dist.get_world_size() | ||
| dist.barrier() | ||
|
|
||
| # Prepare benchmark header | ||
| tput = f'Throughput ({args.bw_unit})' | ||
| busbw = f'BusBW ({args.bw_unit})' | ||
|
|
||
| header = f"\n---- Performance of {collective} on {dist.get_world_size()} devices ---------------------------------------------------------\n" | ||
| header += f"{'Size (Bytes)':20s} {'Description':25s} {'Duration':20s} {tput:20s} {busbw:20s}\n" | ||
| header += "----------------------------------------------------------------------------------------------------" | ||
|
|
||
| print_rank_0(header) | ||
|
|
||
| # loop over various tensor sizes for each collective | ||
| for M in M_LIST: | ||
| global_rank = dist.get_rank() | ||
| mat = torch.ones(N, M, dtype=torch.float32).cuda(local_rank) | ||
| torch.cuda.synchronize() | ||
|
|
||
| if collective == 'alltoall': | ||
| # check needed for alltoall only | ||
| assert mat.numel() % world_size == 0, f"tensor cannot be divided in {world_size} chunks" | ||
|
|
||
| input = ((mat.mul_(float(global_rank))).view(-1)) | ||
| output = (mat.clone().view(-1)) | ||
|
|
||
| timed_benchmark(input, output, args, collective) | ||
|
|
||
| global DEBUG | ||
| if DEBUG: | ||
| test_correctness(input, output, args, collective) | ||
|
|
||
| dist.barrier() | ||
| print_rank_0("\n") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| parser = argparse.ArgumentParser() | ||
| parser.add_argument("--local_rank", type=int) | ||
| parser.add_argument("--trials", type=int, default=5) | ||
| parser.add_argument("--warmup", type=int, default=5) | ||
| parser.add_argument("--maxsize", type=int, default=24) | ||
| parser.add_argument("--async-op", action="store_true") | ||
| parser.add_argument("--bw-unit", type=str, default='Gbps') | ||
| args = parser.parse_args() | ||
| rank = args.local_rank | ||
| init_processes(local_rank=rank, args=args) |
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.