4545import sys
4646import threading
4747import unittest
48- from contextlib import nullcontext
4948from typing import Any
5049from unittest .mock import MagicMock , patch
5150
@@ -93,6 +92,21 @@ def has_nccl_collectives() -> bool:
9392 return False
9493
9594
95+ def _cpp_runtime_available () -> bool :
96+ """Return True when the C++ Torch-TensorRT runtime extension is loaded.
97+
98+ When True, _TRTEngine registers neither its opaque type nor the Python
99+ tensorrt::execute_engine custom op (see _TRTEngine.py:1376), so
100+ TestPythonRuntimePickle cannot run and must be skipped.
101+ """
102+ try :
103+ from torch_tensorrt ._features import ENABLED_FEATURES
104+
105+ return bool (ENABLED_FEATURES .torch_tensorrt_runtime )
106+ except Exception :
107+ return False
108+
109+
96110# ---------------------------------------------------------------------------
97111# Shared test fakes
98112# ---------------------------------------------------------------------------
@@ -1204,7 +1218,6 @@ def _run(self, model: nn.Module, inputs: list[torch.Tensor]) -> None:
12041218 backend = "torch_tensorrt" ,
12051219 dynamic = False ,
12061220 options = {
1207- "use_python_runtime" : True ,
12081221 "min_block_size" : 1 ,
12091222 "use_distributed_mode_trace" : True ,
12101223 },
@@ -1323,6 +1336,10 @@ def test_group_name_survives_context_exit(self) -> None:
13231336 not has_nccl_collectives (),
13241337 "Skipped: No NCCL collective support (neither native TRT collectives nor TRT-LLM)." ,
13251338)
1339+ @unittest .skipIf (
1340+ _cpp_runtime_available (),
1341+ "Skipped: Python runtime (_TRTEngine) op not registered when C++ extension is available." ,
1342+ )
13261343class TestPythonRuntimePickle (unittest .TestCase ):
13271344 """Verifies that _nccl_comm is stripped on pickle and reset on unpickle.
13281345
@@ -1344,8 +1361,12 @@ def tearDownClass(cls) -> None:
13441361 dist .destroy_process_group ()
13451362
13461363 def _compile_small_model (self ) -> Any :
1347- """Return a compiled module backed by a Python ``TRTEngine``."""
1348- import torch_tensorrt
1364+ """Return a compiled module backed by a Python ``TRTEngine``.
1365+
1366+ This class is only reached when ENABLED_FEATURES.torch_tensorrt_runtime=False
1367+ (C++ extension not installed), so setup_engine() naturally instantiates
1368+ _TRTEngine without any patching needed.
1369+ """
13491370
13501371 class LinearModel (nn .Module ):
13511372 def forward (self , x : torch .Tensor ) -> torch .Tensor :
@@ -1359,21 +1380,18 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
13591380 backend = "torch_tensorrt" ,
13601381 dynamic = False ,
13611382 options = {
1362- "use_python_runtime" : True ,
13631383 "min_block_size" : 1 ,
13641384 },
13651385 )
1366- _ = trt_model (inp ) # trigger compilation
1386+ _ = trt_model (inp ) # trigger compilation → setup_engine() → _TRTEngine
13671387 return trt_model
13681388
13691389 @classmethod
13701390 def _find_python_trt_engine (cls , obj : Any ) -> Any :
13711391 """Walk a compiled module and return the Python ``TRTEngine`` instance.
13721392
1373- With the unified runtime, the wrapping ``TorchTensorRTModule`` is the
1374- same regardless of backend; ``use_python_runtime=True`` causes its
1375- ``.engine`` attribute to be a Python ``TRTEngine`` (vs the C++
1376- ``torch.classes.tensorrt.Engine`` otherwise).
1393+ Only meaningful when C++ extension is absent (ENABLED_FEATURES.torch_tensorrt_runtime=False),
1394+ which is enforced by the class-level skipIf decorator.
13771395 """
13781396 from torch_tensorrt .dynamo .runtime ._TorchTensorRTModule import (
13791397 TorchTensorRTModule ,
@@ -1633,7 +1651,6 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
16331651 backend = "torch_tensorrt" ,
16341652 dynamic = False ,
16351653 options = {
1636- "use_python_runtime" : True ,
16371654 "min_block_size" : 1 ,
16381655 "use_distributed_mode_trace" : True ,
16391656 },
@@ -1736,7 +1753,6 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
17361753 backend = "torch_tensorrt" ,
17371754 dynamic = False ,
17381755 options = {
1739- "use_python_runtime" : True ,
17401756 "min_block_size" : 1 ,
17411757 "use_distributed_mode_trace" : True ,
17421758 },
@@ -1770,6 +1786,10 @@ def _multirank_distributed_mode_subgroup(
17701786 # New subgroup with all ranks (different group object from WORLD)
17711787 subgroup = dist .new_group (ranks = list (range (world_size )))
17721788 sg_name = subgroup .group_name
1789+ # Force subgroup NCCL comm init — PyTorch creates ncclComm_t lazily on first
1790+ # collective; bind_nccl_comm() in execute_engine reads getCommPtr() which is 0
1791+ # until at least one collective has run on the group.
1792+ dist .barrier (group = subgroup )
17731793
17741794 class AllReduceModel (nn .Module ):
17751795 def forward (self , x : torch .Tensor ) -> torch .Tensor :
@@ -1791,7 +1811,6 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
17911811 backend = "torch_tensorrt" ,
17921812 dynamic = False ,
17931813 options = {
1794- "use_python_runtime" : True ,
17951814 "min_block_size" : 1 ,
17961815 "use_distributed_mode_trace" : True ,
17971816 },
@@ -1872,6 +1891,9 @@ def _multirank_distributed_mode_context_switch(
18721891 sg2 = dist .new_group (ranks = list (range (world_size )))
18731892 sg1_name = sg1 .group_name
18741893 sg2_name = sg2 .group_name
1894+ # Force both subgroup NCCL comms to init before TRT tries to bind them.
1895+ dist .barrier (group = sg1 )
1896+ dist .barrier (group = sg2 )
18751897
18761898 class AllReduceModel (nn .Module ):
18771899 def __init__ (self , name : str ) -> None :
@@ -1893,7 +1915,6 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
18931915 backend = "torch_tensorrt" ,
18941916 dynamic = False ,
18951917 options = {
1896- "use_python_runtime" : True ,
18971918 "min_block_size" : 1 ,
18981919 "use_distributed_mode_trace" : True ,
18991920 },
@@ -1947,50 +1968,41 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
19471968 expected_sum = world_size * (world_size + 1 ) // 2
19481969 expected = torch .full ((1 , 4 ), float (expected_sum ), device = device )
19491970
1950- for label , use_python_runtime in [("cpp" , False ), ("python" , True )]:
1951- # ---- Step 1: compile + run with default world group ----
1952- model = AllReduceModel (world_name ).to (device ).eval ()
1971+ # ---- Step 1: compile + run with default world group ----
1972+ model = AllReduceModel (world_name ).to (device ).eval ()
19531973
1954- with distributed_context (world_group ):
1955- trt_model = torch .compile (
1956- model ,
1957- backend = "torch_tensorrt" ,
1958- dynamic = False ,
1959- options = {
1960- "use_python_runtime" : use_python_runtime ,
1961- "min_block_size" : 1 ,
1962- "use_distributed_mode_trace" : True ,
1963- },
1964- )
1965- with torch .no_grad ():
1966- out_world = trt_model (inp )
1974+ with distributed_context (world_group ):
1975+ trt_model = torch .compile (
1976+ model ,
1977+ backend = "torch_tensorrt" ,
1978+ dynamic = False ,
1979+ options = {
1980+ "min_block_size" : 1 ,
1981+ "use_distributed_mode_trace" : True ,
1982+ },
1983+ )
1984+ with torch .no_grad ():
1985+ out_world = trt_model (inp )
19671986
1968- _check_close (out_world , expected , f"[ { label } ] world group rank={ rank } " )
1987+ _check_close (out_world , expected , f"world group rank={ rank } " )
19691988
1970- # ---- Step 2: migrate to subgroup via distributed_context(subgroup, model) ----
1971- # This calls set_distributed_mode() which resets nccl_initialized on the
1972- # C++ engine, and keeps _state.pg = subgroup active for the Python runtime's
1973- # lazy setup_nccl_comm() call.
1974- with distributed_context (subgroup , trt_model ) as migrated_model :
1975- with torch .no_grad ():
1976- out_sub = migrated_model (inp )
1989+ # ---- Step 2: migrate to subgroup via distributed_context(subgroup, model) ----
1990+ # set_distributed_mode() resets nccl_initialized on the C++ engine so
1991+ # bind_nccl_comm() re-runs on the next forward with the new group name.
1992+ with distributed_context (subgroup , trt_model ) as migrated_model :
1993+ with torch .no_grad ():
1994+ out_sub = migrated_model (inp )
19771995
1978- _check_close (out_sub , expected , f"[ { label } ] migrated to subgroup rank={ rank } " )
1996+ _check_close (out_sub , expected , f"migrated to subgroup rank={ rank } " )
19791997
1980- # ---- Step 3: set_distributed_mode (persistent, outside context) ----
1981- subgroup2 = dist .new_group (ranks = list (range (world_size )))
1982- torch_tensorrt .distributed .set_distributed_mode (subgroup2 , trt_model )
1983- # _state.pg is NOT set here — Python runtime falls back to world group
1984- # for lazy setup_nccl_comm; C++ runtime uses the pinned group name.
1985- # For C++ runtime only (Python runtime needs _state.pg active):
1986- if not use_python_runtime :
1987- with torch .no_grad ():
1988- out_pin = trt_model (inp )
1989- _check_close (
1990- out_pin , expected , f"[{ label } ] set_distributed_mode rank={ rank } "
1991- )
1998+ # ---- Step 3: set_distributed_mode (persistent, outside context) ----
1999+ subgroup2 = dist .new_group (ranks = list (range (world_size )))
2000+ torch_tensorrt .distributed .set_distributed_mode (subgroup2 , trt_model )
2001+ with torch .no_grad ():
2002+ out_pin = trt_model (inp )
2003+ _check_close (out_pin , expected , f"set_distributed_mode rank={ rank } " )
19922004
1993- print (f"[Rank { rank } ] PASS _multirank_pg_migration [ { label } ] " , flush = True )
2005+ print (f"[Rank { rank } ] PASS _multirank_pg_migration" , flush = True )
19942006
19952007
19962008# ============================================================================
@@ -2096,7 +2108,7 @@ def test_distributed_mode_tp_model(self) -> None:
20962108 @requires_nccl ()
20972109 @skip_if_lt_x_gpu (2 )
20982110 def test_distributed_mode_subgroup (self ) -> None :
2099- """distributed_context() with a non-default TP subgroup routes NCCL correctly."""
2111+ """C++ runtime with a non-default TP subgroup routes NCCL correctly."""
21002112 device = self ._init_dist ()
21012113 _multirank_distributed_mode_subgroup (self .rank , self .world_size , device )
21022114
@@ -2112,7 +2124,7 @@ def test_cpp_runtime_bind_nccl(self) -> None:
21122124 @requires_nccl ()
21132125 @skip_if_lt_x_gpu (2 )
21142126 def test_distributed_mode_context_switch (self ) -> None :
2115- """Switching distributed_context between two subgroups routes to correct communicator ."""
2127+ """C++ runtime: switching distributed_context between two subgroups routes correctly ."""
21162128 device = self ._init_dist ()
21172129 _multirank_distributed_mode_context_switch (self .rank , self .world_size , device )
21182130
@@ -2140,8 +2152,12 @@ def run_multirank_tests() -> None:
21402152 _multirank_all_gather_correctness ,
21412153 _multirank_reduce_scatter_all_reduce_ops ,
21422154 _multirank_all_to_all_correctness ,
2143- _multirank_scatter_correctness ,
2144- _multirank_gather_correctness ,
2155+ lambda r , ws , dev : [
2156+ _multirank_scatter_correctness (i , r , ws , dev ) for i in range (ws )
2157+ ],
2158+ lambda r , ws , dev : [
2159+ _multirank_gather_correctness (i , r , ws , dev ) for i in range (ws )
2160+ ],
21452161 _multirank_distributed_mode_tp_model ,
21462162 _multirank_distributed_mode_subgroup ,
21472163 _multirank_cpp_runtime_bind_nccl ,
0 commit comments