@@ -464,6 +464,13 @@ def log(message: str) -> None:
464464
465465 log ("reference saved" )
466466
467+ # Rank 0 performs extra reference computation and file I/O. Keep the
468+ # other ranks alive until that work is complete so they do not tear
469+ # down NCCL while rank 0 is still using the CUDA context.
470+ dist .barrier ()
471+ torch .cuda .synchronize (device )
472+ log ("worker complete; all ranks ready for teardown" )
473+
467474 finally :
468475 if dist .is_available () and dist .is_initialized ():
469476 log ("destroying torch distributed process group" )
@@ -613,6 +620,13 @@ def log(message: str) -> None:
613620
614621 log ("reference saved" )
615622
623+ # The subgroup communicators are destroyed collectively by
624+ # cleanup_dist_env_and_memory(). Do not let nonzero ranks enter that
625+ # teardown while rank 0 is still computing/saving the reference.
626+ dist .barrier ()
627+ torch .cuda .synchronize (device )
628+ log ("worker complete; all ranks ready for teardown" )
629+
616630 finally :
617631 log ("tearing down FastVideo distributed state" )
618632 cleanup_dist_env_and_memory ()
@@ -656,15 +670,20 @@ def _run_torchrun(
656670 env .update (
657671 {
658672 "PYTHONUNBUFFERED" : "1" ,
659- "NCCL_DEBUG" : env .get ("NCCL_DEBUG" , "INFO" ),
660- "TORCH_DISTRIBUTED_DEBUG" : env .get (
661- "TORCH_DISTRIBUTED_DEBUG" ,
662- "DETAIL" ,
663- ),
664673 "TORCH_NCCL_ASYNC_ERROR_HANDLING" : "1" ,
665674 }
666675 )
667676
677+ # Detailed distributed logging is useful when diagnosing a hang, but it
678+ # must not be the default test environment. In particular, PyTorch's
679+ # DETAIL wrapper can deadlock a raw NCCL P2P send/recv on some NCCL and GPU
680+ # combinations before Ring Attention itself is reached. Preserve any
681+ # values explicitly supplied by the caller, and only provide verbose
682+ # defaults when the test-specific debug switch is enabled.
683+ if env .get ("FASTVIDEO_RING_TEST_DEBUG" ) == "1" :
684+ env .setdefault ("NCCL_DEBUG" , "INFO" )
685+ env .setdefault ("TORCH_DISTRIBUTED_DEBUG" , "DETAIL" )
686+
668687 # # Some containerized hosts have broken CUDA P2P/IPC/SHM paths while
669688 # # NCCL socket transport remains functional. Keep normal NCCL behavior by
670689 # # default, but allow an explicit correctness-only socket fallback.
@@ -707,6 +726,59 @@ def _run_torchrun(
707726 )
708727
709728
729+ @pytest .mark .parametrize (
730+ ("debug_enabled" , "expected_nccl" , "expected_torch" ),
731+ [
732+ (False , None , None ),
733+ (True , "INFO" , "DETAIL" ),
734+ ],
735+ )
736+ def test_torchrun_debug_defaults_are_opt_in (
737+ monkeypatch : pytest .MonkeyPatch ,
738+ tmp_path : Path ,
739+ debug_enabled : bool ,
740+ expected_nccl : str | None ,
741+ expected_torch : str | None ,
742+ ) -> None :
743+ """The harness must not enable distributed debug wrappers by default."""
744+ captured_env : dict [str , str ] = {}
745+
746+ class _CompletedProcess :
747+ pid = 1
748+
749+ @staticmethod
750+ def wait (timeout : int | None = None ) -> int :
751+ del timeout
752+ return 0
753+
754+ def fake_popen (
755+ cmd : list [str ],
756+ * ,
757+ env : dict [str , str ],
758+ start_new_session : bool ,
759+ ) -> _CompletedProcess :
760+ del cmd , start_new_session
761+ captured_env .update (env )
762+ return _CompletedProcess ()
763+
764+ monkeypatch .delenv ("NCCL_DEBUG" , raising = False )
765+ monkeypatch .delenv ("TORCH_DISTRIBUTED_DEBUG" , raising = False )
766+ if debug_enabled :
767+ monkeypatch .setenv ("FASTVIDEO_RING_TEST_DEBUG" , "1" )
768+ else :
769+ monkeypatch .delenv ("FASTVIDEO_RING_TEST_DEBUG" , raising = False )
770+ monkeypatch .setattr (subprocess , "Popen" , fake_popen )
771+
772+ _run_torchrun (
773+ script_path = Path (__file__ ),
774+ nproc_per_node = 2 ,
775+ output_path = tmp_path / "unused.pt" ,
776+ )
777+
778+ assert captured_env .get ("NCCL_DEBUG" ) == expected_nccl
779+ assert captured_env .get ("TORCH_DISTRIBUTED_DEBUG" ) == expected_torch
780+
781+
710782def test_multi_gpu_ring_attention_matches_full_attention (
711783 tmp_path : Path ,
712784) -> None :
@@ -843,4 +915,4 @@ def _parse_args() -> argparse.Namespace:
843915 else :
844916 _run_multi_gpu_worker (
845917 Path (args .output ),
846- )
918+ )
0 commit comments