@@ -2198,6 +2198,125 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
21982198 print (f"[Rank { rank } ] PASS _multirank_pg_migration" , flush = True )
21992199
22002200
2201+ def _find_trt_module (mod : nn .Module ) -> Any :
2202+ """Return the first ``TorchTensorRTModule`` in a compiled module, or None."""
2203+ from torch_tensorrt .dynamo .runtime ._TorchTensorRTModule import TorchTensorRTModule
2204+
2205+ if isinstance (mod , TorchTensorRTModule ):
2206+ return mod
2207+ for child in mod .children () if isinstance (mod , nn .Module ) else []:
2208+ found = _find_trt_module (child )
2209+ if found is not None :
2210+ return found
2211+ return None
2212+
2213+
2214+ def _multirank_comm_survives_invalidation (
2215+ rank : int , world_size : int , device : torch .device , trigger : str
2216+ ) -> None :
2217+ """The NCCL communicator must survive an IExecutionContext invalidation.
2218+
2219+ ``bind_nccl_comm()`` attaches the communicator to the *IExecutionContext*.
2220+ Several C++ entry points drop that context and rely on the lazy re-bind in
2221+ ``execute_engine.cpp`` -- which only fires when ``nccl_initialized`` is
2222+ false. ``TRTEngine::runtime_settings()`` and ``set_device_memory_budget()``
2223+ clear/re-bind correctly; ``disable_profiling()`` (TRTEngine.cpp:378) and
2224+ ``set_resource_allocation_strategy()`` (TRTEngine.cpp:791) do not, so the
2225+ replacement context runs collectives with no communicator attached.
2226+ """
2227+ import torch_tensorrt
2228+ from torch_tensorrt .distributed ._nccl_utils import setup_nccl_for_torch_tensorrt
2229+
2230+ setup_nccl_for_torch_tensorrt ()
2231+ group_name = dist .distributed_c10d ._get_default_group ().group_name
2232+
2233+ class _RowParallelLinear (nn .Module ):
2234+ """Row-parallel Linear: local matmul followed by an all-reduce."""
2235+
2236+ def __init__ (self , lin : nn .Linear , group_name : str ) -> None :
2237+ super ().__init__ ()
2238+ self .lin = lin
2239+ self .group_name = group_name
2240+
2241+ def forward (self , x : torch .Tensor ) -> torch .Tensor :
2242+ out = self .lin (x )
2243+ out = torch .ops ._c10d_functional .all_reduce .default (
2244+ out , "sum" , self .group_name
2245+ )
2246+ return torch .ops ._c10d_functional .wait_tensor .default (out )
2247+
2248+ class TinyMLP (nn .Module ):
2249+ def __init__ (self ) -> None :
2250+ super ().__init__ ()
2251+ self .fc1 = nn .Linear (16 , 64 )
2252+ self .relu = nn .ReLU ()
2253+ self .fc2 = nn .Linear (64 , 16 , bias = False )
2254+
2255+ def forward (self , x : torch .Tensor ) -> torch .Tensor :
2256+ return self .fc2 (self .relu (self .fc1 (x )))
2257+
2258+ # Manually shard: fc1 column-parallel, fc2 row-parallel + all-reduce. Mirrors
2259+ # build_exportable_model() in test_export_save_load.py -- DTensor-based
2260+ # parallelize_module does not survive torch.export cleanly.
2261+ torch .manual_seed (42 )
2262+ model = TinyMLP ().to (device )
2263+ w = model .fc1 .weight .data
2264+ chunk = w .shape [0 ] // world_size
2265+ model .fc1 .weight = nn .Parameter (w [rank * chunk : (rank + 1 ) * chunk ].contiguous ())
2266+ b = model .fc1 .bias .data
2267+ model .fc1 .bias = nn .Parameter (b [rank * chunk : (rank + 1 ) * chunk ].contiguous ())
2268+ w2 = model .fc2 .weight .data
2269+ chunk2 = w2 .shape [1 ] // world_size
2270+ model .fc2 .weight = nn .Parameter (
2271+ w2 [:, rank * chunk2 : (rank + 1 ) * chunk2 ].contiguous ()
2272+ )
2273+ model .fc2 = _RowParallelLinear (model .fc2 , group_name )
2274+
2275+ torch .manual_seed (0 )
2276+ inp = torch .randn (4 , 16 , device = device )
2277+
2278+ # Export, not torch.compile: torch.compile returns an OptimizedModule that
2279+ # wraps the ORIGINAL module, so the TRT submodules are not reachable via
2280+ # children()/named_modules(). dynamo.compile returns a real GraphModule.
2281+ ep = torch .export .export (model , args = (inp ,), strict = False )
2282+ trt_model = torch_tensorrt .dynamo .compile (
2283+ ep ,
2284+ inputs = [inp ],
2285+ device = device ,
2286+ disable_tf32 = True ,
2287+ use_python_runtime = False ,
2288+ min_block_size = 1 ,
2289+ use_distributed_mode_trace = True ,
2290+ )
2291+
2292+ with torch .no_grad ():
2293+ expected = trt_model (inp )
2294+
2295+ trt_mod = _find_trt_module (trt_model )
2296+ if trt_mod is None :
2297+ raise AssertionError ("Could not locate a TorchTensorRTModule" )
2298+
2299+ # Drop the IExecutionContext the communicator was bound to.
2300+ if trigger == "resource_allocation" :
2301+ trt_mod .use_dynamically_allocated_resources (True )
2302+ elif trigger == "disable_profiling" :
2303+ trt_mod .disable_profiling ()
2304+ else :
2305+ raise ValueError (f"unknown trigger { trigger !r} " )
2306+
2307+ dist .barrier () # keep ranks in step before the next collective
2308+
2309+ with torch .no_grad ():
2310+ out = trt_model (inp )
2311+
2312+ _check_close (out , expected , f"output after { trigger } invalidation rank={ rank } " )
2313+
2314+ print (
2315+ f"[Rank { rank } ] PASS _multirank_comm_survives_invalidation[{ trigger } ]" ,
2316+ flush = True ,
2317+ )
2318+
2319+
22012320# ============================================================================
22022321# Section 8 — Multi-rank pytest tests (MultiProcessTestCase, requires 2 GPUs)
22032322# ============================================================================
@@ -2329,6 +2448,26 @@ def test_pg_migration(self) -> None:
23292448 device = self ._init_dist ()
23302449 _multirank_pg_migration (self .rank , self .world_size , device )
23312450
2451+ @unittest .skipIf (not has_nccl_collectives (), "No NCCL collective support available" )
2452+ @requires_nccl ()
2453+ @skip_if_lt_x_gpu (2 )
2454+ def test_comm_survives_resource_allocation_change (self ) -> None :
2455+ """set_resource_allocation_strategy() invalidates the context; comm must survive."""
2456+ device = self ._init_dist ()
2457+ _multirank_comm_survives_invalidation (
2458+ self .rank , self .world_size , device , "resource_allocation"
2459+ )
2460+
2461+ @unittest .skipIf (not has_nccl_collectives (), "No NCCL collective support available" )
2462+ @requires_nccl ()
2463+ @skip_if_lt_x_gpu (2 )
2464+ def test_comm_survives_disable_profiling (self ) -> None :
2465+ """disable_profiling() invalidates the context; comm must survive."""
2466+ device = self ._init_dist ()
2467+ _multirank_comm_survives_invalidation (
2468+ self .rank , self .world_size , device , "disable_profiling"
2469+ )
2470+
23322471
23332472# ============================================================================
23342473# Section 9 — torchrun / mpirun entry point (legacy multi-rank runner)
0 commit comments