diff --git a/.github/workflows/windows-web-ci-workflow.yml b/.github/workflows/windows-web-ci-workflow.yml index 79ca29fe21480..24be32171cc6f 100644 --- a/.github/workflows/windows-web-ci-workflow.yml +++ b/.github/workflows/windows-web-ci-workflow.yml @@ -158,7 +158,6 @@ jobs: # WebGPU EP tests - name: Run ort-web tests - WebGPU EP - continue-on-error: true shell: cmd run: | mkdir ${{ runner.temp }}\web\test\07 diff --git a/.github/workflows/windows_webgpu.yml b/.github/workflows/windows_webgpu.yml index af528a912b4b6..77a99473a47d9 100644 --- a/.github/workflows/windows_webgpu.yml +++ b/.github/workflows/windows_webgpu.yml @@ -161,7 +161,6 @@ jobs: run: python tools\ValidateNativeDelegateAttributes.py shell: cmd working-directory: ${{ github.workspace }}\csharp - continue-on-error: true webgpu_test_x64_RelWithDebInfo: name: webgpu_test_x64_RelWithDebInfo (${{ matrix.vcpkg_option }}) diff --git a/cmake/external/cuda_configuration.cmake b/cmake/external/cuda_configuration.cmake index 47d6eef45302c..a7cf51ebbbd0a 100644 --- a/cmake/external/cuda_configuration.cmake +++ b/cmake/external/cuda_configuration.cmake @@ -121,16 +121,19 @@ macro(setup_cuda_architectures) message(WARNING "Detecting native CUDA compute capability - fail") message(WARNING "CUDA compute capability detection failed, compiling for all optimized architectures") unset(CMAKE_CUDA_ARCHITECTURES) + unset(CMAKE_CUDA_ARCHITECTURES CACHE) else() message(STATUS "Detecting native CUDA compute capability - done") set(CMAKE_CUDA_ARCHITECTURES "${CUDA_ARCH_OUTPUT}") endif() elseif(CMAKE_CUDA_ARCHITECTURES STREQUAL "all") unset(CMAKE_CUDA_ARCHITECTURES) + unset(CMAKE_CUDA_ARCHITECTURES CACHE) message(STATUS "Setting CMAKE_CUDA_ARCHITECTURES to all enables a list of architectures OnnxRuntime optimized for, " "not all architectures CUDA compiler supports.") elseif(CMAKE_CUDA_ARCHITECTURES STREQUAL "all-major") unset(CMAKE_CUDA_ARCHITECTURES) + unset(CMAKE_CUDA_ARCHITECTURES CACHE) message( STATUS "Setting CMAKE_CUDA_ARCHITECTURES to all-major enables a list of architectures OnnxRuntime optimized for, " "not all major architectures CUDA compiler supports.") diff --git a/cmake/onnxruntime_providers_cuda_plugin.cmake b/cmake/onnxruntime_providers_cuda_plugin.cmake index 87d3a3af13975..8450471b65cd9 100644 --- a/cmake/onnxruntime_providers_cuda_plugin.cmake +++ b/cmake/onnxruntime_providers_cuda_plugin.cmake @@ -486,6 +486,12 @@ endif() message(STATUS "CUDA Plugin EP: cuDNN include: ${CUDA_PLUGIN_CUDNN_INCLUDE_DIR}") message(STATUS "CUDA Plugin EP: cuDNN runtime library: ${CUDA_PLUGIN_CUDNN_LIBRARY}") +# With cuDNN in /usr drop CUDA_PLUGIN_CUDNN_INCLUDE_DIR; we do not want +# "-isystem /usr/include" ahead of libstdc++, breaking #include_next mechanism. +if(CUDA_PLUGIN_CUDNN_INCLUDE_DIR IN_LIST CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES) + set(CUDA_PLUGIN_CUDNN_INCLUDE_DIR "") +endif() + # Include directories — only public ORT headers + CUDA toolkit + cuDNN + internal headers for adapter target_include_directories(onnxruntime_providers_cuda_plugin PRIVATE ${REPO_ROOT}/include diff --git a/docs/ContribOperators.md b/docs/ContribOperators.md index d45736fe9e6f1..2589e5878a808 100644 --- a/docs/ContribOperators.md +++ b/docs/ContribOperators.md @@ -133,6 +133,7 @@ Do not modify directly.* * com.microsoft.SparsePagedAttention * com.microsoft.SparseToDenseMatMul * com.microsoft.SpeculativePrefixCommit + * com.microsoft.SwiGLU * com.microsoft.Tokenizer * com.microsoft.TorchEmbedding * com.microsoft.TransposeMatMul @@ -8360,6 +8361,62 @@ This version of the operator has been available since version 1 of the 'com.micr +### **com.microsoft.SwiGLU** + + Clamped SwiGLU, the gated activation of a feed-forward or MoE expert block: + G = clamp(gate, max=limit) + L = clamp(up, min=-limit, max=limit) + output = G * Sigmoid(activation_alpha * G) * (L + activation_beta) + with the arithmetic done in float regardless of T, widened to double for the product when `activation_alpha * G` is below -87 so that a negative tail is not lost to float exp underflow. A `limit` of zero or less disables both clamps. + + The result is rounded to T once, after the multiply by (L + activation_beta). With `round_activation` set, G * Sigmoid(activation_alpha * G) is first rounded to T, as a QuickGelu node followed by a Mul in T would, which keeps a fused QuickGelu + Mul bit-identical to the graph it replaces. + + This uses the same activation_alpha / activation_beta formula and positive-limit clamping rule as MoE and QMoE. The no-clamp sentinel differs: here any `limit` of zero or less disables clamping, while MoE/QMoE use infinity when `swiglu_limit` is omitted and clamp finite values, including an explicit zero. + With `up` supplied and the default `limit` and `activation_beta`, it equals the ONNX SwiGLU operator (opset 28) with `alpha` = `activation_alpha`. + + `up` is optional. When it is omitted, `gate` carries both halves of one `[.., 2 * inter]` projection -- gate first, then up -- and the split is done internally, so a fused sibling GEMM needs no `Split` node. + +#### Version + +This version of the operator has been available since version 1 of the 'com.microsoft' operator set. + +#### Attributes + +
+
activation_alpha : float
+
Multiplier inside the sigmoid.
+
activation_beta : float
+
Value added to the linear half. A zero value leaves it unchanged, including a negative zero.
+
limit : float
+
Clamp applied to both halves; zero or less disables it.
+
round_activation : int
+
If non-zero, round G * Sigmoid(activation_alpha * G) to T before multiplying by (L + activation_beta). Zero rounds only the final result.
+
+ +#### Inputs (1 - 2) + +
+
gate : T
+
Gate half of the projection, or both halves concatenated.
+
up (optional) : T
+
Up half of the projection, same shape as gate.
+
+ +#### Outputs + +
+
output : T
+
Activated tensor, shaped like one half of the projection.
+
+ +#### Type Constraints + +
+
T : tensor(float16), tensor(bfloat16), tensor(float)
+
Constrain the activation type to float tensors.
+
+ + ### **com.microsoft.Tokenizer** Tokenizer divides each string in X into a vector of strings along the last axis. Allowed input shapes are [C] and [N, C]. diff --git a/docs/OperatorKernels.md b/docs/OperatorKernels.md index 21f0365fc90a2..cc0442284693a 100644 --- a/docs/OperatorKernels.md +++ b/docs/OperatorKernels.md @@ -703,6 +703,9 @@ The **OpSet Version** column uses the following notation: |||14|**T** = tensor(double), tensor(float), tensor(float16)
**U** = tensor(double), tensor(float), tensor(float16)| |||[9, 13]|**T** = tensor(double), tensor(float), tensor(float16)| |||[7, 8]|**T** = tensor(double), tensor(float), tensor(float16)| +|BitShift|*in* X:**T**
*in* Y:**T**
*out* Z:**T**|28+|**T** = tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)| +|||[11, 27]|**T** = tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)| +|BitwiseXor|*in* A:**T**
*in* B:**T**
*out* C:**T**|18+|**T** = tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)| |Cast|*in* input:**T1**
*out* output:**T2**|28+|**T1** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(float4e2m1), tensor(float8e4m3fn), tensor(float8e5m2), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)
**T2** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(float4e2m1), tensor(float8e4m3fn), tensor(float8e5m2), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)| |||[25, 27]|**T1** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(float4e2m1), tensor(float8e4m3fn), tensor(float8e5m2), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)
**T2** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(float4e2m1), tensor(float8e4m3fn), tensor(float8e5m2), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)| |||[23, 24]|**T1** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(float4e2m1), tensor(float8e4m3fn), tensor(float8e5m2), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)
**T2** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(float4e2m1), tensor(float8e4m3fn), tensor(float8e5m2), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)| @@ -1179,6 +1182,7 @@ The **OpSet Version** column uses the following notation: |SparseAttentionIndexer|*in* query:**T**
*in* key:**T**
*in* query_norm_weight:**T**
*in* key_norm_weight:**T**
*in* cos_cache:**T**
*in* sin_cache:**T**
*in* mask:**TB**
*in* past_key:**T**
*in* gate:**T**
*in* position_bias:**T**
*in* head_weights:**T**
*in* position_ids:**I**
*in* past_sequence_length:**M**
*in* past_proj_buffer:**T**
*out* selected_indices:**M**
*out* present_key:**T**
*out* present_proj_buffer:**T**|1+|**I** = tensor(int64)
**M** = tensor(int32)
**T** = tensor(bfloat16), tensor(float), tensor(float16)
**TB** = tensor(int64)| |SparsePagedAttention|*in* query:**T**
*in* key:**T**
*in* value:**T**
*in* key_cache:**T_CACHE**
*in* value_cache:**T_CACHE**
*in* cumulative_sequence_length:**S**
*in* past_seqlens:**S**
*in* block_table:**S**
*in* slot_mapping:**S**
*in* selected_indices:**S**
*in* selected_counts:**S**
*in* auxiliary_key:**T**
*in* auxiliary_value:**T**
*in* auxiliary_lengths:**S**
*in* cos_cache:**T**
*in* sin_cache:**T**
*in* head_sink:**T**
*in* q_norm_weight:**T**
*in* k_norm_weight:**T**
*in* k_scale:**T_KV_SCALE**
*in* v_scale:**T_KV_SCALE**
*in* attention_metadata:**S**
*out* output:**T**
*out* key_cache_out:**T_CACHE**
*out* value_cache_out:**T_CACHE**|1+|**S** = tensor(int32)
**T** = tensor(bfloat16), tensor(float16)
**T_CACHE** = tensor(bfloat16), tensor(float16), tensor(int8)
**T_KV_SCALE** = tensor(float)| |SpeculativePrefixCommit|*in* next_tokens:**I64**
*in* input_ids:**I64**
*in* status:**I32**
*in* ctl:**I64**
*in* ctx_commit:**I64**
*in* prefixes:**I64**
*in* ptrs:**I64**
*out* head_c:**I32**
*out* updated_ctl:**I64**
*out* updated_ctx_commit:**I64**|1+|**I32** = tensor(int32)
**I64** = tensor(int64)| +|SwiGLU|*in* gate:**T**
*in* up:**T**
*out* output:**T**|1+|**T** = tensor(bfloat16), tensor(float), tensor(float16)| |TransposeMatMul|*in* A:**T**
*in* B:**T**
*out* Y:**T**|1+|**T** = tensor(bfloat16), tensor(double), tensor(float), tensor(float16)| |Trilu|*in* X:**T**
*in* k:**tensor(int64)**
*out* Y:**T**|1+|**T** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)| |UnfoldTensor|*in* input:**T**
*out* output:**T**|1+|**T** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(string), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)| diff --git a/docs/Pattern_Rewriter.md b/docs/Pattern_Rewriter.md index 333cfe6eadfd7..464bee2871e0c 100644 --- a/docs/Pattern_Rewriter.md +++ b/docs/Pattern_Rewriter.md @@ -1245,6 +1245,75 @@ before entering the transformer or after an error. Callers must not concurrently graph. Options and compiled rules are immutable; match state and statistics are per call. +### Restricting rewriting to a selected region + +The scoped overload accepts an explicit set of live node IDs: + +```cpp +InlinedHashSet region(selected_nodes.begin(), selected_nodes.end()); +ORT_RETURN_IF_ERROR(rewriter.Run(graph, statistics, region)); +ORT_RETURN_IF_ERROR(graph.Resolve()); +``` + +Only roots in the region are visited. Every node consumed by a match must also +belong to the region; a match crossing its boundary is rejected before either +rule callback runs. Inputs may come from outside the region, and returned values +may feed outside consumers. Their `NodeArg` identities are preserved, although +boundary edges are rewired to new producers. Existing deletion-boundary and EP +checks still apply. Region-scope and deletion-boundary rejections both report +`FailureStage::Boundary`; the diagnostic reason distinguishes them. + +Each successful rewrite removes its source IDs from `region` and adds all of its +replacement IDs. New nodes participate in the same ordered, iterative matching; +unmatched region nodes remain in the returned set. This lets a capability +callback reconcile its node list without rescanning unrelated nodes. The +overload neither selects an EP nor performs kernel lookup, placement, or resource +accounting. Callers must still validate those contracts for the resulting region. + +An empty region does no work. Invalid input IDs return an error before matching +or mutation. Match-only mode leaves the set unchanged. Failures do not roll back +earlier rewrites: the set reflects completed replacements, not a transaction. +An ordinary staging failure leaves that match's source IDs in the set and does +not add its staged replacement IDs. +As with unscoped `Run`, resolve the graph explicitly before later consumers. + +### Function expansion and capability application + +The rewriter does not decide when ORT expands functions or assigns nodes to an +EP. A pattern can match a function-call node, but this does not inspect or +rewrite its model-local function definition. + +Capability discovery and rewrite application are separate operations. In +ordinary ONNX initialization, AOT function inlining can query `GetCapability` +before normal partitioning. Reporting a capability can preserve a function +call for later partitioning; it is not permission to run its optimization +callback during that query. Discovery must be read-only and repeatable. +Normal partitioning checks availability before invoking the selected +capability's optimization callback and placing its updated node set. + +`PatternCapabilityTests.PreservesFunctionUntilAcceptedRewrite` and +`PatternCapabilityTests.ExpandsUnclaimedFunctionAndRediscoversBody` exercise +these paths with a test-only CPU EP and existing CPU kernels: + +| Function-call candidate | Expected integration behavior | +|---|---| +| Recognized with an executable rewrite | Discovery leaves the call intact. After acceptance, scoped rewriting replaces the call and updates the parent capability before placement. | +| Not recognized | Existing function expansion exposes the body. A later capability query discovers its Transpose chain and returns a fresh region for rewriting. | + +Both tests run with AOT inlining enabled and disabled. With AOT disabled, the +unclaimed call expands after a partitioning pass, and partitioning repeats. +No borrowed matches or capabilities from the function-call graph are retained +for matching the expanded body. Assertions cover the original call's removal, +callback and rewrite counts, final node types and CPU assignment, output names, +and numerical equality with ordinary CPU execution. + +This is a fixed-function integration probe, not general function-equivalence +checking or a production registration interface. Recognizing only a function +name is insufficient for arbitrary model-local definitions. The probe does not +change expansion policy or cross-EP priority, compare whole-function and +expanded-body placement alternatives, or establish ORT-format export, +resource-accounting, or general N-to-M placement contracts. + ### Resolved metadata and mutation boundaries Call `Graph::Resolve` after constructing a source graph and before `Run` or @@ -1290,9 +1359,10 @@ remain unsupported; identity elimination is a separate transformation. `Options::max_iterations` is a positive upper bound, defaulting to 10. Each iteration processes a fixed candidate batch, initially all live nodes in -topological order. Replacements and affected existing nodes are queued for the -next batch, never appended to the current batch. An existing later candidate -can still match an earlier replacement in the same batch. Processing stops +topological order. For scoped `Run`, the initial batch contains only region nodes. +Replacements and affected existing nodes are queued for the next batch, never +appended to the current batch. An existing later candidate can still match an +earlier replacement in the same batch. Processing stops when a batch performs no rewrites, there are no pending candidates, or the bound is reached. Match-only mode processes exactly one batch without mutation. diff --git a/docs/design/Extensible_Runtime_Fusion.md b/docs/design/Extensible_Runtime_Fusion.md new file mode 100644 index 0000000000000..1089b5a596e56 --- /dev/null +++ b/docs/design/Extensible_Runtime_Fusion.md @@ -0,0 +1,987 @@ +# EP-registered runtime fusion for canonical ONNX + +Status: **engineering design for review, not approved for implementation** (revised October 28, 2026). +Tracks [#33403](https://github.com/microsoft/onnxruntime/issues/33403). +No native build, dependency installation, GPU allocation, model execution, or runtime probe was +performed for this design. Test names, commands, numerical gates, and performance budgets below are +proposed acceptance criteria, not results. Maintainer review and acceptance are still required. + +## 1. Relationship to the original proposal + +This document refines the +[pinned Extensible Runtime Fusion proposal](https://github.com/microsoft/onnxruntime/blob/587ef1bf3467935b083740a52ac8fcba3785b2a6/docs/design/Extensible_Runtime_Fusion.md). +That file is absent from the default-branch base used here; the proposal branch is a reference, +not an implementation dependency or a branch to merge. + +The north star remains: + +> Mobius exports canonical standard ONNX. Olive owns quantization policy. Compiler EPs retain +> visibility and compile their regions. Implementation packages own optional kernel-fusion patterns +> and adapters. ORT owns generic registration, validation, ownership enforcement, and rewriting. + +The canonical artifact is authoritative; a saved optimized model is a target-specific derivative. +Existing contrib artifacts and private transformer registration remain supported. Kernel-internal +improvements behind an unchanged operator contract already need no re-export. The additional proof +required here is a **matcher/adapter package update with identical canonical artifact hashes**. + +### Scope + +Start with existing CUDA kernels: FP16 dense dynamic-cache GQA and stateful convolution, then a +separately gated whole-`Scan` recurrence milestone. No new EP, kernel, quantization format, +Flash-Next/MTP support, universal optimizer migration, automatic offload/defusion search, or +transparent retry after state mutation. CPU and native WebGPU are architectural extensibility +requirements, not first-release implementations. Compiler EPs need not adopt this mechanism. +Qwen3.8-27B is a pilot, not a model-specific runtime API. + +### Revised integration direction + +Fusion patterns register on the EP side. Each EP owns their priority order and exposes eligible +execution candidates through the capability path. ORT chooses compatible candidates in that order, +subject to ownership, semantic, kernel, and, when enabled, memory-feasibility checks. There is no +per-session speed competition and no separate post-partition package-selection pipeline. + +Separate candidate description from application so memory estimation can be connected without +rerunning a mutating matcher. Reuse `GetCapability`, `ComputeCapability`, and the capability +optimization application model first; define only the missing adapters. Independent package delivery +remains a goal, not a reason to freeze a second graph-inspection ABI or require a competing EP. +These are the proposed design direction, not a claim of maintainer approval or existing support. + +## 2. Evidence ledger and baseline discipline + +All runtime claims below refer to **R**, unless a difference is explicitly noted. Links are immutable +research references, not claims that proposed interfaces exist. + +| Key | Exact source | +|---|---| +| R | [ORT main `1988019b6e23ac4def4628cddd603c1bfb504fe8`](https://github.com/microsoft/onnxruntime/tree/1988019b6e23ac4def4628cddd603c1bfb504fe8) | +| M | [Mobius main `b1e9c8e9dec1dfa206183f957e77d07cfb734df2`](https://github.com/onnxruntime/mobius/tree/b1e9c8e9dec1dfa206183f957e77d07cfb734df2) | +| G | [GenAI main `991e3560e63b8a26806d4ec7d702c04e75fb6e51`](https://github.com/microsoft/onnxruntime-genai/tree/991e3560e63b8a26806d4ec7d702c04e75fb6e51) | +| F | [Fusion proposal `587ef1bf3467935b083740a52ac8fcba3785b2a6`](https://github.com/microsoft/onnxruntime/blob/587ef1bf3467935b083740a52ac8fcba3785b2a6/docs/design/Extensible_Runtime_Fusion.md) | +| P | [Placement proposal `2f96ad081fdbe1d76a2a731262a998b3cd4c9c0d`](https://github.com/microsoft/onnxruntime/blob/2f96ad081fdbe1d76a2a731262a998b3cd4c9c0d/docs/annotated_partitioning/PartitioningWithAnnotationsAndMemoryConstraints.md), refreshed October 26 for the proposed recipe/planning integration only | +| W | [Composable rewrite rules, gramalingam/onnxruntime#16 at `6d707086e04fe287f162b9dc2baf58b935d8e2bc`](https://github.com/gramalingam/onnxruntime/blob/6d707086e04fe287f162b9dc2baf58b935d8e2bc/docs/Pattern_Rewriter.md); optional matcher implementation, not an ORT-main dependency | +| V | [OpenVINO plugin `7463f66e48d09fe5150af8c76520fbf75e08c490`](https://github.com/onnxruntime/onnxruntime-ep-openvino/tree/7463f66e48d09fe5150af8c76520fbf75e08c490); compiler-EP comparison only | + +The PR base is default-branch commit `327d11ab6c22e387acf793dd9de8caf406055ca9`. +Source comparison with R found no changes in the cited session hook, public API, registry selection, +Scan registration, or GQA/LinearAttention/GatedDeltaNet kernel files. Relevant differences are the +accountant's new `INT32_MAX` validation for the CUDA GQA total-sequence bound and updates in the GQA +workspace bounds/estimate helpers. These do not supply target re-estimation for arbitrary rewrites. +Do not combine P's proposed reconciliation with R and describe the result as implemented. +Any implementation PR must repeat the comparison against its actual base and publish its build pin. +The October 26 revision also inspected the PR head's `execution_provider.h:152-179`, +`compute_capability.h:27-44`, and `graph_partitioner.cc:1145-1181` for the capability integration below. +Refreshing P and W does not refresh R or turn either proposal into shipped runtime functionality. +V's `CreateEpFactories`/`ReleaseEpFactory` lifetime patterns are useful references. Its +GetCapability/Compile and AddNodesToFuse path owns compiled regions; it is not a public extension +that borrows existing CUDA kernels. Creating a competing EP solely to reach those kernels is rejected. + +### Current behavior versus missing mechanism + +| Evidence at R | Implication | +|---|---| +| [`inference_session.cc:1590-1602,1896-1952,2015-2087`](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/onnxruntime/core/session/inference_session.cc#L1896-L1952) | L1 precedes partition; mutation accounting is installed after partition; L2+ and casts repeat; copies follow. | +| [`graph_transformer_utils.cc:413-426,464-467`](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/onnxruntime/core/optimizer/graph_transformer_utils.cc#L413-L426), [`graph_transformer_mgr.cc:38-59`](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/onnxruntime/core/optimizer/graph_transformer_mgr.cc#L38-L59) | Repeated optimization loops must not independently reselect a capability's implementation. | +| [`execution_provider.h:152-179`](https://github.com/microsoft/onnxruntime/blob/3207aa12931ac9de054fdf0de183f7f036c01269/include/onnxruntime/core/framework/execution_provider.h#L152-L179) | `GetCapability` can return overlapping source-node sets; ORT decides assignment. The signature includes optimizer-registry and resource-accountant inputs. | +| [`compute_capability.h:27-44`](https://github.com/microsoft/onnxruntime/blob/3207aa12931ac9de054fdf0de183f7f036c01269/onnxruntime/core/framework/compute_capability.h#L27-L44), [`graph_partitioner.cc:1145-1181`](https://github.com/microsoft/onnxruntime/blob/3207aa12931ac9de054fdf0de183f7f036c01269/onnxruntime/core/framework/graph_partitioner.cc#L1145-L1181) | Child optimization callbacks can update an available parent capability before placement/fusion. This is an application hook, not a complete alternative-recipe planner. | +| [`group_query_attention_fusion.cc:413-440`](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/onnxruntime/core/optimizer/group_query_attention_fusion.cc#L413-L440) | Existing GQA fusion starts from contrib GQA, not canonical Attention. | +| [`attention_fusion.cc:1030-1046`](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/onnxruntime/core/optimizer/attention_fusion.cc#L1030-L1046), [`session_state.cc:242-310`](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/onnxruntime/core/framework/session_state.cc#L242-L310) | Rewrites can retain EP assignment; finalization later binds registered kernels. | +| [`inference_session.cc:1113-1129`](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/onnxruntime/core/session/inference_session.cc#L1113-L1129) | Private C++ registration is not an external binary contract. Preserve its existing enable behavior. | +| [`onnxruntime_c_api.h:6130-6653,8210-8244`](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/include/onnxruntime/core/session/onnxruntime_c_api.h#L6130-L6653) | Reuse public `OrtGraph`/`OrtNode`/`OrtValueInfo` inspection where possible; Model Editor alone does not supply the capability recipe/application contract. | +| [`onnxruntime_experimental_c_api.inc:21-37`](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/include/onnxruntime/core/session/onnxruntime_experimental_c_api.inc#L21-L37) | Experimental discovery exists, but no independently deployed EP-pattern registration interface is established here. | +| [`kernel_registry_manager.cc:60-104`](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/onnxruntime/core/framework/kernel_registry_manager.cc#L60-L104) | Custom registries have precedence; an operator name alone proves neither implementation nor eligibility. | + +## 3. Integration and graph lifecycle (D1) + +### EP-owned registration and capability discovery + +Each EP owns a registry of enabled fusion patterns and an explicit ordered pattern list. A pattern +can be EP-bundled or supplied by an optional independently updated package, but both enter the same +registry and selection path. EP options express permission and priority, not proof of eligibility. +Keep existing cross-EP priority; this design does not let an external package claim nodes on behalf +of an unrelated provider. + +During `GetCapability`, the EP matches the normalized graph and describes executable candidates. +A candidate identifies a source-node set and a prepared implementation recipe, not a second +complete model that ORT must execute to compare performance. Different patterns may cover the same +source nodes or emit different replacement DAGs. Standalone execution remains a candidate where +supported; a region executable only after fusion need not first have standalone target-EP kernels. + +Use the existing `ComputeCapability` application model, including `nodes_to_optimize` and an +ORT-controlled `optimization_func` adapter, rather than add an unrelated post-partition callback. +Existing registry optimizers can be shared through `GraphOptimizerRegistry`. Neither `Graph&` +nor `std::function` crosses a future external ABI. + +**This is reuse, not an assertion that the current API is sufficient unchanged.** The current +partitioner checks availability, runs child optimizations, and places/fuses the updated parent. +Explicit candidate ordering, nonmutating recipe descriptions, arbitrary replacement-DAG capability +updates/splitting, graph-revision validation, and external registration still need supported adapters. +The first structural probe must verify that emitted adapters and existing contrib nodes receive +normal kernel registration/finalization without incorrectly becoming one compiled synthetic node. + +### Proposed lifecycle and ordering + +The logical lifecycle is: + +```text +canonical load -> shared preprocessing / L0-L1 + -> EP GetCapability: describe candidates from EP-ordered patterns + -> ORT checks availability, eligibility, and ordered conflicts + -> [budgeted integration: query recipe requirements before final selection] + -> provisionally select compatible candidates + -> apply selected recipes through capability optimization adapters + -> reconcile graph, capabilities, kernels, and resource information + -> finalize placement -> kernel creation / PrePack -> Run +``` + +Within an EP, sort candidates first by the EP's pattern-list ordinal, then by stable graph traversal, +anchor node ID, and candidate ID. Package registration/load order must not override this list. +Process valid candidates greedily in that order; an accepted candidate excludes overlapping ones, +including parent whole-Scan versus descendant rewrites. A declined candidate does not claim nodes +or consume reservations, so a lower-priority feasible candidate can still be considered. +Priority never overrides schemas, semantics, ownership, kernel availability, or memory feasibility. +There is no original-versus-fused speed tournament, online benchmark, or optimal-combination search. + +Pattern selection requires explicit enablement and optimization level at least L2. Absent, disabled, +and Basic-only configurations preserve ordinary behavior. Required-pattern policy at Basic fails +configuration validation. Initial scope is ordinary ONNX inference initialization; ORT-format +loading, compile-only sessions, training, online repartition, and optimized-cache replay need +separate support. Optional use on those paths is diagnosed as not applicable; required use fails. + +`GetCapability` can be called again as graphs/providers/layouts change. Discovery must therefore be +read-only and repeatable, not globally once-only. Key prepared candidates by graph revision, +ownership view, EP instance, effective configuration and workload bounds. Do not reuse stale matches +or double-charge resources on another pass. Apply only a selected candidate valid for that revision; +its application callback must execute the chosen recipe, not rerun matching to select another one. +Install the necessary graph/accounting update support before application; do not depend solely on +callbacks currently installed after partition. + +Publish positive and near-match fixtures at capability discovery, not only untouched exporter +topology. Preserve unrelated private transformer behavior. Later transforms may further optimize +emitted nodes, but must not silently invalidate selected kernel/state/resource contracts. + +### Memory estimation and placement planning + +Baseline delivery can first exercise ordered capability selection without claiming a memory bound. +The next integration connects target-specific estimators to those same recipes, not a separate +post-partition fusion pipeline. A budgeted selector evaluates memory feasibility before accepting +a candidate, then considers the next EP-ordered candidate if the higher-priority one is infeasible. +The baseline order-only selector does not need a full CPU/GPU planner to demonstrate fusion. + +For the future planner in [#33372](https://github.com/microsoft/onnxruntime/pull/33372), supply tentative +EP ownership without final assignment. Describe logical graph effects and allocation requirements +before selecting placement; the fused implementation may fit where unfused source estimates do not. +Queries must not mutate the live graph, construct kernels, upload weights, or materialize trial +device resources. Apply a provisional winner on a candidate graph, reconcile transfers/casts and the +execution/backing plan, verify, then commit final placement before materialization. +Placement-sensitive L2+ passes must participate as selected recipes or be gated in this mode. +This planner is proposed follow-on work, not current ORT behavior or a prerequisite for baseline. + +### Responsibility boundaries for future offloading + +Fusion supplies eligible implementation recipes and their resource contracts; P owns graph-level +CPU/GPU region and boundary selection. An expert-offload controller can manage residency and CPU/GPU +execution inside an admitted node without assigning separate graph nodes to the CPU EP, as proposed +by the QMoE [controller](https://github.com/microsoft/onnxruntime/pull/33451) and +[backend](https://github.com/microsoft/onnxruntime/pull/33452). Execution/capture consumes the +reconciled placement, as in the [partitioned-capture prototype](https://github.com/microsoft/onnxruntime/pull/32718). +These proposals are integration references, not implemented compatibility or first-release dependencies. +Not every offloading decision belongs in `GetCapability`. + +Reuse the existing L1/L2 transformer infrastructure and preserve existing fusions; adopting the +rewrite engine does not require migrating them to capability discovery. The fusion adapter must not +become a second global placement planner or budget owner. A node-local controller operates within +resources admitted by the shared resource owner; it cannot independently spend the same headroom. + +## 4. Recipe contracts and independent delivery (D2) + +### Logical descriptors, not a frozen ABI + +Define the capability integration before fixing external function tables. The previous draft's +parallel `OrtRF*` inspection/registration table is not the selected interface for this revision. +Reuse `OrtGraph`, `OrtNode`, and `OrtValueInfo` for external read-only inspection where applicable, +adding only missing revision, EP-instance, workload, and bounded-constant-access contracts. +No new public API names, binary layouts, experimental factory, or loading entry point are approved +by this document. The first prototype can use internal adapters, but cannot claim independent +binary deployment on that basis. + +The following describes required information, not new C/C++ structs: + +| Descriptor | Required information | +|---|---| +| EP pattern registration | Pattern/contract ID and revision, implementation/package identity and revision, enablement, explicit EP-owned priority ordinal, retained callback state. | +| Candidate context | Graph/revision, source-node IDs, anchor, target EP instance and current/tentative ownership view, immutable effective session configuration and workload bounds. | +| Prepared recipe | Candidate ID, complete consumption closure, boundary inputs/outputs/captures, logical replacement DAG or compiler-private implementation description, attributes/types/opsets/constants, dependencies, and retained application state. | +| Requirement query | Selected recipe/kernel/build/device, dtype and workload/alias/concurrency bounds; target-owned requirements with an explicit unknown result, not package-asserted byte counts. | +| Selection/application | Exact candidate/recipe identity and revision, selected contracts, validated boundary mapping, and an application handle that cannot substitute another implementation. | + +ORT must bound candidate count, descriptor storage, and constant reads; recommended initial caps +remain 8,192 candidates, 64 MiB descriptor storage, and 1 MiB new/read constant payload. +Metadata inspection must not materialize 27B weights. Existing initializers can be referenced without +copying payloads; new constants become ORT-owned. Preserve shared storage and every observable +boundary value identity. A consumed CPU compute node is not made eligible by a CPU-resident input. + +Freeze effective configuration and the ordered registry for each session; another session may use +a different order or workload envelope without sharing mutable candidate state. Reject conflicting +duplicate pattern identities rather than let package loading silently replace priority. +Required-pattern policy means at least one accepted candidate of that qualified pattern, not a +promise that every occurrence fuses. A candidate discovered or retained is not yet selected. + +### Delivery, ownership, and lifetime requirements + +EP-bundled patterns and optional independent packages must obey the same recipe/selection model. +An external package registers into the target EP's registry; it does not become a competing EP, +claim nodes directly, or establish priority by load order. No exporter or model weights are required +in the runtime package. EP-owned registration does not require matcher updates to ship with the EP. + +Before external delivery, specify and qualify a version-negotiated registration/recipe adapter, +including which existing C/EP APIs it reuses and which minimal additions are necessary. Do not expose +`Graph&`, STL, protobuf objects, provider pointers, CUDA streams, or kernel factories across that +boundary. Compiler-private lowering and host-visible rewrites must normalize to the same selected +recipe contract; compiler EPs need not adopt the pilot to continue working. + +Retain the following requirements when defining that adapter: + +- Application-controlled static registration and explicit library loading; no directory scanning or + executable paths taken from model metadata. Native packages are trusted application code. +- Atomic registration and cleanup on failure; copied descriptors and explicit retain/release rules + for package, candidate, and recipe state. Graph inspection handles are callback-scoped borrows; + retained recipes must not contain raw pointers that survive a graph revision. +- SessionOptions cloning/configuration isolation and immutable per-session registry snapshots. + Release candidate/session state before package callbacks or libraries can be unloaded. +- Serialized, non-reentrant callbacks within a session and safe concurrent initialization across + sessions. No asynchronous callback outlives its call; shared immutable registrations are thread-safe. +- No exceptions crossing a C boundary, explicit `OrtStatus` ownership, unchanged out-parameters on + failure, and release callbacks that cannot fail. +- Explicit errors for incompatible versions, failed loads/registration, malformed recipes, and + callback errors. Optional absence is an application choice, not permission to swallow errors. + Missing optional patterns leave ordinary execution; required unavailability fails. + +Independent DSO conformance and a supported ORT/EP/build/device/package matrix are delivery gates, +not prerequisites to choosing the capability architecture. Exact public signatures remain a +follow-on approval item; this revision does not promise indefinite cross-release ABI stability. + +### Workload eligibility + +Workload bounds are application-declared in both resource modes. Validate `0 <= min <= max`, +input names, uniqueness, and ranks before discovery. Matchers and requirement queries see the same +immutable validated session view, not mutable options or another session's shared package state. +The eventual implementation must reject out-of-envelope feeds/bindings before any kernel launches. +Graph symbolic dimensions alone are not bounds and missing bounds remain unknown. + +Eligibility must hold for the entire fixed-shape or bounded envelope. No package Run callback, +dynamic match/defuse, or data-dependent fallback is added. For example, a session allowing +`B=2,P>0,S>1` cannot receive GQA just because its first call is prefill. Use B=1, or separate +B=2 prefill (`P=0`) and decode (`S=1`) envelopes with the same canonical file. +Mask contents and arbitrary length-tensor values cannot be certified by shape bounds. + +### Matcher choice is a demo-stage decision + +W's composable pattern engine is an optional implementation, not the system interface. Its +standalone `Pattern::MatchAt`/`VisitMatches` can inspect matches without applying a rule; standalone +matching does not enforce the rule engine's EP/opset policy. `Replacement::Instantiate` constructs +a fragment, not a complete boundary-preserving replacement transaction. Those components may be +adapted behind the EP registry, but do not supply memory admission or an external ABI. + +Do not make adopting that DSL or merging W a system-design prerequisite. During the Qwen demo, +first use small exported Attention/convolution/Scan fixtures to measure pattern expressiveness, +adapter effort, topology survival, and matching overhead; then validate the full model. +A different matcher must produce the same recipe/selection contract. Demo results decide practical +adoption, not whether the architecture needs a second optimizer pipeline. + +## 5. Host validation and commitment (D3) + +1. Freeze the session's EP registry/order and configuration. Discover bounded candidate descriptions + without graph mutation or resource charges; retain them only for their graph/ownership revision. + Enforce count/byte caps and unique candidate identities within that revision. +2. Validate IDs, current revision, graph scope, acyclicity, schemas/import versions, type/shape + compatibility, constants, and every use crossing the region boundary. Preserve all escaping + outputs, including graph outputs and descendant captures. Missing observable state is not dead + just because it has no ordinary local consumer. Do not raise an existing domain import and + reinterpret surviving nodes; reject incompatible requests. Adding an otherwise absent target + domain import is allowed only after validating all emitted schemas. +3. Require the complete consumed executable region to be available for the target EP instance. + Unassigned nodes can be claimed if the proposed implementation supports them; final assignment + of every source node is not a discovery prerequisite. A tentative ownership view can establish + same-EP eligibility. Never steal work committed to another EP, enter compiler-owned opaque + regions, or use host-resident constants as permission to consume CPU-assigned compute. +4. Resolve replacement kernels through the **actual session registry precedence**, without kernel + construction. Validate schema/type constraints, build pruning, host-input requirements, device + capabilities, and package-qualified operator contract. V1 declines any custom-registry override + of an emitted kernel, even one with the same op name; it does not bypass normal lookup. Core + records the selected built-in registration privately and checks it again at finalization. +5. In budgeted mode, obtain target-specific coverage before selection/commit (section 8). +6. Preserve ordinary EP priority, then process candidates in the EP-owned pattern order and stable + tie-break order from section 3. Choose compatible, non-overlapping feasible candidates. Consumption + closure, including Scan descendants, defines overlap. Rejected candidates do not reserve nodes + or bytes. Lower-priority alternatives are available before application, not as recovery from + an already failed mutation. No per-session benchmark or cost tournament is added. +7. Validate the accepted **combined** replacement against a disposable graph representation before + touching the live graph. This catches cycles and boundaries introduced by interactions between + individually valid recipes. Through a validated capability adapter, ORT materializes selected + nodes, updates/splits the parent capability as needed, and preserves boundary values. New compute + nodes must have target kernels; host adapters are explicit and checked, not hidden callbacks. +8. Apply only the prepared recipes, update capabilities/identities/accounting, and Resolve. Reconcile + the actual implementations before final placement/resource commitment. Unexpected application or + Resolve failure aborts baseline initialization; the pilot promises no live-graph rollback or Run + retry. A future planner may reject an isolated candidate graph under P's separate contract, but + cannot continue with a partially mutated live graph or recurrent state. + +Unsupported kernels, masks, ownership, or resource coverage detected before commitment leave the +original region intact with a reason, unless a required pattern cannot be satisfied. If neither a +selected recipe nor ordinary execution covers the region, initialization fails; leaving it unchanged +does not manufacture missing kernel support. +Distinguish `NO_MATCH`, `EP_MISMATCH`, `OPAQUE_REGION`, `UNSUPPORTED_MASK/STATE/TYPE`, +`KERNEL_MISSING/OVERRIDDEN`, `RESOURCE_UNKNOWN/INSUFFICIENT`, `OVERLAP`, `STALE_SNAPSHOT`, +`INVALID_PROPOSAL`, and `COMMIT_FAILED`. Stale/malformed proposals and callback failures are errors, +not silent non-matches. + +Diagnostics identify artifact digest, package/revision, pattern contract, graph/revision, consumed +and emitted nodes, EP pattern priority, target instance diagnostic ID, selected registration, +rejection reason, and later transformations. Default logging includes no tensor payload, library +search path, or weight data. An opt-in trace records timing and resource provenance. + +## 6. Pilot source-to-target contracts (D4) + +The mappings below are package pattern contracts, not model names in the core API. They require +structural and semantic qualification; registration of a kernel establishes feasibility only. +Canonical ONNX inputs/outputs remain unchanged, including all cache and recurrent state values. +Adapters are ordinary existing operations in the replacement DAG, not hidden package execution. +These are demo/implementation qualification gates, not prerequisites to selecting the capability +architecture or a particular matcher DSL. + +### 6.1 Canonical Attention to GroupQueryAttention + +Use M's `_attention.py::_apply_attention` and +[`rewrite_rules/_group_query_attention.py::AttentionToGQA:680-833`](https://github.com/onnxruntime/mobius/blob/b1e9c8e9dec1dfa206183f957e77d07cfb734df2/src/mobius/rewrite_rules/_group_query_attention.py#L680-L833) +as prior art, not as a semantic oracle. +Qwen35Attention's offset Q/K normalization, partial MRoPE, projection packing, and sigmoid output +gate stay outside the first region. Target `com.microsoft::GroupQueryAttention:1`, `do_rotary=0`, +no packed QKV, no local window, no quantized cache, no absorbed normalization. +CUDA activation registration is FP16/BF16, not FP32 +([R registration](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/onnxruntime/contrib_ops/cuda/bert/group_query_attention.cc#L56-L84)). +V1 qualifies **FP16 only**; FP32 remains a baseline/non-match. BF16 is a later independently gated +package contract even though the kernel is registered. + +| Canonical boundary | Emitted boundary / condition | +|---|---| +| Q `[B,Hq,S,D]`, K/V `[B,Hkv,S,D]` or proven equivalent 3-D form | Transpose/reshape to query `[B,S,Hq*D]`, key/value `[B,S,Hkv*D]`; inputs 0/1/2. Require `Hq % Hkv == 0`, equal K/V head dimensions, proven head ordering. | +| `q_num_heads`, `kv_num_heads`, Attention scale | GQA `num_heads=Hq`, `kv_num_heads=Hkv`; materialize exact scale, including default `1/sqrt(D)`. Do not change scaling or softcap/softmax semantics. | +| Dynamic past K/V `[B,Hkv,P,D]` | Inputs 3/4; present outputs 1/2 `[B,Hkv,P+S,D]`. No paged/static/windowed/sliding/borrowed cache, quantization, or fixed-capacity tail semantics. | +| Per-row total valid length `L_b` | `seqlens_k[b] = L_b - 1`, INT32 device input 5; scalar `total_sequence_length = max_b L_b`, INT32 CPU input 6. These are not a mask tensor or past lengths. | +| Causal current chunk | GQA attribute `causal=1` (not the internal `is_unidirectional` name); prove query position i attends keys through `P+i`. Reject top-left/bottom-right alignment disagreement. | +| Y and cache outputs | Output 0 is `[B,S,Hq*D]`; restore canonical Y layout and preserve exact present-K/V value IDs. | +| Optional Attention diagnostics | Reject an observed QK/softmax/LSE or other unsupported output. CUDA GQA cannot return QK; do not erase observable outputs. | + +**First mask envelope:** uniform dense rows, one shared `P`, all `S` query tokens valid, `L_b=P+S`, +and no explicit mask or a structurally proven redundant causal mask. Allow prefill `P=0` and +single-token decode for dense uniform B; continuation chunks `P>0,S>1` require **B=1** under the +[existing helper constraint](https://github.com/microsoft/onnxruntime/blob/1988019b6e23ac4def4628cddd603c1bfb504fe8/onnxruntime/contrib_ops/cpu/bert/group_query_attention_helper.h#L429-L445). +`B>1,P>0,S>1` is a non-match even with equal lengths. Prove INT32 arithmetic before narrowing: `0 <= P`, +`1 <= S`, `P+S <= INT32_MAX`, and all shape/offset products fit their kernel interfaces. +Derive host placement from the selected kernel definition; copies are inserted by normal ORT +logic and included in resource/performance accounting. Never pull arbitrary device data to the +host during discovery or infer runtime mask contents from a symbolic shape. A graph input mask that +happens to contain ones in a test is not structurally proven redundant. V1 declines arbitrary +runtime mask inputs; supporting a checked runtime predicate before any state mutation would require +a separately approved adapter/error contract. A workload promise alone must not erase mask semantics. + +For padding, map a standard prefix-valid mask to `L_b` only under a **separate qualified contract** +that proves query alignment, cache padding, output values for padded queries, and per-row lengths. +V1 rejects different per-row valid lengths, left/interior padding, arbitrary additive bias, +noncausal mode, unknown mask provenance, and fully masked rows. Finite large-negative values are +not automatically equivalent to `-infinity`. Mask conversion must preserve original error/bounds +semantics; `Cast(INT64->INT32)` alone is not range validation. Unproven cases remain unfused. + +The smallest blocker is a tiny capability-discovery Attention fixture with `P>0,S>1`, plus a ragged +near-match, recording source ownership and causal/output equality. Acceptance of uniform dense +GQA does not approve broader padding, BF16, cache aliases, or absorbed rotary. + +### 6.2 Causal convolution + +M's [`functions/causal_conv.py:21-145`](https://github.com/onnxruntime/mobius/blob/b1e9c8e9dec1dfa206183f957e77d07cfb734df2/src/mobius/functions/causal_conv.py#L21-L145) +supplies the source history/Conv/Swish form. Target existing +`com.microsoft::CausalConvWithState:1`: + +| Source | Target | +|---|---| +| Current x `[B,C,S]`, history h `[B,C,K-1]`, `Concat(h,x)` along sequence | input 0 = x; optional input 3 = h; missing history means zero history only if source agrees. | +| Depthwise Conv weight `[C,1,K]`, optional bias `[C]` | inputs 1/2; `group=C`, stride 1, dilation 1, and exactly the source causal padding/cropping. | +| `z * Sigmoid(z)` after bias | `activation="swish"`; do not absorb differently ordered gating. | +| Trailing `K-1` values of **raw** concatenated history/current input | present_state output 1, not post-convolution or activated values. Output 0 keeps x's shape. | + +V1 requires `ndim=1,channels_last=0,dilation=1,state_window=0`, dense FP16, no extra padding/stride +or side uses of consumed intermediates. A Concat already providing history plus a valid Conv +must not also receive another left pad. Check every Slice axis/start/end/step and Conv attribute, +not just the operator names. For `S