Skip to content

Commit 6cc9cc5

Browse files
committed
ParallelProcessGroup
1 parent b84c5a6 commit 6cc9cc5

2 files changed

Lines changed: 132 additions & 0 deletions

File tree

torchft/process_group.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,114 @@ def reduce_scatter_tensor_coalesced(
611611
)
612612

613613

614+
class _ParallelWork(Work):
615+
def __init__(self, works: List[Work]) -> None:
616+
super().__init__()
617+
self._works = works
618+
619+
def wait(self, timeout: Optional[timedelta] = None) -> bool:
620+
for work in self._works:
621+
if timeout is not None:
622+
work.wait(timeout=timeout)
623+
else:
624+
work.wait()
625+
return True
626+
627+
def get_future(self) -> torch.futures.Future[object]:
628+
futures = [work.get_future() for work in self._works]
629+
return torch.futures.collect_all(futures)
630+
631+
632+
class ParallelProcessGroup(ProcessGroupWrapper):
633+
def __init__(
634+
self,
635+
base: ProcessGroupWrapper,
636+
timeout: timedelta = timedelta(seconds=60),
637+
count: int = 10,
638+
) -> None:
639+
super().__init__(timeout=timeout)
640+
641+
self._base = base
642+
self._count = count
643+
self._pgs = []
644+
645+
self._create_pg = base._create_pg
646+
647+
def configure(self, store_addr: str, rank: int, world_size: int) -> None:
648+
# abort if already initialized
649+
self.abort()
650+
651+
self._pgs = []
652+
653+
for i in range(self._count):
654+
store = create_store_client(
655+
f"{store_addr}/parallel{i}", timeout=self._timeout
656+
)
657+
658+
self._pgs.append(self._create_pg(store, rank, world_size))
659+
660+
self._pg = self._pgs[0]
661+
662+
def getBackendName(self) -> str:
663+
return f"{self._base.getBackendName()}-parallel"
664+
665+
def _split_tensors(self, tensors: List[torch.Tensor]) -> List[List[torch.Tensor]]:
666+
if not isinstance(tensors, (list, tuple)):
667+
tensors = [tensors]
668+
669+
tensor_lists = [[] for _ in range(self._count)]
670+
for t in tensors:
671+
chunks = torch.tensor_split(t.view(-1), self._count, dim=0)
672+
for i, chunk in enumerate(chunks):
673+
tensor_lists[i].append(chunk)
674+
675+
return tensor_lists
676+
677+
def allreduce(self, tensors: List[torch.Tensor], opts: object) -> Work:
678+
tensor_lists = self._split_tensors(tensors)
679+
680+
with self._run_context():
681+
works = []
682+
for i in range(self._count):
683+
works.append(
684+
self._pgs[i].allreduce(tensor_lists[i], self._opts_hook(opts))
685+
)
686+
687+
return self._wrap_work(_ParallelWork(works), opts)
688+
689+
def reduce(self, tensors: List[torch.Tensor], dst: int, opts: object) -> Work:
690+
tensor_lists = self._split_tensors(tensors)
691+
692+
with self._run_context():
693+
works = []
694+
for i in range(self._count):
695+
works.append(
696+
self._pgs[i].reduce(tensor_lists[i], dst, self._opts_hook(opts))
697+
)
698+
699+
return self._wrap_work(_ParallelWork(works), opts)
700+
701+
def send(self, tensors: List[torch.Tensor], dst_rank: int, tag: int) -> Work:
702+
tensor_lists = self._split_tensors(tensors)
703+
704+
with self._run_context():
705+
works = []
706+
for i in range(self._count):
707+
works.append(self._pgs[i].send(tensor_lists[i], dst_rank, tag))
708+
709+
return self._wrap_work(_ParallelWork(works), None)
710+
711+
def recv(self, tensors: List[torch.Tensor], src_rank: int, tag: int) -> Work:
712+
tensor_lists = self._split_tensors(tensors)
713+
714+
with self._run_context():
715+
works = []
716+
for i in range(self._count):
717+
works.append(self._pgs[i].recv(tensor_lists[i], src_rank, tag))
718+
719+
return self._wrap_work(_ParallelWork(works), None)
720+
721+
614722
class _WorkCUDATimeout(Work):
615723
def __init__(self, pg: ProcessGroup, work: Work, timeout: timedelta) -> None:
616724
super().__init__()

torchft/process_group_test.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from torchft.process_group import (
4141
ErrorSwallowingProcessGroupWrapper,
4242
ManagedProcessGroup,
43+
ParallelProcessGroup,
4344
ProcessGroup,
4445
ProcessGroupBabyGloo,
4546
ProcessGroupBabyNCCL,
@@ -690,6 +691,29 @@ def test_baby_gloo_apis(self) -> None:
690691
with self.assertRaisesRegex(OSError, "handle is closed"):
691692
a.allreduce([t], AllreduceOptions()).wait()
692693

694+
def test_parallel_gloo_apis(self) -> None:
695+
dummy_init_pg()
696+
697+
store = TCPStore(
698+
host_name="localhost", port=0, is_master=True, wait_for_workers=False
699+
)
700+
701+
store_addr = f"localhost:{store.port}/prefix"
702+
703+
a = ParallelProcessGroup(
704+
base=ProcessGroupGloo(),
705+
count=4,
706+
)
707+
a.configure(store_addr, 0, 1)
708+
a.register("test_parallel_gloo_apis")
709+
710+
_test_pg(
711+
a,
712+
skip=("reduce_scatter_tensor_coalesced"),
713+
)
714+
715+
a.unregister()
716+
693717
# pyre-fixme[56]: Pyre was not able to infer the type of argument
694718
@skipUnless(torch.cuda.is_available(), "needs CUDA")
695719
def test_baby_nccl_apis(self) -> None:

0 commit comments

Comments
 (0)