4545 pre_export_lowering ,
4646)
4747from torch_tensorrt .dynamo .lowering ._buffer_lifting import (
48+ aliased_input_bindings ,
49+ assert_no_kv_alias_markers_survived ,
50+ assert_predicted_kv_aliased ,
51+ hide_copyback_outputs ,
4852 inline_lifted_buffers_into_gm ,
4953 lift_mutated_buffers ,
5054)
@@ -815,7 +819,11 @@ def compile(
815819 # prerequisite for IKVCacheUpdateLayer / aliased I/O to fire on a
816820 # module-held cache). Returns a fresh GraphModule whose forward signature
817821 # reflects the new placeholders.
818- gm , lifted_buffers = lift_mutated_buffers (gm )
822+ gm , lifted_buffers = lift_mutated_buffers (gm , settings )
823+ _copyback_mutation_buffers = gm .meta .get ("_copyback_mutation_buffers" , [])
824+ _copyback_bindings = gm .meta .get ("_copyback_bindings" , [])
825+ _predicted_kv_bindings = gm .meta .get ("_predicted_kv_bindings" , [])
826+ _no_kv_alias_writes = gm .meta .get ("_no_kv_alias_writes" , [])
819827 if lifted_buffers :
820828 # Append each lifted buffer as an engine input AFTER the user inputs.
821829 # Buffer tensors live on the gm's state; prepare an Input spec for
@@ -835,6 +843,12 @@ def compile(
835843 logger .debug (f"CPU memory usage after post_lowering: { get_cpu_memory_usage ()} MB" )
836844 logger .debug ("Lowered Input graph: " + str (gm .graph ))
837845
846+ # The marker is what keeps a re-routed write out of the engine's aliased_io, and
847+ # it rides on node.meta through every pass above. Read it back before conversion
848+ # so a pass that dropped it is named here rather than surfacing as a module that
849+ # raises on every call.
850+ assert_no_kv_alias_markers_survived (gm , _no_kv_alias_writes )
851+
838852 # Move the weights in the state_dict to CPU
839853 if offload_module_to_cpu :
840854 deallocate_module (gm )
@@ -856,6 +870,23 @@ def compile(
856870 engine_cache ,
857871 graph_signature = exported_program .graph_signature ,
858872 )
873+ if _copyback_mutation_buffers :
874+ trt_gm .meta ["_copyback_mutation_buffers" ] = _copyback_mutation_buffers
875+ # Ground-truth check on both directions of the classification: every write lift
876+ # called KV (engine-aliased, so its copy_ was dropped) must appear in a compiled
877+ # engine's aliased_io or its write-back is silently lost, and no write lift
878+ # called copy-back may appear there or the trailing output it added is one the
879+ # runtime truncates while the graph still reads it. Fail loudly for either. A
880+ # dryrun returns before conversion, so there is no ground truth to check against.
881+ assert_predicted_kv_aliased (
882+ aliased_input_bindings (
883+ getattr (sub , "aliased_io" , None ) for _name , sub in trt_gm .named_children ()
884+ ),
885+ _predicted_kv_bindings ,
886+ settings ,
887+ engines_built = not settings .dryrun ,
888+ copyback_bindings = _copyback_bindings ,
889+ )
859890 if lifted_buffers :
860891 # Inline buffers into the compiled gm as get_attr nodes + registered
861892 # buffers. The resulting gm's forward takes only user inputs; buffers
@@ -864,6 +895,12 @@ def compile(
864895 # serializable by torch_tensorrt.save / torch.export (no external
865896 # Python wrapper that would be lost on a round-trip).
866897 trt_gm = inline_lifted_buffers_into_gm (trt_gm , lifted_buffers )
898+ # A copy-back value stays on the output node, where the exporters read it and
899+ # where dead-code elimination cannot reach it, but nothing between here and the
900+ # ExecuTorch runtime writes it into the buffer. Returning it would report a
901+ # mutation the caller cannot act on, so the compiled module keeps the arity of
902+ # the model it was compiled from.
903+ hide_copyback_outputs (trt_gm , len (_copyback_mutation_buffers ))
867904 return trt_gm
868905
869906
@@ -1803,6 +1840,12 @@ def convert_exported_program_to_serialized_trt_engine(
18031840 automatically; this lower-level entry point exposes the same machinery
18041841 for callers that want to manage the bindings themselves.
18051842
1843+ Only writes the engine can alias in place are supported here. A mutation the
1844+ engine cannot alias needs its new value copied back into the buffer after the
1845+ call, and this entry point reports neither which output carries which buffer nor
1846+ performs the copy, so it raises rather than returning an engine whose buffer
1847+ would never update. Use :func:`torch_tensorrt.dynamo.compile` for those models.
1848+
18061849 Arguments:
18071850 exported_program (torch.export.ExportedProgram): Source module, running torch.export on a ``torch.nn.Module``
18081851 inputs (Optional[Sequence[Sequence[Any]]]): List of specifications of input shape, dtype and memory layout for inputs to the module. This argument is required. Input Sizes can be specified as torch sizes, tuples or lists. dtypes can be specified using
@@ -1886,6 +1929,11 @@ def convert_exported_program_to_serialized_trt_engine(
18861929 **kwargs: Any,
18871930 Returns:
18881931 bytes: Serialized TensorRT engine, can either be saved to a file or deserialized via TensorRT APIs
1932+ Raises:
1933+ RuntimeError: if ``lift_mutable_buffers=True`` and the model mutates a buffer
1934+ the engine cannot alias in place, or if a write predicted to be aliased is
1935+ absent from the built engine's ``aliased_io``. Either way the buffer would
1936+ silently never update.
18891937 """
18901938
18911939 if kwargs .get ("debug" , False ):
@@ -2074,8 +2122,26 @@ def convert_exported_program_to_serialized_trt_engine(
20742122 # resulting bindings at runtime — they are appended after the user inputs
20752123 # in the order returned here.
20762124 lifted_buffers : List [Tuple [str , str , torch .Tensor ]] = []
2125+ predicted_kv_bindings : List [str ] = []
20772126 if lift_mutable_buffers :
2078- gm , lifted_buffers = lift_mutated_buffers (gm )
2127+ gm , lifted_buffers = lift_mutated_buffers (gm , settings )
2128+ # Read before lowering: `gm` is replaced below and the meta does not follow it.
2129+ predicted_kv_bindings = gm .meta .get ("_predicted_kv_bindings" , [])
2130+ # A write the engine cannot alias in place is classified as copy-back: its
2131+ # new value is appended as an extra engine output, and whatever loads the
2132+ # serialized program copies that output into the buffer afterwards. This
2133+ # entry point returns engine bytes and no program, so it neither reports
2134+ # which output carries which buffer nor gives the copy anywhere to be
2135+ # declared, and the caller has no way to complete it.
2136+ copyback_buffers = gm .meta .get ("_copyback_mutation_buffers" , [])
2137+ if copyback_buffers :
2138+ raise RuntimeError (
2139+ "convert_exported_program_to_serialized_trt_engine cannot express the "
2140+ f"write-back for mutable buffer(s) { copyback_buffers } : the engine "
2141+ "cannot alias them in place, so the buffers would never update. Use "
2142+ "torch_tensorrt.dynamo.compile and save the result, which declares "
2143+ "the buffer mutation for the runtime to apply."
2144+ )
20792145 if lifted_buffers :
20802146 buffer_tensors = [t for _ , _ , t in lifted_buffers ]
20812147 buffer_inputs = prepare_inputs (buffer_tensors )
@@ -2176,6 +2242,12 @@ def convert_exported_program_to_serialized_trt_engine(
21762242 )
21772243 raise RuntimeError (f"While interpreting the module got an error: { e } " ) from e
21782244
2245+ assert_predicted_kv_aliased (
2246+ aliased_input_bindings ([interpreter_result .aliased_io ]),
2247+ predicted_kv_bindings ,
2248+ settings ,
2249+ )
2250+
21792251 serialized_engine : bytes = interpreter_result .serialized_engine
21802252 return serialized_engine
21812253
0 commit comments