@@ -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+
614722class _WorkCUDATimeout (Work ):
615723 def __init__ (self , pg : ProcessGroup , work : Work , timeout : timedelta ) -> None :
616724 super ().__init__ ()
0 commit comments