5353
5454@triton .jit
5555def add_one_kernel (x_ptr , y_ptr , n_elements , BLOCK_SIZE : tl .constexpr ):
56- # Arg order matters for the AOT path: TRT launches the embedded PTX with
57- # arguments in (input_ptrs, output_ptrs, extra_args) order — inputs first,
58- # then outputs, then anything from ``extra_args`` in ``@trtp.aot_impl``.
59- # If this kernel declared ``(x_ptr, n_elements, y_ptr, ...)`` then TRT
60- # would feed the output pointer into ``n_elements`` and ``n_elements``
61- # into ``y_ptr`` at launch, which is a wild pointer dereference (engine
62- # builds fine, ``enqueueV3`` returns -1 and the process segfaults).
56+ # AOT path requires (inputs, outputs, extra_args) order — swapping any
57+ # two slots feeds the wrong value into the kernel and segfaults.
6358 pid = tl .program_id (0 )
6459 block_start = pid * BLOCK_SIZE
6560 offsets = block_start + tl .arange (0 , BLOCK_SIZE )
@@ -71,13 +66,8 @@ def add_one_kernel(x_ptr, y_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
7166
7267@triton .jit
7368def add_one_inplace_kernel (x_ptr , n_elements , BLOCK_SIZE : tl .constexpr ):
74- # Distinct kernel for the aliased-I/O variant. The plugin descriptor
75- # declares its output as ``X.aliased()`` — at runtime TRT passes a
76- # *single* pointer for the aliased I/O pair (the shared buffer), not two.
77- # If we re-used ``add_one_kernel`` here, TRT would supply: pointer,
78- # n_elements, padding... and the kernel's ``y_ptr`` slot would absorb
79- # ``n_elements`` while ``n_elements`` would read the padding zero — the
80- # mask would be all-false and the kernel would do nothing.
69+ # Aliased-I/O variant: TRT routes a single pointer for the shared
70+ # input/output buffer, so the kernel takes one pointer, not two.
8171 pid = tl .program_id (0 )
8272 block_start = pid * BLOCK_SIZE
8373 offsets = block_start + tl .arange (0 , BLOCK_SIZE )
@@ -191,29 +181,13 @@ def add_plugin_aot_impl(
191181 launch_params .block_x = compiled_kernel .metadata .num_warps * 32 # threads per block
192182 launch_params .shared_mem = compiled_kernel .metadata .shared # bytes of shared mem
193183
194- # ``extra_args`` are scalar arguments appended to the kernel's argument list at
195- # launch. ``n_elements`` is passed as a 32-bit symbolic integer so TRT
196- # evaluates it from the actual tensor size at runtime.
197- #
198- # Triton >= 3.x always emits two trailing ``.param .u64 .ptr`` slots in
199- # the compiled PTX for ``global_scratch`` and ``profile_scratch`` — even
200- # when their sizes (``compiled_kernel.metadata.global_scratch_size`` /
201- # ``profile_scratch_size``) are 0. Triton's own runtime allocates
202- # zero-sized scratch buffers and passes those pointers at launch; TRT's
203- # AOT plugin path doesn't know about them and would otherwise leave the
204- # two trailing slots filled with stale register state — symptom:
205- # ``Failed to enqueue status -1`` and a segfault on the first call.
206- # We pad ``extra_args`` with four ``SymInt32(0)`` (two per u64 slot) so
207- # the kernel sees null pointers for both scratch params; since their
208- # sizes are 0 the kernel never dereferences them.
209- extra_args = trtp .SymIntExprs (1 + 4 )
210- extra_args [0 ] = trtp .SymInt32 (N )
211- for _i in range (1 , 5 ):
212- extra_args [_i ] = trtp .SymInt32 (0 )
184+ extra_args = torch_tensorrt .dynamo .conversion .plugins .make_aot_extra_args (
185+ [trtp .SymInt32 (N )], compiled_kernel = compiled_kernel
186+ )
213187
214188 return (
215- compiled_kernel .metadata .name , # kernel function name in PTX
216- compiled_kernel .asm ["ptx" ], # PTX source — embedded in TRT engine
189+ compiled_kernel .metadata .name ,
190+ compiled_kernel .asm ["ptx" ],
217191 launch_params ,
218192 extra_args ,
219193 )
@@ -244,35 +218,15 @@ def add_plugin_aot_impl(
244218# In-place variant: aliased plugin I/O
245219# -----------------------------------------
246220#
247- # This second registration shows the same kernel exposed as an *in-place* plugin —
248- # the engine mutates the input buffer directly instead of allocating a separate
249- # output. Useful for KV-cache updates and any pattern where only a small slice of
250- # a large state changes per call.
251- #
252- # Three things change vs. ``my::add_one`` above:
253- #
254- # 1. ``mutates_args=("X",)`` on the torch op. This is the load-bearing signal —
255- # it tells the QDP descriptor in ``_generate_plugin.py`` that input ``X`` is
256- # a candidate for aliasing, and it also tells PyTorch's autograd and
257- # functionalization machinery that the op has side effects on ``X``.
258- #
259- # 2. The registered fake returns ``X`` by identity (``return X``). Combined with
260- # the schema's ``mutates_args``, this is what makes the descriptor emit
261- # ``X.aliased()`` (see ``_generate_plugin._generic_plugin_desc``) instead of
262- # building a fresh output ``TensorDesc``.
221+ # Same kernel exposed as an in-place plugin: the engine mutates the input
222+ # buffer directly via QDP ``TensorDesc.aliased()`` instead of allocating a
223+ # separate output. Useful for KV-cache updates and similar patterns.
263224#
264- # 3. The descriptor itself uses ``X.aliased()``. ``aliased()`` returns a
265- # ``TensorDesc`` that shares its data buffer with ``X`` — TRT will route the
266- # same pointer to both the input and output binding at runtime.
267- #
268- # The eager torch impl has to mutate ``X`` itself (so the semantics match what
269- # the engine will do) and return ``X.clone()``. ``torch.library`` forbids
270- # returning an input by identity from a custom op, hence the clone.
271- #
272- # Note on the AOT kernel: we re-use ``add_one_kernel`` unchanged. Its signature
273- # takes two pointers (``x_ptr``, ``y_ptr``). With aliased I/O declared, TRT
274- # passes the same buffer for both — the kernel reads from ``x_ptr`` and writes
275- # to ``y_ptr``, which is the same memory, so the effect is in-place.
225+ # Two signals together declare aliasing to the framework:
226+ # * ``mutates_args=("X",)`` on the torch op
227+ # * the registered fake returns ``X`` by identity
228+ # The eager impl must mutate ``X`` itself and return a clone — ``torch.library``
229+ # rejects returning an input by identity.
276230
277231
278232@torch .library .custom_op ("my::add_one_inplace" , mutates_args = ("X" ,)) # type: ignore[misc]
@@ -281,26 +235,16 @@ def add_one_inplace(X: torch.Tensor) -> torch.Tensor:
281235 BLOCK_SIZE = 256
282236 grid = lambda meta : (triton .cdiv (X .numel (), meta ["BLOCK_SIZE" ]),)
283237 add_one_inplace_kernel [grid ](X , X .numel (), BLOCK_SIZE = BLOCK_SIZE )
284- # Must not return X by identity — torch.library's no-alias constraint
285- # rejects that. The TRT path doesn't observe this clone (aliasing is
286- # declared at the descriptor level), it's purely for the eager impl.
287238 return X .clone ()
288239
289240
290241@torch .library .register_fake ("my::add_one_inplace" )
291242def _ (X : torch .Tensor ) -> torch .Tensor :
292- # Identity return is the secondary signal the descriptor uses to detect
293- # aliasing. Combined with ``mutates_args=("X",)`` above, this is what
294- # makes ``_generic_plugin_desc`` emit ``X.aliased()`` for the output.
295243 return X
296244
297245
298246@trtp .register ("my::add_one_inplace" )
299247def add_one_inplace_desc (X : trtp .TensorDesc ) -> Tuple [trtp .TensorDesc ]:
300- # ``aliased()`` is the QDP API that declares output-shares-storage-with-input.
301- # Engine build will fail with "PreviewFeature::kALIASED_PLUGIN_IO_10_03 not
302- # enabled" unless the build config enables that preview feature; the
303- # converter wires this on for you when it sees a non-empty aliased_map.
304248 return X .aliased ()
305249
306250
@@ -313,7 +257,6 @@ def add_one_inplace_aot_impl(
313257 type_str = "fp32" if X .dtype == trt .float32 else "fp16"
314258
315259 block_size = 256
316- # Single-pointer signature — see the comment on ``add_one_inplace_kernel``.
317260 src = triton .compiler .ASTSource (
318261 fn = add_one_inplace_kernel ,
319262 signature = {
@@ -332,14 +275,9 @@ def add_one_inplace_aot_impl(
332275 launch_params .block_x = compiled_kernel .metadata .num_warps * 32
333276 launch_params .shared_mem = compiled_kernel .metadata .shared
334277
335- # See the matching note on the non-in-place ``add_plugin_aot_impl``:
336- # Triton 3.x emits two trailing ``.param .u64 .ptr`` slots for the
337- # global/profile scratch buffers, and TRT's AOT path needs them zeroed
338- # explicitly via ``extra_args`` so the kernel doesn't read stale state.
339- extra_args = trtp .SymIntExprs (1 + 4 )
340- extra_args [0 ] = trtp .SymInt32 (N )
341- for _i in range (1 , 5 ):
342- extra_args [_i ] = trtp .SymInt32 (0 )
278+ extra_args = torch_tensorrt .dynamo .conversion .plugins .make_aot_extra_args (
279+ [trtp .SymInt32 (N )], compiled_kernel = compiled_kernel
280+ )
343281
344282 return (
345283 compiled_kernel .metadata .name ,
@@ -376,10 +314,6 @@ def forward(self, X: torch.Tensor) -> torch.Tensor:
376314
377315
378316class MyInplaceModel (torch .nn .Module ):
379- """Drives the in-place plugin. The op mutates ``X`` in place; the returned
380- tensor carries the post-mutation value (a clone, only to satisfy
381- torch.library's no-alias rule)."""
382-
383317 def forward (self , X : torch .Tensor ) -> torch .Tensor :
384318 return torch .ops .my .add_one_inplace .default (X )
385319
@@ -416,18 +350,8 @@ def forward(self, X: torch.Tensor) -> torch.Tensor:
416350 # In-place plugin demo
417351 # ---------------------
418352 #
419- # The standard "compile once, run many times" comparison pattern doesn't
420- # work for an in-place op because each call mutates the input — running
421- # eager and TRT on the same buffer double-applies the mutation. We work
422- # off a base tensor and clone for each call instead.
423- #
424- # Three things to verify, beyond "it ran":
425- # 1. The compiled module contains a TRT engine (not a PyTorch fallback —
426- # a regression here would silently pass on value because the eager
427- # kernel mutates the input the same way).
428- # 2. The engine's return value matches the expected post-mutation tensor.
429- # 3. The user's input buffer was mutated in place by the engine — the
430- # actual reason to use aliased plugin I/O in the first place.
353+ # In-place ops mutate their input, so eager and TRT must run on separate
354+ # cloned buffers; otherwise each comparison double-applies the mutation.
431355
432356 print ("\n In-place plugin demo:" )
433357 inplace_model = MyInplaceModel ().to ("cuda" ).eval ()
@@ -452,22 +376,18 @@ def forward(self, X: torch.Tensor) -> torch.Tensor:
452376 if isinstance (m , (PythonTorchTensorRTModule , TorchTensorRTModule ))
453377 ]
454378 assert engine_submodules , (
455- "Expected a TRT engine submodule for the in-place plugin path, but the"
456- " compiled module is pure PyTorch — check that the un-functionalize"
457- " lowering pass restored the mutating op before partitioning. Graph:\n "
458- f"{ model_trt_inplace .graph } "
379+ "Expected a TRT engine submodule for the in-place plugin path; got a"
380+ f" pure-PyTorch fallback. Graph:\n { model_trt_inplace .graph } "
459381 )
460382 print (f" TRT engine submodule(s) present: { len (engine_submodules )} " )
461383
462384 with torch .no_grad ():
463385 trt_input = base .clone ()
464386 trt_out = model_trt_inplace (trt_input )
465387 assert torch .allclose (trt_out , expected_post ), "TRT output mismatch"
466- assert torch .allclose (trt_input , expected_post ), (
467- "Engine did not mutate the input buffer — aliased plugin I/O is"
468- " not active. Check that PreviewFeature.ALIASED_PLUGIN_IO_10_03"
469- " was enabled and that the descriptor emitted X.aliased()."
470- )
388+ assert torch .allclose (
389+ trt_input , expected_post
390+ ), "Engine did not mutate the input buffer — aliased plugin I/O is not active."
471391
472392 print (" Output matches expected post-mutation value." )
473393 print (" Input buffer was mutated in place by the TRT engine." )
0 commit comments