Summary
Creating a VitisAIExecutionProvider session for a BF16 (cast-format) model that contains two Slice nodes with negative starts/ends literals feeding one Concat — the exact ONNX export form of torch.roll(x, shifts=+s) — kills the whole host process with a native assertion during VAIML's ONNX→ONNX-MLIR lowering:
Assertion failed: all_equal({range_size(t), range_size(u), range_size(args)...}) && "Iteratees do not have equal length",
file G:\xmc\src\common\llvm-aie\llvm\include\llvm/ADT/STLExtras.h, line 867
Exception Code: 0x80000003
The minimal reproducer is 3 nodes. The same topology with non-negative literals compiles and runs fine, and each negative-bound Slice compiles fine in isolation — the crash needs the combination.
Because this is the export pattern of torch.roll, it currently blocks every shifted-window transformer (SwinIR / Swin family) at session creation. Since it is a native assert, there is no exception to catch: the application embedding onnxruntime dies.
Environment
- Ryzen AI 7 PRO 350 (XDNA2 NPU), Windows 11 Home 10.0.26200
- NPU driver 32.0.203.329
- Ryzen AI Software 1.8.0 (conda env), onnxruntime 1.27.0 (VitisAI EP)
- amd-quark 0.11rc1, conversion:
quark.onnx.tools.convert_fp32_to_bf16 --format with_cast
Minimal reproduction (3 nodes, no weights needed)
# make_repro.py — builds the fp32 graph
import numpy as np
import onnx
from onnx import helper, numpy_helper, TensorProto
INT64_MAX = 2**63 - 1
SHAPE = [1, 256, 256, 180] # any 4-D shape reproduces; this matches SwinIR-M 256x256
def ini(name, vals):
return numpy_helper.from_array(np.array(vals, dtype=np.int64), name)
graph = helper.make_graph(
[
helper.make_node("Slice", ["x", "sA", "eA", "ax"], ["a"], name="slice_a"),
helper.make_node("Slice", ["x", "sB", "eB", "ax"], ["b"], name="slice_b"),
helper.make_node("Concat", ["a", "b"], ["y"], name="concat", axis=1),
],
"neg_slice_concat_repro",
[helper.make_tensor_value_info("x", TensorProto.FLOAT, SHAPE)],
[helper.make_tensor_value_info("y", TensorProto.FLOAT, SHAPE)],
initializer=[ini("sA", [-4]), ini("eA", [INT64_MAX]),
ini("sB", [0]), ini("eB", [-4]), ini("ax", [1])],
)
model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 17)])
model.ir_version = 8
onnx.checker.check_model(model)
onnx.save(model, "neg_roll_fp32.onnx")
python -m quark.onnx.tools.convert_fp32_to_bf16 \
--input neg_roll_fp32.onnx --output neg_roll_bf16.onnx --format with_cast
import onnxruntime as ort
s = ort.InferenceSession(
"neg_roll_bf16.onnx",
providers=["VitisAIExecutionProvider"],
provider_options=[{"cache_dir": "cache", "cache_key": "neg_roll_repro"}])
# -> process dies here: assertion above, exit code 0x80000003
Observed crash happens ~0.6 s into session creation, before any partition report (context.json / assignment report) is written. Readable frames in the crash dump: flexml::compile::common::lowerONNXToONNXMLIR → flexmlir::VResultNamesUpdater → llvm::zip_equal<SmallVector<std::string>> — i.e. a result-count vs result-name-count mismatch while normalizing the graph, consistent with a Slice decomposition/normalization step mishandling negative bounds.
Isolation evidence (all on the same machine, same conversion path)
| Graph |
Result |
Slice(-4..INT64_MAX) + Slice(0..-4) + Concat (this repro) |
native crash 0x80000003 |
Identical topology, non-negative literals (forward roll: 4..INT64_MAX / 0..4) |
compiles + runs |
Identical topology, only the two negative literals replaced by positive equivalents (-4 → 252) |
compiles + runs |
Each negative-bound Slice alone (no Concat), both variants |
compiles + runs |
SwinIR-M x4 full model (1697 nodes, 72 negative-literal Slice bounds) |
native crash, same assertion |
| Same full model with all negative bounds rewritten to positive equivalents (bit-exact, node count/order unchanged) |
compiles: 4971 ops / 2969 GOPs accepted as a single 100% VAIML subgraph; runs with PSNR 38.5 dB vs fp32 CPU |
Standalone LayerNormalization / Softmax / 4-D MatMul / window-partition Transpose-Reshape chains / masked window attention all compile and run individually, so overall Swin-type support looks otherwise healthy — only the negative-literal handling in this pattern is broken.
Expected behavior
Normalize negative Slice starts/ends per the ONNX spec during lowering (they are fully determined for static shapes), or reject the graph gracefully (CPU fallback / catchable exception). A native assertion that terminates the host process should never be reachable from model input.
Workaround (for anyone hitting this)
Rewrite negative Slice bound literals to their positive equivalents before conversion (value + dim_size); the result is numerically identical and compiles. This unblocks SwinIR-family models completely.
Summary
Creating a
VitisAIExecutionProvidersession for a BF16 (cast-format) model that contains twoSlicenodes with negativestarts/endsliterals feeding oneConcat— the exact ONNX export form oftorch.roll(x, shifts=+s)— kills the whole host process with a native assertion during VAIML's ONNX→ONNX-MLIR lowering:The minimal reproducer is 3 nodes. The same topology with non-negative literals compiles and runs fine, and each negative-bound
Slicecompiles fine in isolation — the crash needs the combination.Because this is the export pattern of
torch.roll, it currently blocks every shifted-window transformer (SwinIR / Swin family) at session creation. Since it is a native assert, there is no exception to catch: the application embedding onnxruntime dies.Environment
quark.onnx.tools.convert_fp32_to_bf16 --format with_castMinimal reproduction (3 nodes, no weights needed)
python -m quark.onnx.tools.convert_fp32_to_bf16 \ --input neg_roll_fp32.onnx --output neg_roll_bf16.onnx --format with_castObserved crash happens ~0.6 s into session creation, before any partition report (
context.json/ assignment report) is written. Readable frames in the crash dump:flexml::compile::common::lowerONNXToONNXMLIR→flexmlir::VResultNamesUpdater→llvm::zip_equal<SmallVector<std::string>>— i.e. a result-count vs result-name-count mismatch while normalizing the graph, consistent with aSlicedecomposition/normalization step mishandling negative bounds.Isolation evidence (all on the same machine, same conversion path)
Slice(-4..INT64_MAX)+Slice(0..-4)+Concat(this repro)4..INT64_MAX/0..4)-4→252)Slicealone (noConcat), both variantsSlicebounds)Standalone LayerNormalization / Softmax / 4-D MatMul / window-partition Transpose-Reshape chains / masked window attention all compile and run individually, so overall Swin-type support looks otherwise healthy — only the negative-literal handling in this pattern is broken.
Expected behavior
Normalize negative
Slicestarts/endsper the ONNX spec during lowering (they are fully determined for static shapes), or reject the graph gracefully (CPU fallback / catchable exception). A native assertion that terminates the host process should never be reachable from model input.Workaround (for anyone hitting this)
Rewrite negative
Slicebound literals to their positive equivalents before conversion (value + dim_size); the result is numerically identical and compiles. This unblocks SwinIR-family models completely.