From fe482bd5752083bfade296d00b468034da34e650 Mon Sep 17 00:00:00 2001 From: Abhishek Gola Date: Sat, 25 Jul 2026 14:09:47 +0530 Subject: [PATCH] Merge pull request #29577 from abhishek-gola:scan_layer Add Scan layer to the new engine #29577 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- .../dnn/include/opencv2/dnn/all_layers.hpp | 22 ++++ modules/dnn/src/graph_buffer_allocator.cpp | 90 +++++++------- modules/dnn/src/init.cpp | 1 + modules/dnn/src/layer.cpp | 2 +- modules/dnn/src/layers/scan_layer.cpp | 76 ++++++++++++ modules/dnn/src/net_impl2.cpp | 111 ++++++++++++++++++ modules/dnn/src/onnx/onnx_importer2.cpp | 64 ++++++++-- ...conformance_layer_filter__openvino.inl.hpp | 6 +- ...yer_filter_opencv_classic_denylist.inl.hpp | 3 + ..._conformance_layer_parser_denylist.inl.hpp | 7 +- 10 files changed, 325 insertions(+), 57 deletions(-) create mode 100644 modules/dnn/src/layers/scan_layer.cpp diff --git a/modules/dnn/include/opencv2/dnn/all_layers.hpp b/modules/dnn/include/opencv2/dnn/all_layers.hpp index 9cafa7ad63..bce0b862de 100644 --- a/modules/dnn/include/opencv2/dnn/all_layers.hpp +++ b/modules/dnn/include/opencv2/dnn/all_layers.hpp @@ -858,6 +858,28 @@ CV__DNN_INLINE_NS_BEGIN static Ptr create(const LayerParams& params); }; + class CV_EXPORTS ScanLayer : public Layer + { + public: + /** Number of trailing inputs that are scanned (the rest are loop-carried state). */ + virtual int numScanInputs() const = 0; + /** Per-scan-input axis to iterate over (empty => 0 for all). */ + virtual const std::vector& scanInputAxes() const = 0; + /** Per-scan-output axis to stack along (empty => 0 for all). */ + virtual const std::vector& scanOutputAxes() const = 0; + /** Per-scan-input direction, 1 = reverse (empty => forward for all). */ + virtual const std::vector& scanInputDirections() const = 0; + /** Per-scan-output direction, 1 = reverse (empty => forward for all). */ + virtual const std::vector& scanOutputDirections() const = 0; + /** ONNX-declared rank of each body scan output (-1 if unknown). A declared rank of + * 0 (scalar) marks an output that OpenCV stores as [1] but must stack into a rank-1 + * tensor, not [T, 1]. Empty => unknown for all. */ + virtual const std::vector& scanOutputRanks() const = 0; + + /** Factory: creates a ScanLayer implementation. */ + static Ptr create(const LayerParams& params); + }; + class CV_EXPORTS ConcatLayer : public Layer { public: diff --git a/modules/dnn/src/graph_buffer_allocator.cpp b/modules/dnn/src/graph_buffer_allocator.cpp index 6563ddc084..2c4b5f500e 100644 --- a/modules/dnn/src/graph_buffer_allocator.cpp +++ b/modules/dnn/src/graph_buffer_allocator.cpp @@ -137,6 +137,49 @@ struct BufferAllocator releaseBuffer(toBuf); } + // Allocate a Loop/Scan body, keeping its closure (outer-scope) args alive across it. + void assignSubgraphKeepingClosure(const Ptr& body) + { + std::unordered_set bodyDefined; + for (Arg ba : body->inputs()) + bodyDefined.insert(ba.idx); + for (const Ptr& blayer : body->prog()) { + if (!blayer) continue; + for (Arg bo : blayer->outputs) + bodyDefined.insert(bo.idx); + } + std::unordered_set closureBumped; + for (const Ptr& blayer : body->prog()) { + if (!blayer) continue; + for (Arg bi : blayer->inputs) { + if (bi.idx <= 0) continue; + if (bodyDefined.count(bi.idx)) continue; + if (netimpl->isConstArg(bi)) continue; + if (bufidxs[bi.idx] < 0) continue; + if (closureBumped.insert(bi.idx).second) { + usecounts[bi.idx]++; + buf_usecounts[bufidxs[bi.idx]]++; + } + } + } + + std::vector saved_freebufs = freebufs; + freebufs.clear(); + assign(body); + freebufs = saved_freebufs; + + for (int idx : closureBumped) { + int bidx = bufidxs[idx]; + if (--usecounts[idx] == 0) { + if (bidx >= 0) + releaseBuffer(bidx); + } else if (bidx >= 0) { + CV_Assert(buf_usecounts[bidx] > 0); + --buf_usecounts[bidx]; + } + } + } + template std::ostream& dumpArgVec(std::ostream& strm, const std::string& name, const vector<_Tp>& vec) const { @@ -355,48 +398,11 @@ struct BufferAllocator } } - // The body reads names produced in the enclosing scope - // (closure references) without listing them in the Loop/If - // layer's inputs. Bump the outer-scope usecount of each - // such arg so its buffer survives until the subgraph runs. - std::unordered_set bodyDefined; - for (Arg ba : body->inputs()) - bodyDefined.insert(ba.idx); - for (const Ptr& blayer : body->prog()) { - if (!blayer) continue; - for (Arg bo : blayer->outputs) - bodyDefined.insert(bo.idx); - } - std::unordered_set closureBumped; - for (const Ptr& blayer : body->prog()) { - if (!blayer) continue; - for (Arg bi : blayer->inputs) { - if (bi.idx <= 0) continue; - if (bodyDefined.count(bi.idx)) continue; - if (netimpl->isConstArg(bi)) continue; - if (bufidxs[bi.idx] < 0) continue; - if (closureBumped.insert(bi.idx).second) { - usecounts[bi.idx]++; - buf_usecounts[bufidxs[bi.idx]]++; - } - } - } - - std::vector saved_freebufs = freebufs; - freebufs.clear(); - assign(body); - freebufs = saved_freebufs; - - for (int idx : closureBumped) { - int bidx = bufidxs[idx]; - if (--usecounts[idx] == 0) { - if (bidx >= 0) - releaseBuffer(bidx); - } else if (bidx >= 0) { - CV_Assert(buf_usecounts[bidx] > 0); - --buf_usecounts[bidx]; - } - } + assignSubgraphKeepingClosure(body); + } else if (opname == "Scan") { + auto subgraphs = layer->subgraphs(); + CV_Assert(subgraphs && subgraphs->size() == 1); + assignSubgraphKeepingClosure(subgraphs->at(0)); } for (auto out: outputs) { diff --git a/modules/dnn/src/init.cpp b/modules/dnn/src/init.cpp index 921ac0cde5..c146dea1b8 100644 --- a/modules/dnn/src/init.cpp +++ b/modules/dnn/src/init.cpp @@ -86,6 +86,7 @@ void initializeLayerFactory() CV_DNN_REGISTER_LAYER_CLASS(If, IfLayer); CV_DNN_REGISTER_LAYER_CLASS(Loop, LoopLayer); + CV_DNN_REGISTER_LAYER_CLASS(Scan, ScanLayer); CV_DNN_REGISTER_LAYER_CLASS(Concat, ConcatLayer); CV_DNN_REGISTER_LAYER_CLASS(Concat2, Concat2Layer); CV_DNN_REGISTER_LAYER_CLASS(ConstantOfShape, ConstantOfShapeLayer); diff --git a/modules/dnn/src/layer.cpp b/modules/dnn/src/layer.cpp index 74a295a2b2..f783762898 100644 --- a/modules/dnn/src/layer.cpp +++ b/modules/dnn/src/layer.cpp @@ -376,7 +376,7 @@ std::ostream& Layer::dump(std::ostream& strm, int indent, bool comma) const std::vector names; if (opname == "If") names = {"then", "else"}; - else if (opname == "Loop") + else if (opname == "Loop" || opname == "Scan") names = {"body"}; else { CV_Error(Error::StsError, diff --git a/modules/dnn/src/layers/scan_layer.cpp b/modules/dnn/src/layers/scan_layer.cpp new file mode 100644 index 0000000000..987c37a2e2 --- /dev/null +++ b/modules/dnn/src/layers/scan_layer.cpp @@ -0,0 +1,76 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +// Copyright (C) 2026, BigVision LLC, all rights reserved. +// Third party copyrights are property of their respective owners. + +#include "../precomp.hpp" +#include "../net_impl.hpp" +#include "layers_common.hpp" +#include + +namespace cv { namespace dnn { +CV__DNN_INLINE_NS_BEGIN + +// ONNX op: Scan — https://onnx.ai/onnx/operators/onnx__Scan.html +// Supported opsets: 9 .. latest (opset-9 dataflow semantics). +// Opset-8 Scan (leading batch dimension + optional sequence_lens input) has different +// semantics and is NOT supported. +// +// Net::Impl::forwardGraph drives the loop; this layer only holds the body and its attributes. +class ScanLayerImpl CV_FINAL : public ScanLayer +{ +public: + explicit ScanLayerImpl(const LayerParams& params) + { + setParamsFrom(params); + num_scan_inputs = params.get("num_scan_inputs"); + readIntList(params, "scan_input_axes", input_axes); + readIntList(params, "scan_output_axes", output_axes); + readIntList(params, "scan_input_directions", input_dirs); + readIntList(params, "scan_output_directions", output_dirs); + readIntList(params, "scan_output_ranks", output_ranks); + } + + std::vector >* subgraphs() const CV_OVERRIDE { return &body_; } + bool dynamicOutputShapes() const CV_OVERRIDE { return true; } + + bool getMemoryShapes(const std::vector&, + const int requiredOutputs, + std::vector& outputs, + std::vector& internals) const CV_OVERRIDE + { + outputs.assign(std::max(1, requiredOutputs), MatShape()); + internals.clear(); + return false; + } + + int numScanInputs() const CV_OVERRIDE { return num_scan_inputs; } + const std::vector& scanInputAxes() const CV_OVERRIDE { return input_axes; } + const std::vector& scanOutputAxes() const CV_OVERRIDE { return output_axes; } + const std::vector& scanInputDirections() const CV_OVERRIDE { return input_dirs; } + const std::vector& scanOutputDirections() const CV_OVERRIDE { return output_dirs; } + const std::vector& scanOutputRanks() const CV_OVERRIDE { return output_ranks; } + +private: + static void readIntList(const LayerParams& params, const std::string& name, std::vector& out) + { + out.clear(); + if (!params.has(name)) return; + const DictValue& v = params.get(name); + out.resize(v.size()); + for (int i = 0; i < v.size(); ++i) out[i] = v.get(i); + } + + int num_scan_inputs = 0; + std::vector input_axes, output_axes, input_dirs, output_dirs, output_ranks; + mutable std::vector > body_; +}; + +Ptr ScanLayer::create(const LayerParams& params) +{ + return makePtr(params); +} +CV__DNN_INLINE_NS_END +}} // namespace cv::dnn diff --git a/modules/dnn/src/net_impl2.cpp b/modules/dnn/src/net_impl2.cpp index 6a26c70bcd..597695b8ef 100644 --- a/modules/dnn/src/net_impl2.cpp +++ b/modules/dnn/src/net_impl2.cpp @@ -1127,6 +1127,49 @@ void Net::Impl::setGraphInput(Ptr& graph, size_t idx, const Mat& m) } } +// Slice a Scan input at index `idx` along `axis`, removing that axis (contiguous result). +static Mat sliceScanAxis(const Mat& m, int axis, int idx) +{ + std::vector r(m.dims, Range::all()); + r[axis] = Range(idx, idx + 1); + Mat sub = m(r).clone(); + std::vector ns; + for (int d = 0; d < m.dims; d++) + if (d != axis) ns.push_back(m.size[d]); + if (ns.empty()) ns.push_back(1); + return sub.reshape(0, (int)ns.size(), &ns[0]); +} + +// Stack per-iteration Scan outputs into one tensor with a new axis at `axis`. +static Mat stackScanAxis(const std::vector& perIter, int axis, bool reverse) +{ + if (perIter.empty()) return Mat(); + const Mat& first = perIter[0]; + const int e = first.dims, T = (int)perIter.size(); + if (axis < 0) axis += e + 1; + CV_Assert(axis >= 0 && axis <= e); + + std::vector os, ss; + for (int d = 0; d < e; d++) { + if (d == axis) { os.push_back(T); ss.push_back(1); } + os.push_back(first.size[d]); + ss.push_back(first.size[d]); + } + if (axis == e) { os.push_back(T); ss.push_back(1); } + + Mat stacked; + stacked.create((int)os.size(), &os[0], first.type()); + for (int k = 0; k < T; k++) { + int idx = reverse ? (T - 1 - k) : k; + std::vector r(os.size(), Range::all()); + r[axis] = Range(k, k + 1); + Mat dst = stacked(r); + Mat src = perIter[idx].reshape(0, (int)ss.size(), &ss[0]); + src.copyTo(dst); + } + return stacked; +} + void Net::Impl::forwardGraph(Ptr& graph, InputArrayOfArrays inputs_, OutputArrayOfArrays outputs_, bool isMainGraph) { @@ -1214,6 +1257,7 @@ void Net::Impl::forwardGraph(Ptr& graph, InputArrayOfArrays inputs_, else { Ptr iflayer = layer.dynamicCast(); Ptr loopLayer = layer.dynamicCast(); + Ptr scanLayer = layer.dynamicCast(); if (iflayer) { int branch = iflayer->branch(inpMats[0]); Ptr subgraph = subgraphs->at(branch); @@ -1306,6 +1350,73 @@ void Net::Impl::forwardGraph(Ptr& graph, InputArrayOfArrays inputs_, outMats[n_state + i] = stacked; } } + else if (scanLayer) { + CV_Assert(subgraphs->size() == 1); + Ptr body = subgraphs->at(0); + + const int M = scanLayer->numScanInputs(); + const int bodyNIn = (int)body->inputs().size(); + const int S = bodyNIn - M; // loop-carried state count + const int K = (int)body->outputs().size() - S; // scan output count + CV_Assert(M > 0 && S >= 0 && K >= 0); + + // Reject opset-8 (batch dim + sequence_lens); those semantics are not implemented. + CV_CheckEQ((int)inpMats.size(), S + M, + "Scan: opset-8 form (batch dim + sequence_lens) is not supported; re-export with opset>=9"); + + const std::vector& iax = scanLayer->scanInputAxes(); + const std::vector& oax = scanLayer->scanOutputAxes(); + const std::vector& idir = scanLayer->scanInputDirections(); + const std::vector& odir = scanLayer->scanOutputDirections(); + const std::vector& orank = scanLayer->scanOutputRanks(); + + std::vector scanIn(M); + std::vector inAxis(M); + std::vector inRev(M); + int T = -1; + for (int j = 0; j < M; j++) { + scanIn[j] = inpMats[S + j]; + int ax = iax.empty() ? 0 : iax[j]; + if (ax < 0) ax += scanIn[j].dims; + inAxis[j] = ax; + inRev[j] = (char)(!idir.empty() && idir[j] != 0); + int len = scanIn[j].size[ax]; + if (T < 0) T = len; else CV_Assert(T == len); + } + CV_Assert(T >= 0); + + std::vector state(S); + for (int i = 0; i < S; i++) state[i] = inpMats[i]; + + std::vector > history(K); + std::vector inputs(bodyNIn), outputs; + + for (int t = 0; t < T; t++) { + for (int i = 0; i < S; i++) inputs[i] = state[i]; + for (int j = 0; j < M; j++) { + int idx = inRev[j] ? (T - 1 - t) : t; + inputs[S + j] = sliceScanAxis(scanIn[j], inAxis[j], idx); + } + forwardGraph(body, inputs, outputs, false); + // Deep-copy: body buffers are recycled across iterations. + // TODO: alias state in/out buffers like Loop to drop this copy. + for (int i = 0; i < S; i++) state[i] = outputs[i].clone(); + for (int k = 0; k < K; k++) history[k].push_back(outputs[S + k].clone()); + } + + outMats.assign(state.begin(), state.end()); + outMats.resize(S + K); + for (int k = 0; k < K; k++) { + int ax = oax.empty() ? 0 : oax[k]; + bool rev = !odir.empty() && odir[k] != 0; + // 0-D scalar output is stored as [1]; drop it so T scalars stack to [T], not [T,1]. + if (k < (int)orank.size() && orank[k] == 0) { + for (Mat& e : history[k]) + e = e.reshape(0, 0, nullptr); + } + outMats[S + k] = stackScanAxis(history[k], ax, rev); + } + } else { CV_Error_(Error::StsNotImplemented, ("unknown layer type '%s' with subgraphs", layer->type.c_str())); diff --git a/modules/dnn/src/onnx/onnx_importer2.cpp b/modules/dnn/src/onnx/onnx_importer2.cpp index b7394b2dbd..2d7b5e1a6e 100644 --- a/modules/dnn/src/onnx/onnx_importer2.cpp +++ b/modules/dnn/src/onnx/onnx_importer2.cpp @@ -202,6 +202,7 @@ protected: void parseClip (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseConcat (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseLoop (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); + void parseScan (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseIf (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseConstant (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseConstantOfShape (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); @@ -742,19 +743,25 @@ Net ONNXImporter2::parseModel() bool ONNXImporter2::parseValueInfo(const opencv_onnx::ValueInfoProto& valueInfoProto, ArgData& data) { CV_Assert(valueInfoProto.has_name()); - CV_Assert(valueInfoProto.has_type()); + // Subgraph body value-infos may omit type/shape; leave unset, inferred at runtime. + if (!valueInfoProto.has_type()) + return true; const opencv_onnx::TypeProto& typeProto = valueInfoProto.type(); - CV_Assert(typeProto.has_tensor_type()); + if (!typeProto.has_tensor_type()) + return true; const opencv_onnx::TypeProto::Tensor& tensor = typeProto.tensor_type(); - CV_Assert(tensor.has_shape()); - const opencv_onnx::TensorShapeProto& tensorShape = tensor.shape(); - auto elem_type = tensor.elem_type(); - data.type = dataType2cv(elem_type); - if (data.type < 0) { - CV_Error(Error::StsNotImplemented, format("unsupported datatype '%s'", dataType2str(elem_type).c_str())); + if (tensor.has_elem_type()) { + auto elem_type = tensor.elem_type(); + data.type = dataType2cv(elem_type); + if (data.type < 0) { + CV_Error(Error::StsNotImplemented, format("unsupported datatype '%s'", dataType2str(elem_type).c_str())); + } } + if (!tensor.has_shape()) + return true; + const opencv_onnx::TensorShapeProto& tensorShape = tensor.shape(); int dim_size = tensorShape.dim_size(); CV_CheckGE(dim_size, 0, ""); MatShape shape(dim_size); @@ -1723,6 +1730,46 @@ void ONNXImporter2::parseLoop(LayerParams& layerParams, *loopLayer->subgraphs() = subgraphs; } +void ONNXImporter2::parseScan(LayerParams& layerParams, + const opencv_onnx::NodeProto& node_proto) +{ + CV_Assert(layerParams.has("num_scan_inputs")); + layerParams.type = "Scan"; + + std::vector saved_inputs = node_inputs, saved_outputs = node_outputs; + std::vector > subgraphs(1); + for (int i = 0; i < node_proto.attribute_size(); ++i) + { + const auto& attr = node_proto.attribute(i); + if (attr.name() == "body") + { + opencv_onnx::GraphProto body = attr.g(); + subgraphs[0] = parseGraph(&body, false); + } + } + CV_Assert(!subgraphs[0].empty()); + node_inputs = saved_inputs; + node_outputs = saved_outputs; + + const int num_scan_inputs = layerParams.get("num_scan_inputs"); + const int n_state = (int)subgraphs[0]->inputs().size() - num_scan_inputs; + const int n_scan_out = (int)subgraphs[0]->outputs().size() - n_state; + if (n_state >= 0 && n_scan_out > 0) + { + std::vector scan_output_ranks(n_scan_out); + for (int k = 0; k < n_scan_out; ++k) + { + const Arg& a = subgraphs[0]->outputs()[n_state + k]; + scan_output_ranks[k] = netimpl->args.at(a.idx).shape.dims; + } + layerParams.set("scan_output_ranks", DictValue::arrayInt(scan_output_ranks.data(), n_scan_out)); + } + + addLayer(layerParams, node_proto); + Ptr& scanLayer = curr_prog.back(); + *scanLayer->subgraphs() = subgraphs; +} + void ONNXImporter2::parseIf(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { @@ -2718,6 +2765,7 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI() dispatch["GatherElements"] = &ONNXImporter2::parseGatherElements; dispatch["Concat"] = &ONNXImporter2::parseConcat; dispatch["Loop"] = &ONNXImporter2::parseLoop; + dispatch["Scan"] = &ONNXImporter2::parseScan; dispatch["If"] = &ONNXImporter2::parseIf; dispatch["Resize"] = &ONNXImporter2::parseResize2; dispatch["Size"] = &ONNXImporter2::parseSize; diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp index 8ff65040da..078f23467c 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp @@ -2423,8 +2423,12 @@ CASE(test_rms_normalization_4d_axis_negative_4_expanded) SKIP; CASE(test_rms_normalization_default_axis_expanded) SKIP; +CASE(test_scan9_multi_state) + SKIP; +CASE(test_scan9_scalar) + SKIP; CASE(test_scan9_sum) - // no filter + SKIP; CASE(test_scan_sum) // no filter CASE(test_scatter_elements_with_axis) diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_classic_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_classic_denylist.inl.hpp index 3563c1847f..c5804ba1cc 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_classic_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_classic_denylist.inl.hpp @@ -736,6 +736,9 @@ "test_reduce_sum_square_empty_set_expanded", "test_reduce_log_sum_exp_empty_set_expanded", "test_loop11", +"test_scan9_multi_state", // Scan supported only by the new graph engine +"test_scan9_scalar", // ---- same as above --- +"test_scan9_sum", // ---- same as above --- "test_eyelike_populate_off_main_diagonal", "test_eyelike_with_dtype", "test_eyelike_without_dtype", diff --git a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp index 10f24b277f..09e996596e 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp @@ -272,8 +272,8 @@ "test_reversesequence_batch", // Issue:: Parser: Can't create layer "onnx_node_output_0!y" of type "ReverseSequence" in function 'getLayerInstance' "test_reversesequence_time", // ---- same as above --- "test_rnn_seq_length", // Issue:: Parser: Can't create layer "onnx_node_output_1!Y_h" of type "RNN" in function 'getLayerInstance' -"test_scan9_sum", // Issue:: Parser: 'Graph' is not supported in function 'getLayerParams' -"test_scan_sum", // ---- same as above --- +// Scan edge cases beyond the opset-9+ dataflow the new engine supports: +"test_scan_sum", // opset-8 Scan (leading batch dim + sequence_lens, different semantics) "test_sequence_insert_at_back", // Issue:: Parser: typeProto.has_tensor_type() in function 'populateNet' "test_sequence_insert_at_front", // ---- same as above --- "test_sequence_map_add_1_sequence_1_tensor", @@ -420,9 +420,6 @@ "test_linear_attention_no_past_explicit_zeros_expanded", "test_linear_attention_prefill_with_past", "test_linear_attention_prefill_with_past_expanded", -// Scan op not supported -"test_scan9_multi_state", -"test_scan9_scalar", // misc unsupported (expanded subgraphs / new ops) "test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ_expanded", "test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FN_expanded",