11# ExecuTorch partitioner: partition by execute_engine nodes.
22
3+ import logging
34from typing import Callable , Dict , List , Optional , Tuple
45
56import torch
1213from executorch .exir .backend .utils import tag_constant_data
1314from torch .export import ExportedProgram
1415from torch .fx .passes .infra .partitioner import CapabilityBasedPartitioner
15- from torch_tensorrt .executorch .backend import TensorRTBackend
16+ from torch_tensorrt .dynamo .runtime ._TorchTensorRTModule import DEVICE_IDX
17+ from torch_tensorrt .executorch .backend import (
18+ TensorRTBackend ,
19+ _get_engine_info_from_edge_program ,
20+ _parse_device_id ,
21+ )
1622from torch_tensorrt .executorch .operator_support import TensorRTOperatorSupport
1723
1824# Key recognized by ExecuTorch's PropagateDevicePass that tags delegate I/O
2935except ImportError :
3036 _TARGET_DEVICE_COMPILE_SPEC_KEY = "target_device"
3137
38+ logger = logging .getLogger (__name__ )
39+
3240
3341class TensorRTPartitioner (Partitioner ): # type: ignore[misc]
3442 """Partitions the graph for TensorRT delegation.
@@ -42,6 +50,11 @@ class TensorRTPartitioner(Partitioner): # type: ignore[misc]
4250 Callers targeting a non-default GPU should pre-populate
4351 ``compile_specs`` with the desired ``CompileSpec("target_device",
4452 b"cuda:<index>")`` to override the default.
53+
54+ Note: ``target_device`` is AOT metadata only -- it drives ExecuTorch's
55+ PropagateDevicePass tagging at export time. At runtime the C++ backend
56+ selects the GPU from the device baked into the serialized engine blob,
57+ not from this value.
4558 """
4659
4760 def __init__ (
@@ -50,21 +63,45 @@ def __init__(
5063 ) -> None :
5164 super ().__init__ ()
5265 self .compile_specs = list (compile_specs ) if compile_specs else []
53- # Mirror CudaPartitioner: emit a target_device CompileSpec so that
54- # ExecuTorch's PropagateDevicePass tags delegate I/O TensorSpecs with
55- # the correct device, which is then serialized into the .pte's
56- # extra_tensor_info.device_type field.
57- if not any (
66+ # Mirror CudaPartitioner: a target_device CompileSpec drives ExecuTorch's
67+ # PropagateDevicePass, which tags delegate I/O TensorSpecs with the device
68+ # and serializes it into the .pte's extra_tensor_info. When the caller pins
69+ # it we use that verbatim; otherwise it is derived per export from the
70+ # engine's real device in partition() (engine nodes are not available here)
71+ # so a cuda:N engine is not mislabeled cuda:0.
72+ self ._has_explicit_target_device = any (
5873 s .key == _TARGET_DEVICE_COMPILE_SPEC_KEY for s in self .compile_specs
59- ):
60- self .compile_specs .append (
61- CompileSpec (_TARGET_DEVICE_COMPILE_SPEC_KEY , b"cuda:0" )
62- )
74+ )
6375 self .delegation_spec = DelegationSpec (
6476 backend_id = TensorRTBackend .__name__ ,
6577 compile_specs = self .compile_specs ,
6678 )
6779
80+ def _resolve_target_device (self , exported_program : ExportedProgram ) -> bytes :
81+ """Best-effort ``target_device`` for the delegate-boundary TensorSpecs.
82+
83+ Reuses the backend's own engine-info extraction so the device index
84+ cannot drift from the runtime blob. Any extraction failure -- no single
85+ engine node (zero or multiple TRT partitions) or an unreadable index --
86+ falls back to ``cuda:0``; per-partition multi-GPU labeling is left to a
87+ follow-up.
88+ """
89+ try :
90+ engine_info = _get_engine_info_from_edge_program (exported_program )
91+ return f"cuda:{ _parse_device_id (engine_info [DEVICE_IDX ])} " .encode ()
92+ except Exception as e :
93+ # Broad by design: any extraction failure must fall back, not abort
94+ # the export. Warn so a non-default GPU silently labeled cuda:0 stays
95+ # diagnosable.
96+ logger .warning (
97+ "Could not derive target_device from the TensorRT engine (%s); "
98+ "falling back to cuda:0. A non-default GPU engine may be "
99+ 'mislabeled -- pin it via CompileSpec("target_device", '
100+ 'b"cuda:<index>").' ,
101+ e ,
102+ )
103+ return b"cuda:0"
104+
68105 def partition (self , exported_program : ExportedProgram ) -> PartitionResult :
69106 capability_partitioner = CapabilityBasedPartitioner (
70107 exported_program .graph_module ,
@@ -73,12 +110,26 @@ def partition(self, exported_program: ExportedProgram) -> PartitionResult:
73110 )
74111 partition_list = capability_partitioner .propose_partitions ()
75112
113+ if self ._has_explicit_target_device :
114+ delegation_spec = self .delegation_spec
115+ else :
116+ delegation_spec = DelegationSpec (
117+ backend_id = TensorRTBackend .__name__ ,
118+ compile_specs = self .compile_specs
119+ + [
120+ CompileSpec (
121+ _TARGET_DEVICE_COMPILE_SPEC_KEY ,
122+ self ._resolve_target_device (exported_program ),
123+ )
124+ ],
125+ )
126+
76127 partition_tags : Dict [str , DelegationSpec ] = {}
77128 for partition in partition_list :
78129 tag = f"tensorrt_{ partition .id } "
79130 for node in partition .nodes :
80131 node .meta ["delegation_tag" ] = tag
81- partition_tags [tag ] = self . delegation_spec
132+ partition_tags [tag ] = delegation_spec
82133
83134 tag_constant_data (exported_program )
84135
0 commit comments