This document is relevant for: Trn2, Trn3

Aliasing Output Rewrite Pass#

This pass runs as part of the FX passes stage of the compilation pipeline, between FX tracing and HLO lowering. It detects in-place buffer mutations and produces aliasing metadata for the XLA/HLO backend.

Problem Statement#

The XLA/HLO backend used by Neuron allocates separate memory buffers for every input and output of a compiled graph. When a model mutates an input buffer in-place (e.g. updating KV-cache state), the runtime must know that a particular output is the same buffer as a particular input so it can reuse the allocation instead of copying.

HLO expresses this through input_output_alias entries. The aliasing pass is responsible for analyzing the FX graph, detecting every case where an output derives from a mutated input, and producing the io_map metadata that downstream HLO conversion uses to emit those alias entries.

Sources of Aliasing#

The pass handles four categories:

  1. In-place torch operations β€” x.add_(y), operator.setitem, etc.

  2. NKI kernel mutations β€” wrap_nki HOP nodes whose operand_output_aliases dict declares which kernel inputs are written back to kernel outputs.

  3. Custom ops with mutable arguments β€” ops whose schema contains Tensor(a!) annotations (alias_info.is_write=True).

  4. Pass-through / view aliases β€” an output that is a direct reference to an input, or reaches one through a chain of view ops (view, reshape, transpose, …).

Pipeline Position#

DeviceRewriterPass
     β”‚
     β–Ό
AliasingOutputRewritePass   ◄── this pass
     β”‚
     β–Ό
InPlaceToOutOfPlacePass
     β”‚
     β–Ό
NkiKernelWriteBackendConfigPass
     β”‚
     β–Ό
CollectiveReplicaGroupsPass

The aliasing pass runs before the in-place-to-out-of-place pass. It analyzes the graph while in-place operations are still present, records which inputs are mutated, and stashes root_input metadata on mutating nodes so the subsequent InPlaceToOutOfPlacePass can name replacement nodes correctly.

Algorithm Design#

Overview#

The pass performs a single linear scan of the graph followed by an output rewrite step. It produces an io_map dict ({output_idx: input_idx}) and an original_output_count integer that are returned as metadata alongside the transformed GraphModule.

Phase 1: Collect Input Placeholders#

All placeholder nodes are collected in order. Their positional index is significant because io_map uses it to pair outputs with inputs. Dynamo’s mangled names (e.g. L_self_modules_layer_parameters_weight_) are cleaned up for debug logging.

Phase 2: Build the Alias Chain#

A single forward pass over every node classifies it into one of:

  • Mutating op β€” the mutated tensor is traced back through the alias chain to its root input placeholder. The node is recorded in mutated_inputs and added to alias_chain so downstream nodes can be traced back too. node.meta["root_input"] is set for the InPlaceToOutOfPlacePass.

  • Aliasing op β€” view/reshape/transpose/etc. The node is added to alias_chain pointing at its source tensor (first argument).

  • Custom op with mutations β€” schema arguments with alias_info.is_write=True are resolved to their root input placeholders.

  • NKI kernel with mutations β€” operand_output_aliases entries are resolved. For tuple-returning kernels, getitem nodes are found or created and registered in alias_chain.

Phase 3: Extend Outputs and Build io_map#

Mutated inputs that are not already represented in the output tuple are appended as new output entries. For NKI kernels, the getitem node carrying the post-mutation value is preferred over the raw input placeholder.

A second scan over the (now extended) output tuple detects pass-through and view-alias relationships that allow buffer reuse even without mutations. Slice views are excluded because they change shape and cannot alias the full input buffer in HLO.

Data Structures#

alias_chain : dict[Node, Node]
    Maps each node to the node it aliases.  Following the chain
    eventually reaches a graph-level input placeholder.

mutated_inputs : dict[int, Node]
    Maps input placeholder index β†’ placeholder node for every
    input that is mutated in-place.

io_map : dict[int, int]
    Maps output index β†’ input index.  Forwarded to HLO conversion
    which writes input_output_alias entries.

Algorithm Flow#

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Collect placeholdersβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  For each node:      β”‚
β”‚  β€’ Mutating op?      │──▢ Record in mutated_inputs + alias_chain
β”‚  β€’ Aliasing op?      │──▢ Record in alias_chain
β”‚  β€’ Custom op w/mut?  │──▢ Resolve schema args β†’ mutated_inputs
β”‚  β€’ NKI kernel w/mut? │──▢ Resolve aliases β†’ mutated_inputs + alias_chain
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Extend output tuple β”‚
β”‚  with mutated inputs β”‚
β”‚  not already present β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Scan outputs for    β”‚
β”‚  pass-through/view   β”‚
β”‚  aliases β†’ io_map    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Return (gm, {       β”‚
β”‚    io_map,            β”‚
β”‚    original_output_   β”‚
β”‚    count              β”‚
β”‚  })                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Example#

Before the pass, a graph that mutates kv_cache in-place:

placeholder  x
placeholder  kv_cache
call_method  add_        (kv_cache, x)
output       (result,)

After the pass, kv_cache is appended to the output tuple and io_map records the relationship:

placeholder  x
placeholder  kv_cache
call_method  add_        (kv_cache, x)
output       (result, kv_cache)

io_map = {1: 1}   # output[1] aliases input[1]

The InPlaceToOutOfPlacePass then rewrites add_ β†’ add and updates references, but the io_map remains valid because the output position is stable.

Module-Level Constants#

Three sets classify operations for the analysis:

  • ALIASING_METHODS β€” method-call names that produce views (view, reshape, transpose, permute, …).

  • ALIASING_ATEN_OPS β€” ATen-level equivalents seen after torch.export lowering (includes _unsafe_view, view_as, reshape_as, …).

  • INPLACE_METHODS β€” in-place method names used for mutation detection (add_, copy_, scatter_, …).

Source Location#

vllm_neuron/fx_passes/aliasing_pass.py

This document is relevant for: Trn2, Trn3