1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-31 00:03:03 +04:00

Compare commits

..

81 Commits

Author SHA1 Message Date
Abhishek Gola 9548c7ea4e Merge pull request #29632 from ArneshBanerjee/fix-29568-extractchannel-inplace
core: restore in-place support for extractChannel (#29568)
2026-07-30 16:01:32 +05:30
Abhishek Gola 0f83d516bf Merge pull request #29630 from varun-jaiswal17/heavy_test_skip
skip test DNNTestNetwork.AlexNet/0 on 32-bit target
2026-07-30 15:02:28 +05:30
Abhishek Gola ccfa3a733b Merge pull request #29286 from abhishek-gola/cuda_layer_dispatch
CUDA support and Layer Split + per-op executors
2026-07-30 10:45:17 +05:30
Arnesh Banerjee ba5b40b1a2 core: restore in-place support for extractChannel (#29568)
extractChannel() stopped supporting in-place operation (dst aliasing
src) after commit 416bf3253 (PR #23473), which replaced

    Mat src = _src.getMat();
    _dst.create(src.dims, &src.size[0], depth);

with a single up-front

    _dst.createSameSize(_src, depth);
    ...
    Mat src = _src.getMat();

When _src and _dst reference the same array, the up-front reallocation
reshapes the shared buffer to a single channel before the multi-channel
source header is fetched. mixChannels() then sees a 1-channel source and
throws for any coi >= 1.

Move createSameSize() back to after the source Mat/UMat is obtained, so
the source header keeps the original multi-channel data alive across the
destination reallocation. This preserves the 0D/1D handling introduced
by createSameSize while restoring the pre-existing in-place contract.

Adds regression test Core_Mat.extractChannel_inplace_29568.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 03:09:43 +05:30
Abhishek Gola 3ef693c48a warning fix 2026-07-29 21:18:11 +05:30
Varun Jaiswal e0e52c14b1 restrict skip to windows 32 only 2026-07-29 20:38:18 +05:30
Abhishek Gola 34c478016d Adapt merged 5.x Scan helpers to the LayerInfo split
# Ptr<LayerInfo> over body->prog().
 # (matches sibling parseLoop/parseIf).
 # sliceScanAxis/stackScanAxis helpers (definition order only).
2026-07-29 20:32:42 +05:30
Abhishek Gola 58c28e1e82 wrapper-free GpuMatND forward path 2026-07-29 20:23:02 +05:30
Abhishek Gola 059a93339c code refactoring 2026-07-29 20:23:02 +05:30
Abhishek Gola 12fb9a6c24 using gpuMat instead of backend wrappers 2026-07-29 20:23:02 +05:30
Abhishek Gola 7f63fede4c added unsupported tests to denylist 2026-07-29 20:23:02 +05:30
Abhishek Gola e7dc3a9a9b added support check 2026-07-29 20:23:02 +05:30
Abhishek Gola 0843a0bb17 fusion fix 2026-07-29 20:23:02 +05:30
Abhishek Gola 2089d7fdb1 fixed test 2026-07-29 20:19:40 +05:30
Abhishek Gola dd3f3f6d9d build issue fixed 2026-07-29 20:19:40 +05:30
Abhishek Gola c48e3f7f3e changed OpData to LayerInfo 2026-07-29 20:19:40 +05:30
Abhishek Gola bc5c1b4da8 bug fix 2026-07-29 20:17:42 +05:30
Abhishek Gola cab931296f cleanup 2026-07-29 20:17:42 +05:30
Abhishek Gola 72ffdfc170 cuda support and Layer Split + per-op executors 2026-07-29 20:17:42 +05:30
Varun Jaiswal 204725ea18 skip AlexNet on 32-bit target 2026-07-29 18:15:37 +05:30
Abhishek Gola a5e4c04903 Merge pull request #29341 from abhishek-gola/disable_engine_classic
Remove ENGINE CLASSIC, switching to ENGINE NEW as default engine
2026-07-29 13:00:29 +05:30
Abhishek Gola 8e4e45eac3 Merge pull request #29451 from abhishek-gola/umat_optimization
Extending CUDA support in UMat
2026-07-29 11:19:35 +05:30
Abhishek Gola b94079a214 Merge pull request #29620 from ArneshBanerjee/fix-29615-icvcvt-pointer-overflow
imgcodecs: fix pointer overflow in 16u icvCvt helpers
2026-07-28 22:15:18 +05:30
Abhishek Gola 62732ab966 Merge pull request #29524 from abhishek-gola/onnx_coverage
Fix: 0-D / empty-Mat Python binding
2026-07-28 22:14:50 +05:30
Abhishek Gola 0a7288695f replaced ENGINE_NEW with ENGINE_OPENCV 2026-07-28 20:30:13 +05:30
Abhishek Gola f3a8f654c3 umat cuda integration 2026-07-28 20:28:24 +05:30
Abhishek Gola 51f7547bf1 refactoring 2026-07-28 20:27:29 +05:30
Abhishek Gola 57bcf14e78 moved to engine_opencv 2026-07-28 20:27:29 +05:30
Abhishek Gola c2e0e4edb9 removed engine classic related elements 2026-07-28 20:27:29 +05:30
Abhishek Gola 940810b589 java warning fix 2026-07-28 20:27:29 +05:30
Abhishek Gola 0bc2636564 fixed single output 2026-07-28 20:27:29 +05:30
Abhishek Gola 2c67582020 slope fix 2026-07-28 20:27:29 +05:30
Abhishek Gola ad8056f9b0 Support named output subset 2026-07-28 20:27:29 +05:30
Abhishek Gola 46331314a5 removed classic engine 2026-07-28 20:27:29 +05:30
Abhishek Gola 49b8f7c03f engine classic removed 2026-07-28 20:27:29 +05:30
Abhishek Gola 36d8bc4a44 Merge pull request #29622 from jeevan6996/fix-connectedcomponents-cv-bool
imgproc: accept CV_Bool masks in connected components
2026-07-28 11:00:53 +05:30
jeevan6996 e1f1495faa imgproc: accept CV_Bool masks in connected components 2026-07-27 18:54:59 +01:00
Arnesh Banerjee 7a25b1986d imgcodecs: fix pointer overflow in 16u icvCvt helpers
The 16-bit icvCvt_* helpers in utils.cpp advance the row pointer with
step/sizeof(x[0]) - size.width*N at the end of each row. sizeof is size_t
(unsigned), so the whole subtraction is done in unsigned math and wraps to
a huge offset when the caller passes step == 0.

The TIFF tile decoder does exactly that: it walks rows itself and calls
these helpers with step = 0 and Size(width, 1), so decoding a 16-bit
4-channel TIFF forms an out of range pointer. UBSan reports it as an
unsigned offset overflow (issue #29615).

Cast sizeof(...) to int so the step math is signed. The result is the same
for valid multi-row calls and no longer overflows when step is 0.

Added Imgcodecs_Tiff.regression_29615_16UC4 which round-trips a CV_16UC4
TIFF in memory.
2026-07-27 19:32:12 +05:30
Abhishek Gola 53d308c2a5 Merge pull request #29597 from biconcavelens/fix/distancetransform-bool-mask
imgproc: accept CV_Bool masks in distanceTransform 🤖🤖🤖
2026-07-27 10:59:36 +05:30
kirtijindal14 4ccee4a65f Merge pull request #29485 from kirtijindal14:opengl-core-highgui
Core, Highgui: OpenGL wrappers & window free-callback API #29485

This PR is a continuation of the work started in [20371](https://github.com/opencv/opencv/pull/20371)
### 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
2026-07-26 15:03:35 +03:00
Alexander Smorkalov 6e70e5d2a9 Merge pull request #29603 from asmorkalov:as/win32_odometry_warning
Fixed MSVS warning in the new odometry sample.
2026-07-26 14:14:14 +03:00
Alexander Smorkalov 4ba2752f36 Merge pull request #29601 from Teddy-Yangjiale:rvv-fast-attn
dnn: enable the existing SIMD attention-softmax on scalable RVV
2026-07-26 12:06:45 +03:00
Alexander Smorkalov 9fcdbaa8b5 Fixed MSVS warning in the new odometry sample. 2026-07-26 10:30:52 +03:00
velonica0 18b39f1f34 RVV: switch scalable universal intrinsics from LMUL=2 to LMUL=1 (#29493)
* dnn:rvv: restore the fixed block size C0 == 8

#29304 made the net-wide block size follow the RVV vector width
(defaultC0 = max(8, vlanes())), so the blocked memory layout of every tensor
depended on the host's VLEN and on the LMUL of the universal intrinsics in core.
The same model gets a different layout on a 128-, 256- or 1024-bit machine.

Restore the fixed default of 8, which is the mainstream, well-tested setting on
every platform. C0/K0 become compile-time constants again on RVV as they already
were elsewhere, so the runtime block-size plumbing and the vlanes()-based scratch
sizing are dropped, and getConvFunc_ no longer advertises block sizes the kernels
cannot handle.

The blocked kernels currently require C0 == vlanes(), which this alone does not
satisfy; the next commit removes that requirement.

* dnn:rvv: pin the vector length to the channel block

The blocked kernels assumed one channel block is exactly one vector
(C0 == vlanes()). That is not a property of the layout, it is a property of the
hardware, so the kernels only worked where the two happened to coincide: with
C0 fixed at 8 they break wherever vlanes() != 8 -- under-filling the block when
the vector is narrower (silently leaving output channels unwritten) and
overrunning it when the vector is wider (CV_Assert).

The universal intrinsics cannot express this: v_float32 is always vlanes() wide
and there is no way to make it span exactly C0 elements. So the RVV paths drop to
native intrinsics and set the vector length explicitly:

    const size_t _vl = __riscv_vsetvl_e32m2(C0);   // == 8 on every VLEN >= 128

LMUL=2 is required, not incidental: vlmax(e32,m2) = VLEN/32*2 >= 8 even at
VLEN=128, the narrowest RVV configuration, so _vl == C0 on every RVV 1.0 core.
One vector then spans precisely one channel block regardless of VLEN, and the
C0-vs-vlanes asserts and VLEN-dependent branches are removed rather than extended.
The existing int8 kernels already choose LMUL=2 for the same reason
(conv2_int8_kernels.simd.hpp).

This is a native per-kernel choice and is independent of the LMUL the universal
intrinsics are built with; dnn no longer reads the vector width of
intrin_rvv_scalable.hpp at all.

conv, deconv, maxpool, avgpool and depthwise use native intrinsics. batch_norm
stays portable and instead replicates the C0-wide coefficient pattern across the
vector, which keeps full lane utilisation where a fixed vl would idle most of it.
The x86/ARM paths are restored to their pre-#29304 form and are unchanged.

Tested on SpaceMIT K3 (X100 VLEN=256, A100 VLEN=1024), opencv_test_dnn: no
regressions against upstream on either core.

* core:rvv: switch scalable universal intrinsics from LMUL=2 to LMUL=1

Move the RVV universal-intrinsic types (v_uint8..v_float64) from m2 (paired
registers, 16 usable) to m1 (individual registers, 32 usable) per #29454, to
relieve register pressure in the complex kernels that rely on universal
intrinsics. Simple kernels that regress can take native RVV branches; the complex
ones cannot, so the default should suit them.

Only intrin_rvv_scalable.hpp changes. The widening/narrowing cascades and
max_nlanes are adjusted, and helpers that spell an LMUL into the intrinsic name
shift down one step (m2->m1, m1->mf2): v_popcount, v_lut (vector and f64 paths),
the f64 dot-product reduction (#27003), v_matmul, and the byte-indexed v_lut
added by #29250, whose __riscv_vluxei8_v_u8m2 no longer matches the m1 base type.

VLEN=1024 needs no change: under m1, max_nlanes = CV_RVV_MAX_VLEN/SZ equals
vlanes() at 1024, and CV_RVV_MAX_VLEN already defaults to 1024. Verified on a
1024-bit core.

Tested on SpaceMIT K3, which exposes both a 256-bit (X100) and a 1024-bit (A100)
core. opencv_test_core: 5605/5612, the 7 failures (filestorage_base64, globbing)
non-SIMD and failing identically on the m2 baseline. Perf (x-factor = m2/m1,
>1 = m1 faster), median >= 300us: core 0.966 -> 0.996 and imgproc 0.950 -> 1.038
as VLEN goes 256 -> 1024, i.e. m2's width advantage disappears once both are wide
while m1's register headroom persists. m1 wins resize (1.50/1.41), Exp (1.28),
gaussianBlur (1.23), norm (1.22), scharr (1.20); m2 still wins the light
memory-streaming kernels addWeighted (0.76), compare (0.82) and dot (0.87), which
are the natural candidates for native RVV branches.

* Revert "dnn:rvv: pin the vector length to the channel block"

This reverts commit 40587c6dd9.

* dnn:rvv: disable CV_SIMD_SCALABLE in the new-engine conv kernel

Under the m1 switch the RVV conv kernel breaks at VLEN=128: it assumes
K0 == vlanes(), but m1 makes vlanes()=4 while the block stays C0=K0=8, so it
computes only half of each output block. Temporarily disable the RVV
(CV_SIMD_SCALABLE) branches so conv runs scalar on RVV, exactly as #29180 did for
the other new-engine layers. The vectorized path will be restored in a follow-up.

Only conv is affected: maxpool/avgpool/depthwise/deconv/batch_norm already handle
C0 == 2*vlanes (the SSE/NEON case) and stay on their universal-intrinsic paths.

* dnn:rvv: document the supported VLEN range and the v_setvlmax follow-up

With the fixed C0=8 the RVV blocked kernels cover VLEN=128 and 256 (C0 >= vlanes);
VLEN>=512 (C0 < vlanes) is not yet handled. Add TODO notes recording this and
pointing at the planned fix: a portable v_setvlmax<VT>(n) universal intrinsic that
caps the vector to the block (no-op on fixed-width ISAs, sets the vl/mask on
RVV/SVE), with an optimized multi-pixel variant as a later step, possibly an RVV
HAL for the new dnn engine. See the #29493 discussion.

* dnn:rvv: disable CV_SIMD_SCALABLE in max/avg pooling and depthwise

These three blocked kernels assert (C0 == nlanes || ...) at VLEN>=512, where the
fixed C0=8 is narrower than the vector register — breaking whole networks
(AlexNet/ResNet/YOLO) on wide RVV cores, though CI (VLEN=128) is unaffected.
Disable their RVV (CV_SIMD_SCALABLE) paths so they run scalar on RVV at every
VLEN, matching #29180's scope. Verified on a SpaceMIT K3: this removes the 92
VLEN=1024 assert failures, with no change at VLEN=128/256.

deconv and batch_norm are left enabled: they degrade to scalar at VLEN>=512
(deconv's C0 % vlanes guard, batch_norm's vector-loop bound) without asserting,
so they stay correct and keep their 128/256 vectorization. All of these
re-vectorize uniformly via the v_setvlmax<VT>(n) follow-up.
2026-07-26 02:31:52 +03:00
Teddy-Yangjiale 73c580723c dnn: enable the existing SIMD attention-softmax on scalable RVV 2026-07-26 00:06:23 +08:00
Abhishek Gola fe482bd575 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
2026-07-25 11:39:47 +03:00
Alexander Smorkalov 859d550029 Merge pull request #29584 from Thebinary110:expose-handeye-to-python
calib: expose calibrateHandEye and calibrateRobotWorldHandEye to Python
2026-07-25 11:37:18 +03:00
Abhishek Gola b83e561526 Merge pull request #29579 from abhishek-gola:cumprod_causalconv_layers
Added Cumprod and Causalconv layers in new dnn engine #29579

### 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
2026-07-25 11:35:48 +03:00
Trishanth Mellimi 17d63dddf6 imgproc: remove obvious comment per review
Per asmorkalov's review on #29597.
2026-07-25 13:17:39 +05:30
Abhishek Gola b671e1e56f Merge pull request #29581 from varun-jaiswal17/fix_ci-warnings
fix MSVC C4293 warning in CV_ELEM_SIZE1 macro
2026-07-25 12:26:10 +05:30
Abhishek Gola 0d88e1933a Merge pull request #29331 from agrim-rai/slamVO
[GSOC] Added Visual Odometry (VO) for SLAM
2026-07-25 10:34:00 +05:30
Abhishek Gola e247376ee2 Merge pull request #29585 from Thebinary110/expose-window-parameters-to-python
highgui: expose saveWindowParameters and loadWindowParameters to Python
2026-07-25 10:30:51 +05:30
Trishanth Mellimi b41cda2b93 imgproc: accept CV_Bool masks in distanceTransform
cv::Mat_<bool>::depth() returns CV_Bool in 5.0 where it returned CV_8U in
4.x, so distanceTransform started rejecting boolean masks. CV_Bool is a
1-byte type whose values are only zero-tested here, so accept it wherever
a CV_8UC1 mask was previously accepted, matching the 4.x behaviour and the
4->5 migration guide.

Relaxes the type checks on all four reachable paths: the public wrapper,
distanceTransform_L1_8U, trueDistTrans (DIST_MASK_PRECISE) and
distanceATS_L1_8u (CV_8U output).

Closes #29596
2026-07-24 23:48:00 +05:30
Varun Jaiswal c3d4337abb perf: widen solvePnP tvec sanity tolerance 2026-07-24 18:36:35 +05:30
Agrim Rai aacac3d349 minor slamVO cleanup 2026-07-24 18:24:12 +05:30
Abhishek Gola 864f9594ad Merge pull request #29574 from Teddy-Yangjiale/gemm-fast
dnn: keep fastGemmThin accumulators in registers on scalable-vector targets (RVV)
2026-07-24 18:11:22 +05:30
Abhishek Gola a1084efef2 Merge pull request #29548 from vpisarev/no_ocl_fp16
dnn: map FP16 input types to FP32 in getFLOPS for the new graph engine
2026-07-24 15:06:59 +05:30
Abhishek Gola 4bace2fc28 Merge pull request #29580 from ArneshBanerjee/fix-29578-boundingrect-bool
geometry: fix boundingRect throwing for boolean masks
2026-07-24 14:13:07 +05:30
Thebinary110 9c487708f9 highgui test: force offscreen Qt platform to avoid CI crash
CI (Ubuntu ARM64 x2) showed the Python test suite aborting the whole
process with exit code 134: on a Qt-enabled build, namedWindow() tried
the "xcb" platform plugin, found no display (the Python test step,
unlike the C++ gtest step, does not run under xvfb-run), and Qt
aborted the process instead of raising a catchable exception -- so the
existing try/except around namedWindow never got a chance to run.

Fix: set QT_QPA_PLATFORM=offscreen (only if not already set) before
any GUI call. "offscreen" is the platform plugin Qt ships specifically
for headless/CI use, and is listed as available in the failing CI
job's own log output.

Verified locally: built opencv_python3 with WITH_QT=ON (real Qt6, not
the WITH_QT=OFF build used in the initial verification), and confirmed
that in a clean environment (no DISPLAY, no QT_QPA_PLATFORM preset),
namedWindow + saveWindowParameters + loadWindowParameters all succeed
with this fix, where they would previously depend on a native display
backend being reachable.
2026-07-24 12:31:53 +05:30
Thebinary110 8308fbf9ba highgui: expose saveWindowParameters and loadWindowParameters to Python
Both functions persist/restore a window's size, position, flags,
trackbar values, and zoom/pan state (Qt highgui backend only), take
only a plain String argument, and were already fully documented -- but
were declared CV_EXPORTS instead of CV_EXPORTS_W, so they were
unreachable from Python.

Sibling Qt-only functions in the same header (displayOverlay,
displayStatusBar) already follow the CV_EXPORTS_W + runtime
"not implemented without Qt" convention this change follows.

Adds a Python test that exercises namedWindow + save/loadWindowParameters
and gracefully skips when no GUI backend or no Qt support is present,
consistent with how other environment-dependent tests in this suite
(e.g. CUDA) skip rather than fail.

Verified locally: built opencv_python3 from this branch (WITH_QT=OFF)
and confirmed cv2.saveWindowParameters / cv2.loadWindowParameters are
present on the built module and correctly raise the expected
"compiled without QT support" error, proving the binding reaches the
C++ layer end-to-end.
2026-07-24 05:19:08 +05:30
Thebinary110 325e30db1d calib: expose calibrateHandEye and calibrateRobotWorldHandEye to Python
Both functions are fully implemented and documented but were tagged
CV_EXPORTS instead of CV_EXPORTS_W, so they were unreachable from
Python (and Java/JS) bindings. They use only bindable types
(InputArrayOfArrays, OutputArray, plain enums), so this is a pure
binding-exposure fix, not a behavior change.

Adds Python regression tests for both functions using synthetic
hand-eye data that satisfies the documented AX=XB (calibrateHandEye)
and AX=ZB (calibrateRobotWorldHandEye) relations, verifying the
recovered transform matches the synthetic ground truth.

Verified locally: built opencv_python3 bindings from this branch and
confirmed cv2.calibrateHandEye / cv2.calibrateRobotWorldHandEye are
now present and both new tests pass against synthetic ground-truth
poses.
2026-07-24 05:18:18 +05:30
Varun Jaiswal 757c40c81a fix MSVC C4293 warning in CV_ELEM_SIZE1 macro 2026-07-23 21:14:49 +05:30
Teddy-Yangjiale 1af832250b dnn: parameterize fastGemmThin accuracy test (TEST_P) 2026-07-23 23:40:52 +08:00
Arnesh Banerjee c219bb783d geometry: fix boundingRect throwing for boolean masks
In 5.0 cv::Mat_<bool>::depth() returns CV_Bool instead of CV_8U, so
boundingRect routed boolean masks into pointSetBoundingRect, which expects
a CV_32S/CV_32F point set and threw. CV_Bool is a 1-byte 0/1 mask type, so
handle it like CV_8U/CV_8S via the byte-wise maskBoundingRect path.
2026-07-23 19:32:36 +05:30
Abhishek Gola eccd1c43c4 Merge pull request #29572 from vrabaud/persistence2
Fix false MSAN warnings
2026-07-23 16:59:17 +05:30
Abhishek Gola c42778a0e8 Merge pull request #29560 from abhishek-gola:update_conformance_test_list
Updated ONNX conformance list #29560

updated ONNX conformance list with onnx version = 1.22.0

Current ONNX coverage in OpenCV ENGINE_NEW is now **72.1%**
 
Merge with: https://github.com/opencv/opencv_extra/pull/1398 
### 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
2026-07-23 10:43:35 +03:00
Teddy-Yangjiale 6b04d456b3 add accuracy test for fastGemmThin 2026-07-23 10:17:18 +08:00
Teddy-Yangjiale 7cc9947809 dnn: keep fastGemmThin accumulators in registers on scalable-vector targets (RVV) 2026-07-22 22:42:25 +08:00
Vincent Rabaud dcc3daf76b Fix false MSAN warnings 2026-07-22 11:44:27 +02:00
Abhishek Gola 319a03a2c9 added m.empty() 2026-07-22 14:22:40 +05:30
Vadim Pisarevsky fd4491fcb1 dnn: map FP16 input types to FP32 in getFLOPS for the new graph engine
The new graph engine has no FP16 execution path yet and runs FP32 on CPU
regardless of the requested target. When an OpenCL FP16 target is requested,
callers may pass CV_16F input types to Net::getFLOPS(), which then flow through
tryInferShapes() -> Layer::getTypes() and get rejected (CV_16F is only allowed
when preferableTarget == DNN_TARGET_OPENCL_FP16, which the new engine never sets).

Normalize FP16 input types to FP32 in the mainGraph path of getFLOPS via a small
filterFP16InputTypes() helper. This fixes the OCL_FP16 parameterization of the
DNNTestNetwork perf tests (e.g. YuNet_320/640/1280) which crashed in getFLOPS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 02:28:41 +03:00
Abhishek Gola ae392712b1 onnx coverage 2026-07-15 17:26:50 +05:30
Agrim Rai ccbc31a0f1 slam VO review2 minor changes/fixes 2026-07-14 03:48:16 +05:30
Agrim Rai 4ef179b4bf slam VO sample file refactor 2026-07-08 03:14:17 +05:30
Agrim Rai f552e8ac82 slam VO modular matcher changes/fixes 2026-07-07 21:12:37 +05:30
Agrim Rai 747a71fdfe slam:VO fix part 1 2026-07-07 15:06:18 +05:30
Agrim Rai bf5224cfa8 cv::slam output files fixed 2026-06-26 19:36:50 +05:30
Agrim Rai a02eb6ad0f better vo samples 2026-06-23 15:14:58 +05:30
Agrim Rai 65c93e673d slam files merge into ptcloud dir 2026-06-21 07:20:25 +05:30
Agrim Rai 08077a00a1 cleanupFinal 2026-06-17 13:15:57 +05:30
Agrim Rai 72b3fea7dd slam-vo init 2026-06-05 18:00:13 +05:30
178 changed files with 8439 additions and 5835 deletions
+6 -6
View File
@@ -53,7 +53,7 @@ std::string diagnosticKeys =
"{ model m | | Path to the model file. }"
"{ config c | | Path to the model configuration file. }"
"{ framework f | | [Optional] Name of the model framework. }"
"{ engine e | auto | [Optional] Graph negine selector: auto or classic or new}"
"{ engine e | auto | [Optional] DNN engine selector: auto (default), opencv or ort}"
"{ input0_name | | [Optional] Name of input0. Use with input0_shape}"
"{ input0_shape | | [Optional] Shape of input0. Use with input0_name}"
"{ input1_name | | [Optional] Name of input1. Use with input1_shape}"
@@ -104,13 +104,13 @@ int main( int argc, const char** argv )
std::string eng_name = argParser.get<std::string>("engine");
if(eng_name == "auto")
engine = dnn::ENGINE_AUTO;
else if(eng_name == "classic")
engine = dnn::ENGINE_CLASSIC;
else if(eng_name == "new")
engine = dnn::ENGINE_NEW;
else if(eng_name == "opencv")
engine = dnn::ENGINE_OPENCV;
else if(eng_name == "ort")
engine = dnn::ENGINE_ORT;
else
{
std::cerr << "Unknown DNN graph engine \"" << eng_name << "\"\n";
std::cerr << "Unknown DNN graph engine \"" << eng_name << "\" (use 'auto', 'opencv' or 'ort')\n";
return -1;
}
}
+2
View File
@@ -832,6 +832,8 @@ macro(ocv_glob_module_sources)
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/hal/*.h"
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/utils/*.hpp"
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/utils/*.h"
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/slam/*.hpp"
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/slam/*.h"
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/legacy/*.h"
)
file(GLOB lib_hdrs_detail
+1 -2
View File
@@ -678,9 +678,8 @@ def _redirect_orphan_duplicates(app, out_dir: pathlib.Path) -> None:
_DNN_ENGINE_LINKS = {
"readNet": "dnn.html#readnet",
"readNetFromONNX": "dnn.html#readnetfromonnx",
"ENGINE_NEW": "dnn.html#enginetype",
"ENGINE_CLASSIC": "dnn.html#enginetype",
"ENGINE_AUTO": "dnn.html#enginetype",
"ENGINE_OPENCV": "dnn.html#enginetype",
"ENGINE_ORT": "dnn.html#enginetype",
"EngineType": "dnn.html#enginetype",
"DNN_BACKEND_CUDA": "dnn.html#backend",
+2 -2
View File
@@ -1256,7 +1256,7 @@ A minimum of 2 motions with non parallel rotation axes are necessary to determin
So at least 3 different poses are required, but it is strongly recommended to use many more poses.
*/
CV_EXPORTS void calibrateHandEye( InputArrayOfArrays R_gripper2base, InputArrayOfArrays t_gripper2base,
CV_EXPORTS_W void calibrateHandEye( InputArrayOfArrays R_gripper2base, InputArrayOfArrays t_gripper2base,
InputArrayOfArrays R_target2cam, InputArrayOfArrays t_target2cam,
OutputArray R_cam2gripper, OutputArray t_cam2gripper,
HandEyeCalibrationMethod method=CALIB_HAND_EYE_TSAI );
@@ -1399,7 +1399,7 @@ This problem is also known as solving the \f$\mathbf{A}\mathbf{X}=\mathbf{Z}\mat
At least 3 measurements are required (input vectors size must be greater or equal to 3).
*/
CV_EXPORTS void calibrateRobotWorldHandEye( InputArrayOfArrays R_world2cam, InputArrayOfArrays t_world2cam,
CV_EXPORTS_W void calibrateRobotWorldHandEye( InputArrayOfArrays R_world2cam, InputArrayOfArrays t_world2cam,
InputArrayOfArrays R_base2gripper, InputArrayOfArrays t_base2gripper,
OutputArray R_base2world, OutputArray t_base2world,
OutputArray R_gripper2cam, OutputArray t_gripper2cam,
@@ -92,5 +92,85 @@ class calibration_test(NewOpenCVTests):
self.assertTrue(isinstance(dist, (float, np.floating)))
self.assertGreaterEqual(dist, 0.0)
@staticmethod
def _random_rt():
# random rotation (axis-angle) + random translation
axis = np.random.uniform(-1, 1, 3)
axis /= np.linalg.norm(axis)
angle = np.random.uniform(0.2, 1.0) * (1 if np.random.rand() > 0.5 else -1)
rvec = (axis * angle).astype(np.float64)
R, _ = cv.Rodrigues(rvec)
t = np.random.uniform(-100, 100, 3).astype(np.float64)
return R, t
@staticmethod
def _to_T(R, t):
T = np.eye(4)
T[:3, :3] = R
T[:3, 3] = t
return T
def test_calibrateHandEye(self):
# Ground truth camera-to-gripper transform (what calibrateHandEye should recover)
R_cam2gripper_gt, t_cam2gripper_gt = self._random_rt()
X = self._to_T(R_cam2gripper_gt, t_cam2gripper_gt)
# Arbitrary fixed target-to-base transform (the calibration board doesn't move)
R_target2base, t_target2base = self._random_rt()
Wt = self._to_T(R_target2base, t_target2base)
n_poses = 10
Rs_gripper2base, ts_gripper2base = [], []
Rs_target2cam, ts_target2cam = [], []
for _ in range(n_poses):
R_g2b, t_g2b = self._random_rt()
G = self._to_T(R_g2b, t_g2b)
# Consistent synthetic data: Wt == G @ X @ T_target2cam for every pose
T_target2cam = np.linalg.inv(X) @ np.linalg.inv(G) @ Wt
Rs_gripper2base.append(R_g2b)
ts_gripper2base.append(t_g2b)
Rs_target2cam.append(T_target2cam[:3, :3])
ts_target2cam.append(T_target2cam[:3, 3])
R_cam2gripper, t_cam2gripper = cv.calibrateHandEye(
Rs_gripper2base, ts_gripper2base, Rs_target2cam, ts_target2cam)
self.assertLess(cv.norm(R_cam2gripper - R_cam2gripper_gt, cv.NORM_L1), 1e-3)
self.assertLess(cv.norm(t_cam2gripper.flatten() - t_cam2gripper_gt, cv.NORM_L1), 1e-3)
def test_calibrateRobotWorldHandEye(self):
# Ground truth outputs (what calibrateRobotWorldHandEye should recover)
R_base2world_gt, t_base2world_gt = self._random_rt()
X = self._to_T(R_base2world_gt, t_base2world_gt)
R_gripper2cam_gt, t_gripper2cam_gt = self._random_rt()
Z = self._to_T(R_gripper2cam_gt, t_gripper2cam_gt)
n_poses = 10
Rs_world2cam, ts_world2cam = [], []
Rs_base2gripper, ts_base2gripper = [], []
for _ in range(n_poses):
R_b2g, t_b2g = self._random_rt()
B = self._to_T(R_b2g, t_b2g)
# Consistent synthetic data: A_i @ X == Z @ B_i => A_i = Z @ B_i @ inv(X)
A = Z @ B @ np.linalg.inv(X)
Rs_world2cam.append(A[:3, :3])
# NOTE: unlike calibrateHandEye, calibrateRobotWorldHandEye's Shah/Li
# solvers require translations as explicit (3,1) column vectors, not
# flat (3,) arrays -- passing (3,) raises a gemm shape-assertion error.
ts_world2cam.append(A[:3, 3].reshape(3, 1))
Rs_base2gripper.append(R_b2g)
ts_base2gripper.append(t_b2g.reshape(3, 1))
R_base2world, t_base2world, R_gripper2cam, t_gripper2cam = cv.calibrateRobotWorldHandEye(
Rs_world2cam, ts_world2cam, Rs_base2gripper, ts_base2gripper)
self.assertLess(cv.norm(R_base2world - R_base2world_gt, cv.NORM_L1), 1e-3)
self.assertLess(cv.norm(t_base2world.flatten() - t_base2world_gt, cv.NORM_L1), 1e-3)
self.assertLess(cv.norm(R_gripper2cam - R_gripper2cam_gt, cv.NORM_L1), 1e-3)
self.assertLess(cv.norm(t_gripper2cam.flatten() - t_gripper2cam_gt, cv.NORM_L1), 1e-3)
if __name__ == '__main__':
NewOpenCVTests.bootstrap()
@@ -1335,6 +1335,16 @@ private:
CV_EXPORTS_W void printCudaDeviceInfo(int device);
CV_EXPORTS_W void printShortCudaDeviceInfo(int device);
/** @brief Returns the MatAllocator that backs cv::UMat with CUDA device memory.
Bind it to a UMat before allocation to place the buffer on the current CUDA device:
@code
UMat u; u.allocator = cv::cuda::getCudaAllocator(); u.create(rows, cols, CV_32F);
@endcode
Returns NULL when OpenCV is built without CUDA. Mirrors cv::ocl::getOpenCLAllocator().
*/
CV_EXPORTS MatAllocator* getCudaAllocator();
//! @} cudacore_init
}} // namespace cv { namespace cuda {
+3 -2
View File
@@ -521,10 +521,11 @@ Cv64suf;
CV_16U - 2 bytes
...
*/
// '& 15' == '- 16' for depth in [16,31] here, but avoids a spurious MSVC C4293 on this ternary.
#define CV_ELEM_SIZE1(type) \
((int)((CV_MAT_DEPTH(type) < 16 \
? (0x1114881228442211ULL >> (CV_MAT_DEPTH(type) * 4)) \
: (0x0000000000000001ULL >> ((CV_MAT_DEPTH(type) - 16) * 4))) & 15))
? (0x1114881228442211ULL >> ((CV_MAT_DEPTH(type) & 15) * 4)) \
: (0x0000000000000001ULL >> ((CV_MAT_DEPTH(type) & 15) * 4))) & 15))
#define CV_ELEM_SIZE(type) (CV_MAT_CN(type)*CV_ELEM_SIZE1(type))
File diff suppressed because it is too large Load Diff
@@ -472,6 +472,244 @@ private:
Buffer texCoord_;
};
/** @brief Struct used to describe a vertex array attribute.
*/
struct CV_EXPORTS Attribute
{
/** @brief The target defines how you intend to use the buffer object.
*/
enum Type
{
BYTE = 0x1400, //!< 8-bit integer.
UNSIGNED_BYTE = 0x1401, //!< 8-bit unsigned integer.
SHORT = 0x1402, //!< 16-bit integer.
UNSIGNED_SHORT = 0x1403, //!< 16-bit unsigned integer.
INT = 0x1404, //!< 32-bit integer.
UNSIGNED_INT = 0x1405, //!< 32-bit unsigned integer.
FLOAT = 0x1406, //!< 32-bit floating point.
};
//! The buffer this attribute takes data from.
Buffer buffer_;
//! Number of bytes between each element described by this attribute.
size_t stride_;
//! Position of the first element in the buffer in bytes.
size_t offset_;
//! Number of components of the type (eg.: float = 1 component, vec3 = 3 components).
int size_;
//! Type of the vertex attribute. See cv::ogl::Attribute::Type.
Type type_;
//! Is the vertex attribute an integer?
bool integer_;
//! If the vertex attribute isn't an integer, should it be normalized?
bool normalized_;
//! Location of the attribute in the program. See cv::ogl::Program::getAttributeLocation.
unsigned int shader_loc_;
};
/** @brief Smart pointer for OpenGL vertex arrays with reference counting.
*/
class CV_EXPORTS VertexArray
{
public:
/** @brief The constructors.
Creates empty ogl::VertexArray object, creates ogl::VertexArray object from a list of attributes.
*/
VertexArray();
/** @overload
@param attributes List of attributes.
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
*/
VertexArray(std::initializer_list<Attribute> attributes, bool autoRelease = false);
/** @brief Creates ogl::VertexArray object from a list of attributes.
@param attributes List of attributes.
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
*/
void create(std::initializer_list<Attribute> attributes, bool autoRelease = false);
/** @brief Decrements the reference counter and destroys the vertex array object if needed.
The function will call setAutoRelease(true) .
*/
void release();
/** @brief Sets auto release mode.
The lifetime of the OpenGL object is tied to the lifetime of the context. If OpenGL context was
bound to a window it could be released at any time (user can close a window). If object's destructor
is called after destruction of the context it will cause an error. Thus ogl::VertexArray doesn't destroy
OpenGL object in destructor by default (all OpenGL resources will be released with OpenGL context).
This function can force ogl::VertexArray destructor to destroy OpenGL object.
@param flag Auto release mode (if true, release will be called in object's destructor).
*/
void setAutoRelease(bool flag);
/** @brief Binds OpenGL vertex array.
*/
void bind() const;
/** @brief Unbind any vertex array.
*/
static void unbind();
//! get OpenGL object id
unsigned int vaId() const;
class Impl;
private:
Ptr<Impl> impl_;
};
/** @brief Smart pointer for OpenGL shader with reference counting.
*/
class CV_EXPORTS Shader
{
public:
/** @brief The target defines how you intend to use the buffer object.
*/
enum ShaderType
{
FRAGMENT = 0x8B30, //!< The shader will be a fragment shader
VERTEX = 0x8B31, //!< The shader will be a vertex shader
};
/** @brief The constructors.
Creates empty ogl::Shader object, creates ogl::Shader object from source code.
*/
Shader();
/** @overload
@param src Shader source code.
@param type Shader type. See cv::ogl::Shader::ShaderType.
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
*/
Shader(const char* src, ShaderType type, bool autoRelease = false);
/** @brief Creates ogl::Shader object from source code.
@param src Shader source code.
@param type Shader type. See cv::ogl::Shader::ShaderType.
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
*/
void create(const char* src, ShaderType type, bool autoRelease = false);
/** @brief Decrements the reference counter and destroys the shader object if needed.
The function will call setAutoRelease(true) .
*/
void release();
/** @brief Sets auto release mode.
The lifetime of the OpenGL object is tied to the lifetime of the context. If OpenGL context was
bound to a window it could be released at any time (user can close a window). If object's destructor
is called after destruction of the context it will cause an error. Thus ogl::Shader doesn't destroy
OpenGL object in destructor by default (all OpenGL resources will be released with OpenGL context).
This function can force ogl::Shader destructor to destroy OpenGL object.
@param flag Auto release mode (if true, release will be called in object's destructor).
*/
void setAutoRelease(bool flag);
ShaderType type() const;
//! get OpenGL object id
unsigned int shaderId() const;
class Impl;
private:
Ptr<Impl> impl_;
ShaderType type_;
};
/** @brief Smart pointer for OpenGL program with reference counting.
*/
class CV_EXPORTS Program
{
public:
/** @brief The constructors.
Creates empty ogl::Program object, creates ogl::Program object from a group of shader objects.
*/
Program();
/** @overload
@param vert Vertex shader. See cv::ogl::Shader.
@param frag Fragment shader. See cv::ogl::Shader.
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
*/
Program(const Shader& vert, const Shader& frag, bool autoRelease = false);
/** @brief Creates ogl::Program object from a group of shader objects.
@param vert Vertex shader. See cv::ogl::Shader.
@param frag Fragment shader. See cv::ogl::Shader.
@param autoRelease Auto release mode (if true, release will be called in object's destructor).
*/
void create(const Shader& vert, const Shader& frag, bool autoRelease = false);
/** @brief Decrements the reference counter and destroys the program object if needed.
The function will call setAutoRelease(true) .
*/
void release();
/** @brief Sets auto release mode.
The lifetime of the OpenGL object is tied to the lifetime of the context. If OpenGL context was
bound to a window it could be released at any time (user can close a window). If object's destructor
is called after destruction of the context it will cause an error. Thus ogl::Program doesn't destroy
OpenGL object in destructor by default (all OpenGL resources will be released with OpenGL context).
This function can force ogl::Program destructor to destroy OpenGL object.
@param flag Auto release mode (if true, release will be called in object's destructor).
*/
void setAutoRelease(bool flag);
/** @brief Binds OpenGL shader program.
*/
void bind() const;
/** @brief Unbind any shader program.
*/
static void unbind();
/** @brief Returns the shader location of an attribute from its name.
@param name Attribute name.
*/
int getAttributeLocation(const char* name) const;
/** @brief Returns the shader location of an uniform from its name.
@param name Uniform name.
*/
int getUniformLocation(const char* name) const;
/** @brief Sets a uniform vector value.
@param location Uniform location.
@param vec Vector value.
*/
static void setUniformVec3(int location, Vec3f vec);
/** @brief Sets a uniform matrix value.
@param location Uniform location.
@param mat Matrix value.
*/
static void setUniformMat4x4(int location, Matx44f mat);
//! get OpenGL object id
unsigned int programId() const;
class Impl;
private:
Ptr<Impl> impl_;
};
/////////////////// Render Functions ///////////////////
//! render mode
@@ -488,6 +726,18 @@ enum RenderModes {
POLYGON = 0x0009
};
//! index type
enum IndexType {
UNSIGNED_BYTE = 0x1401,
UNSIGNED_SHORT = 0x1403,
UNSIGNED_INT = 0x1405,
};
//! capability
enum Capability {
DEPTH_TEST = 0x0B71,
};
/** @brief Render OpenGL texture or primitives.
@param tex Texture to draw.
@param wndRect Region of window, where to draw a texture (normalized coordinates).
@@ -512,6 +762,41 @@ CV_EXPORTS void render(const Arrays& arr, int mode = POINTS, Scalar color = Scal
*/
CV_EXPORTS void render(const Arrays& arr, InputArray indices, int mode = POINTS, Scalar color = Scalar::all(255));
/** @brief Clears the current target to a single color.
@param color New color.
*/
CV_EXPORTS void clearColor(Scalar color = Scalar::all(0));
/** @brief Clears the current target depth to a single value.
@param depth New depth.
*/
CV_EXPORTS void clearDepth(float depth);
/** @brief Renders primitives from array data.
@param first Index of the first vertex to draw.
@param count Number of vertices to draw.
@param mode Rendering mode. See cv::ogl::RenderModes.
*/
CV_EXPORTS void drawArrays(int first, int count, int mode);
/** @brief Renders primitives from array data with index buffer set.
@param first Byte offset of the first index in the bound element buffer.
@param count Number of indices to draw.
@param type Index type. See cv::ogl::IndexType.
@param mode Rendering mode. See cv::ogl::RenderModes.
*/
CV_EXPORTS void drawElements(int first, int count, int type, int mode);
/** @brief Enable server-side GL capabilities.
@param cap The capability to enable.
*/
CV_EXPORTS void enable(int cap);
/** @brief Disable server-side GL capabilities.
@param cap The capability to disable.
*/
CV_EXPORTS void disable(int cap);
/////////////////// CL-GL Interoperability Functions ///////////////////
namespace ocl {
+5 -1
View File
@@ -428,11 +428,14 @@ void cv::extractChannel(InputArray _src, OutputArray _dst, int coi)
CV_Assert( 0 <= coi && coi < cn );
int ch[] = { coi, 0 };
_dst.createSameSize(_src, depth);
#ifdef HAVE_OPENCL
if (ocl::isOpenCLActivated() && _src.dims() <= 2 && _dst.isUMat())
{
UMat src = _src.getUMat();
// NB: fetch the source before (re)allocating the destination so that
// in-place operation (_src and _dst referencing the same array) keeps
// the multi-channel source alive while dst is reshaped to 1 channel.
_dst.createSameSize(_src, depth);
UMat dst = _dst.getUMat();
mixChannels(std::vector<UMat>(1, src), std::vector<UMat>(1, dst), ch, 1);
return;
@@ -440,6 +443,7 @@ void cv::extractChannel(InputArray _src, OutputArray _dst, int coi)
#endif
Mat src = _src.getMat();
_dst.createSameSize(_src, depth);
Mat dst = _dst.getMat();
CV_IPP_RUN_FAST(ipp_extractChannel(src, dst, coi))
+188
View File
@@ -0,0 +1,188 @@
// 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.
// CUDA-backed MatAllocator for UMat (Phase P1): synchronous host<->device transfers.
#include "precomp.hpp"
namespace cv { namespace cuda {
#ifndef HAVE_CUDA
MatAllocator* getCudaAllocator()
{
return NULL;
}
#else
class CudaUMatAllocator CV_FINAL : public MatAllocator
{
public:
UMatData* allocate(int dims, const int* sizes, int type, void* data0,
size_t* step, AccessFlag /*flags*/, UMatUsageFlags /*usageFlags*/) const CV_OVERRIDE
{
const size_t elemSize = CV_ELEM_SIZE(type);
size_t total = elemSize;
for (int i = 0; i < dims; i++)
total *= (size_t)sizes[i];
if (step)
{
step[dims - 1] = elemSize;
for (int i = dims - 2; i >= 0; i--)
step[i] = step[i + 1] * (size_t)sizes[i + 1];
}
UMatData* u = new UMatData(this);
u->size = total;
if (total > 0)
cudaSafeCall(cudaMalloc(&u->handle, total));
if (data0)
{
u->data = u->origdata = static_cast<uchar*>(data0);
u->flags |= UMatData::USER_ALLOCATED;
u->markDeviceCopyObsolete(true);
}
else
{
u->markHostCopyObsolete(true);
}
return u;
}
bool allocate(UMatData* u, AccessFlag /*accessFlags*/, UMatUsageFlags /*usageFlags*/) const CV_OVERRIDE
{
if (!u)
return false;
if (!u->handle && u->size > 0)
{
cudaSafeCall(cudaMalloc(&u->handle, u->size));
if (u->data)
u->markDeviceCopyObsolete(true);
else
u->markHostCopyObsolete(true);
}
return true;
}
void deallocate(UMatData* u) const CV_OVERRIDE
{
if (!u)
return;
if (u->handle)
cudaSafeCall(cudaFree(u->handle));
if (u->data && !(u->flags & UMatData::USER_ALLOCATED))
fastFree(u->data);
delete u;
}
void map(UMatData* u, AccessFlag accessFlags) const CV_OVERRIDE
{
if (!u)
return;
if (!u->data && u->size > 0)
u->data = u->origdata = static_cast<uchar*>(fastMalloc(u->size));
if (u->hostCopyObsolete() && u->handle && u->data)
{
cudaSafeCall(cudaMemcpy(u->data, u->handle, u->size, cudaMemcpyDeviceToHost));
u->markHostCopyObsolete(false);
}
if (!!(accessFlags & ACCESS_WRITE))
u->markDeviceCopyObsolete(true);
}
void unmap(UMatData* u) const CV_OVERRIDE
{
if (!u)
return;
if (u->deviceCopyObsolete() && u->handle && u->data)
{
cudaSafeCall(cudaMemcpy(u->handle, u->data, u->size, cudaMemcpyHostToDevice));
u->markDeviceCopyObsolete(false);
}
}
void download(UMatData* u, void* dstptr, int dims, const size_t sz[],
const size_t srcofs[], const size_t srcstep[],
const size_t dststep[]) const CV_OVERRIDE
{
if (!u || !u->handle || !dstptr)
return;
copyPlanes((uchar*)u->handle, srcofs, srcstep, (uchar*)dstptr, 0, dststep,
dims, sz, cudaMemcpyDeviceToHost);
}
void upload(UMatData* u, const void* srcptr, int dims, const size_t sz[],
const size_t dstofs[], const size_t dststep[],
const size_t srcstep[]) const CV_OVERRIDE
{
if (!u || !srcptr)
return;
if (!u->handle && u->size > 0)
cudaSafeCall(cudaMalloc(&u->handle, u->size));
copyPlanes((uchar*)srcptr, NULL, srcstep, (uchar*)u->handle, dstofs, dststep,
dims, sz, cudaMemcpyHostToDevice);
u->markHostCopyObsolete(true);
u->markDeviceCopyObsolete(false);
}
// Same-world D2D only; UMat::copyTo calls copy() solely when both share this allocator.
void copy(UMatData* src, UMatData* dst, int dims, const size_t sz[],
const size_t srcofs[], const size_t srcstep[],
const size_t dstofs[], const size_t dststep[], bool /*sync*/) const CV_OVERRIDE
{
if (!src || !dst || !src->handle || !dst->handle)
return;
copyPlanes((uchar*)src->handle, srcofs, srcstep, (uchar*)dst->handle, dstofs, dststep,
dims, sz, cudaMemcpyDeviceToDevice);
dst->markHostCopyObsolete(true);
dst->markDeviceCopyObsolete(false);
}
private:
// sz/steps follow the MatAllocator convention: the last dim carries element bytes.
static void copyPlanes(uchar* srcbase, const size_t srcofs[], const size_t srcstep[],
uchar* dstbase, const size_t dstofs[], const size_t dststep[],
int dims, const size_t sz[], cudaMemcpyKind kind)
{
int isz[CV_MAX_DIM];
uchar* srcptr = srcbase;
uchar* dstptr = dstbase;
for (int i = 0; i < dims; i++)
{
CV_Assert(sz[i] <= (size_t)INT_MAX);
if (sz[i] == 0)
return;
if (srcofs)
srcptr += srcofs[i] * (i <= dims - 2 ? srcstep[i] : 1);
if (dstofs)
dstptr += dstofs[i] * (i <= dims - 2 ? dststep[i] : 1);
isz[i] = (int)sz[i];
}
Mat src(dims, isz, CV_8U, srcptr, srcstep);
Mat dst(dims, isz, CV_8U, dstptr, dststep);
const Mat* arrays[] = { &src, &dst };
uchar* ptrs[2];
NAryMatIterator it(arrays, ptrs, 2);
const size_t planesz = it.size;
for (size_t j = 0; j < it.nplanes; j++, ++it)
cudaSafeCall(cudaMemcpy(ptrs[1], ptrs[0], planesz, kind));
}
};
MatAllocator* getCudaAllocator()
{
CV_SINGLETON_LAZY_INIT(CudaUMatAllocator, new CudaUMatAllocator())
}
#endif
}} // namespace cv { namespace cuda {
+3
View File
@@ -379,6 +379,7 @@ lapack_QR(fptype* a, size_t a_step, int m, int n, int k, fptype* b, size_t b_ste
else if (typeid(fptype) == typeid(double))
OCV_LAPACK_FUNC(dgels)(mode, &_m, &_n, &_k, (double*)tmpA, &ldtmpA, (double*)b, &_m, (double*)&work1, &lwork, _info);
CV_ANNOTATE_MEMORY_IS_INITIALIZED(info, sizeof(int));
if (*info < 0)
return CV_HAL_ERROR_NOT_IMPLEMENTED;
@@ -403,6 +404,7 @@ lapack_QR(fptype* a, size_t a_step, int m, int n, int k, fptype* b, size_t b_ste
else if (typeid(fptype) == typeid(double))
OCV_LAPACK_FUNC(dgels)(mode, &_m, &_n, &_k, (double*)tmpA, &ldtmpA, (double*)tmpB, &_m, (double*)&work1, &lwork, _info);
CV_ANNOTATE_MEMORY_IS_INITIALIZED(info, sizeof(int));
if (*info < 0)
return CV_HAL_ERROR_NOT_IMPLEMENTED;
@@ -425,6 +427,7 @@ lapack_QR(fptype* a, size_t a_step, int m, int n, int k, fptype* b, size_t b_ste
else if (typeid(fptype) == typeid(double))
dgeqrf_(&_m, &_n, (double*)tmpA, &ldtmpA, (double*)dst, (double*)&work1, &lwork, _info);
CV_ANNOTATE_MEMORY_IS_INITIALIZED(info, sizeof(int));
if (*info < 0)
return CV_HAL_ERROR_NOT_IMPLEMENTED;
+632 -2
View File
@@ -316,6 +316,8 @@ public:
void copyFrom(GLsizeiptr size, const GLvoid* data);
void copyTo(GLsizeiptr size, GLvoid* data) const;
void resize(GLsizeiptr size, GLenum target);
void* mapHost(GLenum access);
void unmapHost();
@@ -407,6 +409,19 @@ void cv::ogl::Buffer::Impl::copyFrom(GLsizeiptr size, const GLvoid* data)
CV_CheckGlError();
}
void cv::ogl::Buffer::Impl::resize(GLsizeiptr size, GLenum target)
{
gl::BindBuffer(target, bufId_);
CV_CheckGlError();
// keep the same buffer id so bound vertex arrays stay valid
gl::BufferData(target, size, 0, gl::DYNAMIC_DRAW);
CV_CheckGlError();
gl::BindBuffer(target, 0);
CV_CheckGlError();
}
void cv::ogl::Buffer::Impl::copyTo(GLsizeiptr size, GLvoid* data) const
{
gl::BindBuffer(gl::COPY_READ_BUFFER, bufId_);
@@ -547,8 +562,15 @@ void cv::ogl::Buffer::create(int arows, int acols, int atype, Target target, boo
#else
if (rows_ != arows || cols_ != acols || type_ != atype)
{
const GLsizeiptr asize = arows * acols * CV_ELEM_SIZE(atype);
impl_.reset(new Impl(asize, 0, target, autoRelease));
const GLsizeiptr asize = (GLsizeiptr)arows * acols * CV_ELEM_SIZE(atype);
// resize in place when sole owner (keeps the GL id); else allocate fresh (copy-on-write)
if (impl_->bufId() != 0 && impl_.use_count() == 1)
{
impl_->resize(asize, target);
impl_->setAutoRelease(autoRelease);
}
else
impl_.reset(new Impl(asize, 0, target, autoRelease));
rows_ = arows;
cols_ = acols;
type_ = atype;
@@ -1411,6 +1433,551 @@ void cv::ogl::Arrays::bind() const
#endif
}
////////////////////////////////////////////////////////////////////////
// ogl::VertexArray
#ifndef HAVE_OPENGL
class cv::ogl::VertexArray::Impl
{
};
#else
class cv::ogl::VertexArray::Impl
{
public:
static const Ptr<Impl>& empty();
Impl(GLuint vaId, bool autoRelease);
Impl(std::initializer_list<Attribute> attributes, bool autoRelease);
~Impl();
void bind();
void setAutoRelease(bool flag) { autoRelease_ = flag; }
GLuint vaId() const { return vaId_; }
private:
Impl();
GLuint vaId_;
bool autoRelease_;
};
const Ptr<cv::ogl::VertexArray::Impl>& cv::ogl::VertexArray::Impl::empty()
{
static Ptr<Impl> p(new Impl);
return p;
}
cv::ogl::VertexArray::Impl::Impl() : vaId_(0), autoRelease_(false)
{
}
cv::ogl::VertexArray::Impl::Impl(GLuint vaId, bool autoRelease) : vaId_(vaId), autoRelease_(autoRelease)
{
CV_Assert( gl::IsVertexArray(vaId) == gl::TRUE_ );
}
cv::ogl::VertexArray::Impl::Impl(std::initializer_list<Attribute> attributes, bool autoRelease) : vaId_(0), autoRelease_(autoRelease)
{
gl::GenVertexArrays(1, &vaId_);
CV_CheckGlError();
CV_Assert( vaId_ != 0 );
gl::BindVertexArray(vaId_);
CV_CheckGlError();
for (auto& attribute : attributes)
{
attribute.buffer_.bind(Buffer::ARRAY_BUFFER);
if (attribute.integer_)
{
gl::VertexAttribIPointer(
attribute.shader_loc_,
attribute.size_,
attribute.type_,
static_cast<GLsizei>(attribute.stride_),
reinterpret_cast<const void*>(attribute.offset_)
);
CV_CheckGlError();
}
else
{
gl::VertexAttribPointer(
attribute.shader_loc_,
attribute.size_,
attribute.type_,
attribute.normalized_,
static_cast<GLsizei>(attribute.stride_),
reinterpret_cast<const void*>(attribute.offset_)
);
CV_CheckGlError();
}
gl::EnableVertexAttribArray(attribute.shader_loc_);
CV_CheckGlError();
}
}
cv::ogl::VertexArray::Impl::~Impl()
{
if (autoRelease_ && vaId_)
gl::DeleteVertexArrays(1, &vaId_);
}
void cv::ogl::VertexArray::Impl::bind()
{
gl::BindVertexArray(vaId_);
CV_CheckGlError();
}
#endif // HAVE_OPENGL
cv::ogl::VertexArray::VertexArray()
{
#ifndef HAVE_OPENGL
throw_no_ogl();
#else
impl_ = Impl::empty();
#endif
}
cv::ogl::VertexArray::VertexArray(std::initializer_list<Attribute> attributes, bool autoRelease)
{
#ifndef HAVE_OPENGL
CV_UNUSED(attributes); CV_UNUSED(autoRelease);
throw_no_ogl();
#else
impl_.reset(new Impl(attributes, autoRelease));
#endif
}
void cv::ogl::VertexArray::create(std::initializer_list<Attribute> attributes, bool autoRelease)
{
#ifndef HAVE_OPENGL
CV_UNUSED(attributes); CV_UNUSED(autoRelease);
throw_no_ogl();
#else
impl_.reset(new Impl(attributes, autoRelease));
#endif
}
void cv::ogl::VertexArray::release()
{
#ifdef HAVE_OPENGL
if (impl_)
impl_->setAutoRelease(true);
impl_ = Impl::empty();
#endif
}
void cv::ogl::VertexArray::setAutoRelease(bool flag)
{
#ifndef HAVE_OPENGL
CV_UNUSED(flag);
throw_no_ogl();
#else
impl_->setAutoRelease(flag);
#endif
}
void cv::ogl::VertexArray::bind() const
{
#ifndef HAVE_OPENGL
throw_no_ogl();
#else
impl_->bind();
#endif
}
void cv::ogl::VertexArray::unbind()
{
#ifndef HAVE_OPENGL
throw_no_ogl();
#else
gl::BindVertexArray(0);
#endif
}
unsigned int cv::ogl::VertexArray::vaId() const
{
#ifndef HAVE_OPENGL
throw_no_ogl();
#else
return impl_->vaId();
#endif
}
////////////////////////////////////////////////////////////////////////
// ogl::Shader
#ifndef HAVE_OPENGL
class cv::ogl::Shader::Impl
{
};
#else
class cv::ogl::Shader::Impl
{
public:
static const Ptr<Impl>& empty();
Impl(GLuint shaderId, bool autoRelease);
Impl(const char* src, GLenum type, bool autoRelease);
~Impl();
void setAutoRelease(bool flag) { autoRelease_ = flag; }
GLuint shaderId() const { return shaderId_; }
private:
Impl();
GLuint shaderId_;
bool autoRelease_;
};
const Ptr<cv::ogl::Shader::Impl>& cv::ogl::Shader::Impl::empty()
{
static Ptr<Impl> p(new Impl);
return p;
}
cv::ogl::Shader::Impl::Impl() : shaderId_(0), autoRelease_(false)
{
}
cv::ogl::Shader::Impl::Impl(GLuint shaderId, bool autoRelease) : shaderId_(shaderId), autoRelease_(autoRelease)
{
CV_Assert( gl::IsShader(shaderId) == gl::TRUE_ );
}
cv::ogl::Shader::Impl::Impl(const char* src, GLenum type, bool autoRelease) : shaderId_(0), autoRelease_(autoRelease)
{
shaderId_ = gl::CreateShader(type);
CV_CheckGlError();
CV_Assert( shaderId_ != 0 );
GLint status;
gl::ShaderSource(shaderId_, 1, &src, 0);
gl::CompileShader(shaderId_);
gl::GetShaderiv(shaderId_, gl::COMPILE_STATUS, &status);
if (!status) {
String info_log;
gl::GetShaderiv(shaderId_, gl::INFO_LOG_LENGTH, &status);
info_log.resize(status);
gl::GetShaderInfoLog(shaderId_, GLsizei(info_log.size()) + 1, nullptr, &info_log[0]);
CV_Error(Error::OpenGlApiCallError, info_log);
}
CV_CheckGlError();
}
cv::ogl::Shader::Impl::~Impl()
{
if (autoRelease_ && shaderId_)
gl::DeleteShader(shaderId_);
}
#endif // HAVE_OPENGL
cv::ogl::Shader::Shader() : type_(ShaderType::VERTEX)
{
#ifndef HAVE_OPENGL
throw_no_ogl();
#else
impl_ = Impl::empty();
#endif
}
cv::ogl::Shader::Shader(const char* src, ShaderType type, bool autoRelease) : type_(type)
{
#ifndef HAVE_OPENGL
CV_UNUSED(src); CV_UNUSED(type); CV_UNUSED(autoRelease);
throw_no_ogl();
#else
impl_.reset(new Impl(src, type, autoRelease));
#endif
}
void cv::ogl::Shader::create(const char* src, ShaderType type, bool autoRelease)
{
#ifndef HAVE_OPENGL
CV_UNUSED(src); CV_UNUSED(type); CV_UNUSED(autoRelease);
throw_no_ogl();
#else
impl_.reset(new Impl(src, type, autoRelease));
type_ = type;
#endif
}
void cv::ogl::Shader::release()
{
#ifdef HAVE_OPENGL
if (impl_)
impl_->setAutoRelease(true);
impl_ = Impl::empty();
#endif
}
void cv::ogl::Shader::setAutoRelease(bool flag)
{
#ifndef HAVE_OPENGL
CV_UNUSED(flag);
throw_no_ogl();
#else
impl_->setAutoRelease(flag);
#endif
}
unsigned int cv::ogl::Shader::shaderId() const
{
#ifndef HAVE_OPENGL
throw_no_ogl();
#else
return impl_->shaderId();
#endif
}
cv::ogl::Shader::ShaderType cv::ogl::Shader::type() const
{
return type_;
}
////////////////////////////////////////////////////////////////////////
// ogl::Program
#ifndef HAVE_OPENGL
class cv::ogl::Program::Impl
{
};
#else
class cv::ogl::Program::Impl
{
public:
static const Ptr<Impl>& empty();
Impl(GLuint programId, bool autoRelease);
Impl(const Shader& vert, const Shader& frag, bool autoRelease);
~Impl();
void bind();
int getAttributeLocation(const char* name) const;
int getUniformLocation(const char* name) const;
void setAutoRelease(bool flag) { autoRelease_ = flag; }
GLuint programId() const { return programId_; }
private:
Impl();
Shader vert_;
Shader frag_;
GLuint programId_;
bool autoRelease_;
};
const Ptr<cv::ogl::Program::Impl>& cv::ogl::Program::Impl::empty()
{
static Ptr<Impl> p(new Impl);
return p;
}
cv::ogl::Program::Impl::Impl() : programId_(0), autoRelease_(false)
{
}
cv::ogl::Program::Impl::Impl(GLuint programId, bool autoRelease) : programId_(programId), autoRelease_(autoRelease)
{
CV_Assert( gl::IsProgram(programId) == gl::TRUE_ );
}
cv::ogl::Program::Impl::Impl(const Shader& vert, const Shader& frag, bool autoRelease) : programId_(0), autoRelease_(autoRelease)
{
programId_ = gl::CreateProgram();
CV_CheckGlError();
CV_Assert( programId_ != 0 );
gl::AttachShader(programId_, vert.shaderId());
CV_CheckGlError();
gl::AttachShader(programId_, frag.shaderId());
CV_CheckGlError();
GLint status;
gl::LinkProgram(programId_);
gl::GetProgramiv(programId_, gl::LINK_STATUS, &status);
if (!status) {
String info_log;
gl::GetProgramiv(programId_, gl::INFO_LOG_LENGTH, &status);
info_log.resize(status);
gl::GetProgramInfoLog(programId_, GLsizei(info_log.size()) + 1, nullptr, &info_log[0]);
CV_Error(Error::OpenGlApiCallError, info_log);
}
CV_CheckGlError();
vert_ = vert;
frag_ = frag;
}
cv::ogl::Program::Impl::~Impl()
{
if (autoRelease_ && programId_)
gl::DeleteProgram(programId_);
}
void cv::ogl::Program::Impl::bind()
{
gl::UseProgram(programId_);
CV_CheckGlError();
}
int cv::ogl::Program::Impl::getAttributeLocation(const char* name) const
{
// -1 if absent (e.g. optimized out); caller decides.
int location = gl::GetAttribLocation(programId_, name);
CV_CheckGlError();
return location;
}
int cv::ogl::Program::Impl::getUniformLocation(const char* name) const
{
// -1 if absent (e.g. optimized out); caller decides.
int location = gl::GetUniformLocation(programId_, name);
CV_CheckGlError();
return location;
}
#endif // HAVE_OPENGL
cv::ogl::Program::Program()
{
#ifndef HAVE_OPENGL
throw_no_ogl();
#else
impl_ = Impl::empty();
#endif
}
cv::ogl::Program::Program(const Shader& vert, const Shader& frag, bool autoRelease)
{
#ifndef HAVE_OPENGL
CV_UNUSED(vert); CV_UNUSED(frag); CV_UNUSED(autoRelease);
throw_no_ogl();
#else
impl_.reset(new Impl(vert, frag, autoRelease));
#endif
}
void cv::ogl::Program::create(const Shader& vert, const Shader& frag, bool autoRelease)
{
#ifndef HAVE_OPENGL
CV_UNUSED(vert); CV_UNUSED(frag); CV_UNUSED(autoRelease);
throw_no_ogl();
#else
impl_.reset(new Impl(vert, frag, autoRelease));
#endif
}
void cv::ogl::Program::release()
{
#ifdef HAVE_OPENGL
if (impl_)
impl_->setAutoRelease(true);
impl_ = Impl::empty();
#endif
}
void cv::ogl::Program::setAutoRelease(bool flag)
{
#ifndef HAVE_OPENGL
CV_UNUSED(flag);
throw_no_ogl();
#else
impl_->setAutoRelease(flag);
#endif
}
void cv::ogl::Program::bind() const
{
#ifndef HAVE_OPENGL
throw_no_ogl();
#else
impl_->bind();
#endif
}
void cv::ogl::Program::unbind()
{
#ifndef HAVE_OPENGL
throw_no_ogl();
#else
gl::UseProgram(0);
#endif
}
int cv::ogl::Program::getAttributeLocation(const char* name) const
{
#ifndef HAVE_OPENGL
CV_UNUSED(name);
throw_no_ogl();
#else
return impl_->getAttributeLocation(name);
#endif
}
int cv::ogl::Program::getUniformLocation(const char* name) const
{
#ifndef HAVE_OPENGL
CV_UNUSED(name);
throw_no_ogl();
#else
return impl_->getUniformLocation(name);
#endif
}
void cv::ogl::Program::setUniformVec3(int loc, Vec3f vec)
{
#ifndef HAVE_OPENGL
CV_UNUSED(loc); CV_UNUSED(vec);
throw_no_ogl();
#else
gl::Uniform3f(loc, vec(0), vec(1), vec(2));
#endif
}
void cv::ogl::Program::setUniformMat4x4(int loc, Matx44f mat)
{
#ifndef HAVE_OPENGL
CV_UNUSED(loc); CV_UNUSED(mat);
throw_no_ogl();
#else
gl::UniformMatrix4fv(loc, 1, true, mat.val);
#endif
}
unsigned int cv::ogl::Program::programId() const
{
#ifndef HAVE_OPENGL
throw_no_ogl();
#else
return impl_->programId();
#endif
}
////////////////////////////////////////////////////////////////////////
// Rendering
@@ -1577,6 +2144,69 @@ void cv::ogl::render(const ogl::Arrays& arr, InputArray indices, int mode, Scala
#endif
}
void cv::ogl::clearColor(Scalar color)
{
#ifndef HAVE_OPENGL
CV_UNUSED(color);
throw_no_ogl();
#else
gl::ClearColor(static_cast<GLfloat>(color[0] / 255.0), static_cast<GLfloat>(color[1] / 255.0),
static_cast<GLfloat>(color[2] / 255.0), static_cast<GLfloat>(color[3] / 255.0));
gl::Clear(gl::COLOR_BUFFER_BIT);
#endif // HAVE_OPENGL
}
void cv::ogl::clearDepth(float depth)
{
#ifndef HAVE_OPENGL
CV_UNUSED(depth);
throw_no_ogl();
#else
gl::ClearDepth(depth);
gl::Clear(gl::DEPTH_BUFFER_BIT);
#endif // HAVE_OPENGL
}
void cv::ogl::drawArrays(int first, int count, int mode)
{
#ifndef HAVE_OPENGL
CV_UNUSED(first); CV_UNUSED(count); CV_UNUSED(mode);
throw_no_ogl();
#else
gl::DrawArrays(mode, first, count);
#endif // HAVE_OPENGL
}
void cv::ogl::drawElements(int first, int count, int type, int mode)
{
#ifndef HAVE_OPENGL
CV_UNUSED(first); CV_UNUSED(count); CV_UNUSED(type); CV_UNUSED(mode);
throw_no_ogl();
#else
gl::DrawElements(mode, count, type, reinterpret_cast<const void*>(static_cast<size_t>(first)));
#endif
}
void cv::ogl::enable(int cap)
{
#ifndef HAVE_OPENGL
CV_UNUSED(cap);
throw_no_ogl();
#else
gl::Enable(cap);
#endif
}
void cv::ogl::disable(int cap)
{
#ifndef HAVE_OPENGL
CV_UNUSED(cap);
throw_no_ogl();
#else
gl::Disable(cap);
#endif
}
////////////////////////////////////////////////////////////////////////
// CL-GL Interoperability
+231
View File
@@ -0,0 +1,231 @@
// 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 "test_precomp.hpp"
#ifdef HAVE_CUDA
#include <opencv2/core/cuda.hpp>
#include <opencv2/core/ocl.hpp>
namespace opencv_test { namespace {
static void skipIfNoCudaDevice()
{
if (cv::cuda::getCudaEnabledDeviceCount() <= 0)
throw SkipTestException("No CUDA device available");
}
static UMat makeCudaUMat()
{
UMat u;
u.allocator = cv::cuda::getCudaAllocator();
return u;
}
TEST(Core_CudaUMat, roundtrip_contiguous)
{
skipIfNoCudaDevice();
ASSERT_TRUE(cv::cuda::getCudaAllocator() != NULL);
Mat src(64, 48, CV_32FC1);
randu(src, 0, 1);
UMat u = makeCudaUMat();
src.copyTo(u);
ASSERT_TRUE(u.u != NULL && u.u->handle != NULL);
Mat dst;
u.copyTo(dst);
EXPECT_EQ(src.size(), dst.size());
EXPECT_EQ(src.type(), dst.type());
EXPECT_EQ(0.0, cvtest::norm(src, dst, NORM_INF));
}
TEST(Core_CudaUMat, roundtrip_multichannel)
{
skipIfNoCudaDevice();
Mat src(37, 53, CV_8UC3);
randu(src, 0, 255);
UMat u = makeCudaUMat();
src.copyTo(u);
Mat dst;
u.copyTo(dst);
EXPECT_EQ(0.0, cvtest::norm(src, dst, NORM_INF));
}
TEST(Core_CudaUMat, roundtrip_roi_source)
{
skipIfNoCudaDevice();
Mat full(100, 100, CV_32FC1);
randu(full, 0, 1);
Mat roi = full(Rect(10, 12, 50, 40));
ASSERT_FALSE(roi.isContinuous());
UMat u = makeCudaUMat();
roi.copyTo(u);
Mat dst;
u.copyTo(dst);
EXPECT_EQ(roi.size(), dst.size());
EXPECT_EQ(0.0, cvtest::norm(roi, dst, NORM_INF));
}
TEST(Core_CudaUMat, download_into_roi)
{
skipIfNoCudaDevice();
Mat src(40, 50, CV_32FC1);
randu(src, 0, 1);
UMat u = makeCudaUMat();
src.copyTo(u);
Mat canvas(80, 90, CV_32FC1, Scalar(0));
Mat dstRoi = canvas(Rect(5, 7, 50, 40));
ASSERT_FALSE(dstRoi.isContinuous());
u.copyTo(dstRoi);
EXPECT_EQ(0.0, cvtest::norm(src, dstRoi, NORM_INF));
}
TEST(Core_CudaUMat, device_to_device_copy)
{
skipIfNoCudaDevice();
Mat src(70, 90, CV_32FC1);
randu(src, 0, 1);
UMat a = makeCudaUMat();
src.copyTo(a);
UMat b = makeCudaUMat();
a.copyTo(b);
Mat dst;
b.copyTo(dst);
EXPECT_EQ(0.0, cvtest::norm(src, dst, NORM_INF));
}
// Proves the allocator's handle is a real, kernel-usable CUDA buffer via GpuMat interop.
TEST(Core_CudaUMat, kernel_interop_via_gpumat)
{
skipIfNoCudaDevice();
Mat src(32, 40, CV_32FC1, Scalar(1.0f));
UMat u = makeCudaUMat();
src.copyTo(u);
ASSERT_TRUE(u.u != NULL && u.u->handle != NULL);
cv::cuda::GpuMat g(u.rows, u.cols, u.type(), u.u->handle,
(size_t)u.cols * CV_ELEM_SIZE(u.type()));
Mat viaGpu;
g.download(viaGpu);
EXPECT_EQ(0.0, cvtest::norm(src, viaGpu, NORM_INF));
g.setTo(Scalar(7.0f));
u.u->markHostCopyObsolete(true); // device was mutated behind UMat's back
Mat afterKernel;
u.copyTo(afterKernel);
EXPECT_EQ(0.0, cvtest::norm(Mat(src.size(), src.type(), Scalar(7.0f)), afterKernel, NORM_INF));
}
// P1 gives a UMat storage on CUDA, not GPU execution. With OpenCL on, cv:: ops feed the
// CUDA pointer to the driver as a cl_mem and segfault; the tests below force OpenCL off so
// ops resolve through the host getMat() fallback, and pin why the OpenCL path is unusable.
struct NoOpenCL
{
bool prev;
NoOpenCL() : prev(cv::ocl::useOpenCL()) { cv::ocl::setUseOpenCL(false); }
~NoOpenCL() { cv::ocl::setUseOpenCL(prev); }
};
// A CUDA UMat fails the OpenCL kernel path's isCLBuffer precondition (carries the CUDA allocator).
TEST(Core_CudaUMat, is_not_opencl_buffer)
{
skipIfNoCudaDevice();
UMat u = makeCudaUMat();
Mat(8, 8, CV_32FC1, Scalar(1)).copyTo(u);
ASSERT_TRUE(u.u != NULL);
EXPECT_EQ(cv::cuda::getCudaAllocator(), u.u->currAllocator);
#ifdef HAVE_OPENCL
EXPECT_NE((MatAllocator*)cv::ocl::getOpenCLAllocator(), u.u->currAllocator);
#endif
}
TEST(Core_CudaUMat, op_arithmetic_host_fallback)
{
skipIfNoCudaDevice();
NoOpenCL guard;
Mat a(50, 40, CV_32FC1), b(50, 40, CV_32FC1);
randu(a, 0, 10); randu(b, 0, 10);
UMat ua = makeCudaUMat(), ub = makeCudaUMat();
a.copyTo(ua); b.copyTo(ub);
UMat usum = makeCudaUMat(), udiff = makeCudaUMat(), uprod = makeCudaUMat();
cv::add(ua, ub, usum);
cv::subtract(ua, ub, udiff);
cv::multiply(ua, ub, uprod);
Mat sum, diff, prod;
usum.copyTo(sum); udiff.copyTo(diff); uprod.copyTo(prod);
EXPECT_LE(cvtest::norm(sum, a + b, NORM_INF), 1e-5);
EXPECT_LE(cvtest::norm(diff, a - b, NORM_INF), 1e-5);
EXPECT_LE(cvtest::norm(prod, a.mul(b), NORM_INF), 1e-4);
}
TEST(Core_CudaUMat, op_convertTo_host_fallback)
{
skipIfNoCudaDevice();
NoOpenCL guard;
Mat a(30, 45, CV_32FC1);
randu(a, 0, 100);
UMat ua = makeCudaUMat();
a.copyTo(ua);
UMat u8 = makeCudaUMat();
ua.convertTo(u8, CV_8U, 2.0, 1.0);
Mat got, expected;
u8.copyTo(got);
a.convertTo(expected, CV_8U, 2.0, 1.0);
EXPECT_EQ(0.0, cvtest::norm(got, expected, NORM_INF));
}
TEST(Core_CudaUMat, op_addWeighted_host_fallback)
{
skipIfNoCudaDevice();
NoOpenCL guard;
Mat a(40, 40, CV_32FC1), b(40, 40, CV_32FC1);
randu(a, 0, 5); randu(b, 0, 5);
UMat ua = makeCudaUMat(), ub = makeCudaUMat();
a.copyTo(ua); b.copyTo(ub);
UMat ur = makeCudaUMat();
cv::addWeighted(ua, 2.0, ub, 1.0, 0.0, ur);
Mat got, expected;
ur.copyTo(got);
cv::addWeighted(a, 2.0, b, 1.0, 0.0, expected);
EXPECT_LE(cvtest::norm(got, expected, NORM_INF), 1e-4);
}
}} // namespace
#endif // HAVE_CUDA
+11
View File
@@ -1381,6 +1381,17 @@ TEST(Core_Mat, regression_9507)
EXPECT_EQ(25u, m2.total());
}
TEST(Core_Mat, extractChannel_inplace_29568)
{
// https://github.com/opencv/opencv/issues/29568
// extractChannel() must support in-place operation (dst aliasing src).
Mat mat(3, 3, CV_8UC3, Scalar(1, 2, 3));
ASSERT_NO_THROW(cv::extractChannel(mat, mat, 1));
EXPECT_EQ(1, mat.channels());
EXPECT_EQ(Size(3, 3), mat.size());
EXPECT_EQ(0, cv::norm(mat, Mat(3, 3, CV_8UC1, Scalar(2)), NORM_INF));
}
TEST(Core_Mat, empty)
{
// Should not crash.
+24 -38
View File
@@ -6,31 +6,27 @@ OpenCV 5 introduces a selectable inference backend for the DNN module, referred
The engine is specified at model-load time and cannot be changed after the model has been loaded, as each engine uses a different internal graph representation.
### ENGINE_NEW
`ENGINE_NEW` is a ground-up rewrite of the DNN inference graph, introduced in OpenCV 5. It is built around a typed operation graph with shape inference, constant folding, and operator fusion, covering approximately 75-80% of the ONNX operator specification. Models that failed to load under OpenCV 4.x due to dynamic shapes or unsupported operators will typically load and run correctly under this engine.
The engine performs automatic attention fusion: the `MatMul``Softmax``MatMul` subgraph common to transformer architectures is recognised and collapsed into a single fused operation at load time, with no changes required to the model or calling code.
`ENGINE_NEW` also introduces native support for Large Language Models and Vision-Language Models. Built-in tokenizers, attention layers, decoding blocks, and KV-caching allow models such as Qwen, Gemma, and PaliGemma to run end-to-end through the standard `Net` API, with no external runtime required.
In OpenCV 5.0, `ENGINE_NEW` runs on CPU only. Support for CUDA and other non-CPU backends is planned for a subsequent release. Users requiring GPU acceleration should use `ENGINE_CLASSIC` or `ENGINE_ORT`.
### ENGINE_CLASSIC
`ENGINE_CLASSIC` is the inference engine carried over from OpenCV 4.x. It supports the full set of DNN backends and hardware targets, including `DNN_BACKEND_CUDA` for NVIDIA GPUs, `DNN_BACKEND_OPENVINO` for Intel hardware, and FP16 inference targets. Its ONNX operator coverage is approximately 22% of the specification. Models with dynamic shapes or transformer-style subgraphs will generally not load under this engine.
@note The Darknet and Caffe parsers have been removed in OpenCV 5; ONNX is the recommended format for all engines. TFLite models are still supported and currently executed via `ENGINE_CLASSIC`.
### ENGINE_AUTO
`ENGINE_AUTO` is the default value for the engine parameter on all `readNet*()` functions. When active, OpenCV first attempts to load the model with `ENGINE_NEW`. If the model cannot be loaded - for example, because it uses an operator not yet implemented in the new engine - the load is automatically retried with `ENGINE_CLASSIC`.
`ENGINE_AUTO` is the default value for the engine parameter on all `readNet*()` functions. It lets OpenCV pick the engine: it currently resolves to `ENGINE_OPENCV`. The resolution is intentionally an implementation detail and may change as more engines are added, so code that does not need a specific engine should leave the default in place.
Because `ENGINE_AUTO` is the default, existing code that does not pass an engine argument requires no modification.
### ENGINE_OPENCV
`ENGINE_OPENCV` is OpenCV's built-in DNN engine, a ground-up rewrite of the inference graph introduced in OpenCV 5. It is built around a typed operation graph with shape inference, constant folding, and operator fusion, covering approximately 75-80% of the ONNX operator specification. Models that failed to load under OpenCV 4.x due to dynamic shapes or unsupported operators will typically load and run correctly under this engine.
The engine performs automatic attention fusion: the `MatMul``Softmax``MatMul` subgraph common to transformer architectures is recognised and collapsed into a single fused operation at load time, with no changes required to the model or calling code.
`ENGINE_OPENCV` also introduces native support for Large Language Models and Vision-Language Models. Built-in tokenizers, attention layers, decoding blocks, and KV-caching allow models such as Qwen, Gemma, and PaliGemma to run end-to-end through the standard `Net` API, with no external runtime required.
In OpenCV 5.0, `ENGINE_OPENCV` runs on CPU only. Support for CUDA and other non-CPU backends is planned for a subsequent release. Users requiring GPU acceleration should use `ENGINE_ORT`.
@note The Darknet and Caffe parsers have been removed in OpenCV 5; ONNX is the recommended format. TFLite and TensorFlow models are still supported and are executed via `ENGINE_OPENCV`.
### ENGINE_ORT
`ENGINE_ORT` routes inference through a bundled ONNX Runtime (ORT) wrapper. OpenCV uses its own ONNX parser to construct the ORT graph internally, so only the ORT library is required at runtime, not the standalone onnx package.
`ENGINE_ORT` routes inference through a bundled ONNX Runtime (ORT) wrapper. OpenCV uses its own ONNX parser to construct the ORT graph internally, so only the ORT library is required at runtime, not the standalone onnx package. It applies to ONNX models only.
`ENGINE_ORT` must be enabled at compile time:
@@ -42,7 +38,7 @@ cmake -DWITH_ONNXRUNTIME=ON ..
cmake -DWITH_ONNXRUNTIME=ON -DDOWNLOAD_ONNXRUNTIME_GPU=ON ..
```
ORT execution providers are supported, including CUDA for NVIDIA hardware. This makes `ENGINE_ORT` the recommended choice for GPU-accelerated inference on models that `ENGINE_CLASSIC` cannot load, while native GPU support for `ENGINE_NEW` is still in development.
ORT execution providers are supported, including CUDA for NVIDIA hardware. This makes `ENGINE_ORT` the recommended choice for GPU-accelerated inference while native GPU support for `ENGINE_OPENCV` is still in development.
### Selecting an Engine
@@ -50,18 +46,13 @@ The engine parameter is accepted by the `readNet*()` family of functions.
@add_toggle_cpp
@code{.cpp}
// ENGINE_AUTO is the default
// ENGINE_AUTO is the default (resolves to ENGINE_OPENCV)
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx");
// Force the new engine
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx", cv::dnn::ENGINE_NEW);
// Explicitly select OpenCV's built-in engine
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx", cv::dnn::ENGINE_OPENCV);
// Force the classic engine with CUDA backend
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx", cv::dnn::ENGINE_CLASSIC);
net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
// Use ONNX Runtime
// Use ONNX Runtime (ONNX models only, requires WITH_ONNXRUNTIME=ON)
cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx", cv::dnn::ENGINE_ORT);
@endcode
@end_toggle
@@ -70,18 +61,13 @@ cv::dnn::Net net = cv::dnn::readNetFromONNX("model.onnx", cv::dnn::ENGINE_ORT);
@code{.py}
import cv2
# ENGINE_AUTO is the default
# ENGINE_AUTO is the default (resolves to ENGINE_OPENCV)
net = cv2.dnn.readNetFromONNX("model.onnx")
# Force the new engine
net = cv2.dnn.readNetFromONNX("model.onnx", engine=cv2.dnn.ENGINE_NEW)
# Explicitly select OpenCV's built-in engine
net = cv2.dnn.readNetFromONNX("model.onnx", engine=cv2.dnn.ENGINE_OPENCV)
# Force the classic engine with CUDA backend
net = cv2.dnn.readNetFromONNX("model.onnx", engine=cv2.dnn.ENGINE_CLASSIC)
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
# Use ONNX Runtime
# Use ONNX Runtime (ONNX models only, requires WITH_ONNXRUNTIME=ON)
net = cv2.dnn.readNetFromONNX("model.onnx", engine=cv2.dnn.ENGINE_ORT)
@endcode
@end_toggle
@@ -90,7 +76,7 @@ The engine cannot be changed after a model has been loaded. To use a different e
### Engine Selection via Environment Variable
The engine can be overridden at the process level using the `OPENCV_FORCE_DNN_ENGINE` environment variable. The integer values correspond directly to the `EngineType` enum: 1 for `ENGINE_CLASSIC`, 2 for `ENGINE_NEW`, 3 for `ENGINE_AUTO`, and 4 for `ENGINE_ORT`.
The engine can be overridden at the process level using the `OPENCV_FORCE_DNN_ENGINE` environment variable. The integer values correspond directly to the `EngineType` enum: 0 for `ENGINE_AUTO`, 1 for `ENGINE_OPENCV`, and 2 for `ENGINE_ORT`. Leaving the variable unset (or set to `ENGINE_AUTO`) does not force any engine, so the value passed to `readNet*()` is honored.
```bash
# Linux / macOS
@@ -858,6 +858,28 @@ CV__DNN_INLINE_NS_BEGIN
static Ptr<LoopLayer> 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<int>& scanInputAxes() const = 0;
/** Per-scan-output axis to stack along (empty => 0 for all). */
virtual const std::vector<int>& scanOutputAxes() const = 0;
/** Per-scan-input direction, 1 = reverse (empty => forward for all). */
virtual const std::vector<int>& scanInputDirections() const = 0;
/** Per-scan-output direction, 1 = reverse (empty => forward for all). */
virtual const std::vector<int>& 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<int>& scanOutputRanks() const = 0;
/** Factory: creates a ScanLayer implementation. */
static Ptr<ScanLayer> create(const LayerParams& params);
};
class CV_EXPORTS ConcatLayer : public Layer
{
public:
@@ -1101,6 +1123,8 @@ CV__DNN_INLINE_NS_BEGIN
{
public:
static Ptr<Layer> create(const LayerParams& params);
// Set the per-channel slope when it arrives as a second input, not a blob.
virtual void setSlope(const Mat& /*slope*/) {}
};
class CV_EXPORTS ELULayer : public ActivationLayer
@@ -1920,6 +1944,16 @@ CV__DNN_INLINE_NS_BEGIN
static Ptr<AttentionOnnxAiLayer> create(const LayerParams &params);
};
class CV_EXPORTS CausalConvWithStateLayer : public Layer {
public:
static Ptr<CausalConvWithStateLayer> create(const LayerParams &params);
};
class CV_EXPORTS CumProdLayer : public Layer {
public:
static Ptr<CumProdLayer> create(const LayerParams &params);
};
class CV_EXPORTS GroupNormLayer : public Layer {
public:
static Ptr<GroupNormLayer> create(const LayerParams &params);
+115 -123
View File
@@ -262,23 +262,91 @@ CV__DNN_INLINE_NS_BEGIN
class CV_EXPORTS Graph;
class CV_EXPORTS ActivationLayer;
/** @brief This interface class allows to build new Layers - are building blocks of networks.
/** @brief Backend-independent description of a graph operation (node).
*
* Each class, derived from Layer, must implement forward() method to compute outputs.
* Also before using the new layer into networks you must register your layer by using one of @ref dnnLayerFactory "LayerFactory" macros.
* %LayerInfo carries everything needed to reason about an operation *without* executing it:
* its parameters (#blobs and type-specific fields of derived classes), graph wiring
* (#inputs / #outputs as Arg indices) and shape/type/layout inference. The new DNN graph
* engine stores a topologically sorted sequence of %LayerInfo nodes (see Graph::prog());
* executable, backend-specific instances (Layer subclasses) are constructed from an
* %LayerInfo during Net::finalizeNet().
*
* Each operation type registers a `static Ptr<LayerInfo> create(const LayerParams&)` factory
* via @ref CV_DNN_REGISTER_OP_CLASS.
*/
class CV_EXPORTS_W Layer : public Algorithm
class CV_EXPORTS_W LayerInfo
{
public:
LayerInfo();
explicit LayerInfo(const LayerParams& params);
virtual ~LayerInfo();
void setParamsFrom(const LayerParams& params);
//! List of learned parameters must be stored here to allow read them by using Net::getParam().
CV_PROP_RW std::vector<Mat> blobs;
std::vector<Arg> inputs;
std::vector<Arg> outputs;
void* netimpl;
void* netimpl = nullptr;
CV_PROP String name;
CV_PROP String type;
virtual std::vector<Ptr<Graph> >* subgraphs() const;
virtual int inputNameToIndex(String inputName); // FIXIT const
CV_WRAP virtual int outputNameToIndex(const String& outputName); // FIXIT const
virtual bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
std::vector<MatShape> &internals) const;
virtual void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs,
const int requiredInternals,
std::vector<MatType>&outputs,
std::vector<MatType>&internals) const;
virtual int getLayouts(const std::vector<DataLayout>& actualInputs,
std::vector<DataLayout>& desiredInputs,
const int requiredOutputs,
std::vector<DataLayout>& outputs) const;
virtual int64 getFLOPS(const std::vector<MatShape> &inputs,
const std::vector<MatShape> &outputs) const;
virtual bool updateMemoryShapes(const std::vector<MatShape> &inputs);
virtual bool alwaysSupportInplace() const;
virtual bool dynamicOutputShapes() const;
virtual bool isDataShuffling() const;
virtual void getScaleShift(Mat& scale, Mat& shift) const;
virtual void getScaleZeropoint(float& scale, int& zeropoint) const;
virtual std::ostream& dumpAttrs(std::ostream& strm, int indent) const;
virtual std::ostream& dump(std::ostream& strm, int indent, bool comma) const;
};
/** @brief This interface class allows to build new Layers - are building blocks of networks.
*
* A %Layer is the *executable*, backend-specific counterpart of a LayerInfo node: it
* implements forward() (and finalize()) for a particular backend/target. In the new graph
* engine a %Layer is created from a LayerInfo by the executor factory; shape/type inference
* stays on the LayerInfo node.
*
* Each class, derived from Layer, must implement forward() method to compute outputs.
* Also before using the new layer into networks you must register your layer by using one of @ref dnnLayerFactory "LayerFactory" macros.
*/
class CV_EXPORTS_W Layer : public LayerInfo
{
public:
/** @brief Computes and sets internal parameters according to inputs, outputs and blobs.
* @deprecated Use Layer::finalize(InputArrayOfArrays, OutputArrayOfArrays) instead
* @param[in] input vector of already allocated input blobs
@@ -341,18 +409,6 @@ CV__DNN_INLINE_NS_BEGIN
CV_DEPRECATED CV_WRAP void run(const std::vector<Mat> &inputs, CV_OUT std::vector<Mat> &outputs,
CV_IN_OUT std::vector<Mat> &internals);
/** @brief Returns index of input blob into the input array.
* @param inputName label of input blob
*
* Each layer input and output can be labeled to easily identify them using "%<layer_name%>[.output_name]" notation.
* This method maps label of input blob to its index into input vector.
*/
virtual int inputNameToIndex(String inputName); // FIXIT const
/** @brief Returns index of output blob in output array.
* @see inputNameToIndex()
*/
CV_WRAP virtual int outputNameToIndex(const String& outputName); // FIXIT const
/**
* @brief Ask layer if it support specific backend for doing computations.
* @param[in] backendId computation backend identifier.
@@ -379,6 +435,20 @@ CV__DNN_INLINE_NS_BEGIN
const std::vector<Ptr<BackendWrapper>>& outputs
);
/**
* @brief Returns a CUDA backend node for the new graph engine (wrapper-free).
*
* Inputs and outputs are device tensors (arrays of cuda::GpuMatND) carrying shape and type;
* only that metadata is needed to build the node, the buffers are filled later by
* forwardCUDA(). The default adapts the wrapper-based initCUDA() so classic-engine ops keep
* working. @p context is a void pointer to a CSLContext object.
*/
virtual Ptr<BackendNode> initCUDA(
void *context,
InputArrayOfArrays inputs,
InputArrayOfArrays outputs
);
/**
* @brief Returns a TimVX backend node
*
@@ -419,102 +489,26 @@ CV__DNN_INLINE_NS_BEGIN
virtual bool tryFuse(Ptr<Layer>& top);
/**
* @brief Returns parameters of layers with channel-wise multiplication and addition.
* @param[out] scale Channel-wise multipliers. Total number of values should
* be equal to number of channels.
* @param[out] shift Channel-wise offsets. Total number of values should
* be equal to number of channels.
* @brief Executes the operation on the CUDA backend (new graph engine).
*
* Some layers can fuse their transformations with further layers.
* In example, convolution + batch normalization. This way base layer
* use weights from layer after it. Fused layer is skipped.
* By default, @p scale and @p shift are empty that means layer has no
* element-wise multiplications or additions.
* Called by the engine for nodes assigned to DNN_BACKEND_CUDA. Inputs and outputs are
* device-resident tensors passed as arrays of cuda::GpuMatND; no backend wrappers are
* involved. The default implementation raises an error. @p workspace is an opaque pointer
* to a cuda4dnn::csl::Workspace (kept void* to avoid leaking internal CUDA types).
*/
virtual void getScaleShift(Mat& scale, Mat& shift) const;
/**
* @brief Returns scale and zeropoint of layers
* @param[out] scale Output scale
* @param[out] zeropoint Output zeropoint
*
* By default, @p scale is 1 and @p zeropoint is 0.
*/
virtual void getScaleZeropoint(float& scale, int& zeropoint) const;
virtual void forwardCUDA(InputArrayOfArrays inputs,
OutputArrayOfArrays outputs,
void* workspace);
/**
* @brief "Detaches" all the layers, attached to particular layer.
*/
virtual void unsetAttached();
virtual bool getMemoryShapes(const std::vector<MatShape> &inputs,
const int requiredOutputs,
std::vector<MatShape> &outputs,
std::vector<MatShape> &internals) const;
virtual void getTypes(const std::vector<MatType>& inputs,
const int requiredOutputs,
const int requiredInternals,
std::vector<MatType>&outputs,
std::vector<MatType>&internals) const;
// this is the method for Layer to express its attitude to the block layout
// or any other special form of layout. It takes
// layouts of the inputs and should return the desired layouts of
// inputs, as well as layouts of the outputs.
// By default, no mater what the actual inputs' layouts are,
// the desired inputs as well as outputs will get 'Unknown' layout values.
// It means that the layer can only handle non-block layout
// (depending on the model format, e.g. NCHW for ONNX or NHWC for TFLite)
// and will return tensors with non-block layout as well.
// Some layers could override this default behaviour:
// a) if they _can_ process block-layout data, like element-wise operations, or
// b) if they _need_ block-layout data, like convolution
virtual int getLayouts(const std::vector<DataLayout>& actualInputs,
std::vector<DataLayout>& desiredInputs,
const int requiredOutputs,
std::vector<DataLayout>& outputs) const;
virtual int64 getFLOPS(const std::vector<MatShape> &inputs,
const std::vector<MatShape> &outputs) const;
virtual bool updateMemoryShapes(const std::vector<MatShape> &inputs);
// returns true if the operation takes a single input and can always be performed in-place,
// assuming that the input is contiguous.
// Examples of such operations are: Reshape, Flatten, Squeeze, Unsqueeze,
// as well many unary element-wise operations (ReLU, Tanh, ...)
virtual bool alwaysSupportInplace() const;
// returns false if the shape of Layer outputs is defined only by the shapes of inputs.
// Sometimes the shape depends on the content of the input(s), then the method should return true.
// In such a rare case forward() method should take care of proper allocation of the output tensors.
// On the other hand, when this method returns false, the engine takes care of proper allocation of the outputs,
// so that forward() can assume that the outputs are already allocated.
virtual bool dynamicOutputShapes() const;
// returns true if the layer only rearranges data without changing values.
// Examples: Flatten, Reshape, Transpose, Permute, Squeeze, Unsqueeze,
// Concat, Split, Slice, Tile, MaxPool.
// Used by QDQ fusion to elide redundant dequantize-quantize pairs
// when the scale and zero point are the same.
virtual bool isDataShuffling() const;
// dumps attributes of the layer (e.g. strides, dilations in Convolution, MaxPool)
virtual std::ostream& dumpAttrs(std::ostream& strm, int indent) const;
// dumps information about the layer. The default implementation is usually good enough,
// just override dumpAttrs().
virtual std::ostream& dump(std::ostream& strm, int indent, bool comma) const;
CV_PROP String name; //!< Name of the layer instance, can be used for logging or other internal purposes.
CV_PROP String type; //!< Type name which was used for creating layer by layer factory.
CV_PROP int preferableTarget; //!< prefer target for layer forwarding
Layer();
explicit Layer(const LayerParams &params); //!< Initializes only #name, #type and #blobs fields.
void setParamsFrom(const LayerParams &params); //!< Initializes only #name, #type and #blobs fields.
virtual ~Layer();
};
@@ -533,15 +527,16 @@ CV__DNN_INLINE_NS_BEGIN
virtual bool empty() const = 0;
virtual void clear() = 0;
virtual std::string name() const = 0;
virtual const std::vector<Arg>& append(Ptr<Layer>& layer,
virtual const std::vector<Arg>& append(Ptr<LayerInfo>& op,
const std::vector<std::string>& outnames=std::vector<std::string>()) = 0;
virtual Arg append(Ptr<Layer>& layer, const std::string& outname=std::string()) = 0;
virtual Arg append(Ptr<LayerInfo>& op, const std::string& outname=std::string()) = 0;
virtual std::ostream& dump(std::ostream& strm, int indent, bool comma) = 0;
virtual const std::vector<Arg>& inputs() const = 0;
virtual const std::vector<Arg>& outputs() const = 0;
virtual void setOutputs(const std::vector<Arg>& outputs) = 0;
virtual const std::vector<Ptr<Layer> >& prog() const = 0;
virtual void setProg(const std::vector<Ptr<Layer> >& newprog) = 0;
virtual const std::vector<Ptr<LayerInfo> >& prog() const = 0;
virtual void setProg(const std::vector<Ptr<LayerInfo> >& newprog) = 0;
virtual int opBackend(int opidx) const = 0;
};
/** @brief This class allows to create and manipulate comprehensive artificial neural networks.
@@ -835,7 +830,7 @@ CV__DNN_INLINE_NS_BEGIN
*/
CV_WRAP void setParam(int layer, int numParam, CV_ND const Mat &blob);
/** @brief Sets the parameter blob of a layer identified by its name or output tensor name.
* @param layerName layer name (classic engine) or raw ONNX output tensor name (ENGINE_NEW).
* @param layerName raw ONNX output tensor name (ENGINE_OPENCV).
* @param numParam index of the constant weight input to update (0 = kernel, 1 = bias, etc.).
* @param blob the new parameter value.
*/
@@ -1083,10 +1078,9 @@ CV__DNN_INLINE_NS_BEGIN
enum EngineType
{
ENGINE_CLASSIC=1, //!< Force use the old dnn engine similar to 4.x branch
ENGINE_NEW=2, //!< Force use the new dnn engine. The engine does not support non CPU back-ends for now.
ENGINE_AUTO=3, //!< Try to use the new engine and then fall back to the classic version.
ENGINE_ORT=4 //!< Try to use ONNX Runtime wrapper (ONNX only, requires build with WITH_ONNXRUNTIME=ON).
ENGINE_AUTO=0, //!< Automatically select the engine. Currently resolves to ENGINE_OPENCV; the mapping may change as more engines are added.
ENGINE_OPENCV=1, //!< Use OpenCV's built-in DNN engine. Does not support non-CPU back-ends for now.
ENGINE_ORT=2 //!< Use the ONNX Runtime wrapper (ONNX only, requires build with WITH_ONNXRUNTIME=ON).
};
/** @brief Reads a network model stored in <a href="https://www.tensorflow.org/">TensorFlow</a> framework's format.
@@ -1094,7 +1088,7 @@ CV__DNN_INLINE_NS_BEGIN
* @param config path to the .pbtxt file that contains text graph definition in protobuf format.
* Resulting Net object is built by text graph using weights from a binary one that
* let us make it more flexible.
* @param engine select DNN engine to be used. With auto selection the new engine is used.
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV.
* @param extraOutputs specify model outputs explicitly, in addition to the outputs the graph analyzer finds.
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
* @returns Net object.
@@ -1107,7 +1101,7 @@ CV__DNN_INLINE_NS_BEGIN
/** @brief Reads a network model stored in <a href="https://www.tensorflow.org/">TensorFlow</a> framework's format.
* @param bufferModel buffer containing the content of the pb file
* @param bufferConfig buffer containing the content of the pbtxt file
* @param engine select DNN engine to be used. With auto selection the new engine is used.
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV.
* @param extraOutputs specify model outputs explicitly, in addition to the outputs the graph analyzer finds.
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
* @returns Net object.
@@ -1124,7 +1118,7 @@ CV__DNN_INLINE_NS_BEGIN
* @param lenModel length of bufferModel
* @param bufferConfig buffer containing the content of the pbtxt file
* @param lenConfig length of bufferConfig
* @param engine select DNN engine to be used. With auto selection the new engine is used.
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV.
* @param extraOutputs specify model outputs explicitly, in addition to the outputs the graph analyzer finds.
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
*/
@@ -1135,7 +1129,7 @@ CV__DNN_INLINE_NS_BEGIN
/** @brief Reads a network model stored in <a href="https://www.tensorflow.org/lite">TFLite</a> framework's format.
* @param model path to the .tflite file with binary flatbuffers description of the network architecture
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
* @returns Net object.
*/
@@ -1143,7 +1137,7 @@ CV__DNN_INLINE_NS_BEGIN
/** @brief Reads a network model stored in <a href="https://www.tensorflow.org/lite">TFLite</a> framework's format.
* @param bufferModel buffer containing the content of the tflite file
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
* @returns Net object.
*/
@@ -1154,7 +1148,7 @@ CV__DNN_INLINE_NS_BEGIN
* It differs from the above function only in what argument(s) it accepts.
* @param bufferModel buffer containing the content of the tflite file
* @param lenModel length of bufferModel
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
*/
CV_EXPORTS Net readNetFromTFLite(const char *bufferModel, size_t lenModel, int engine=ENGINE_AUTO);
@@ -1171,9 +1165,8 @@ CV__DNN_INLINE_NS_BEGIN
* * `*.pbtxt` (TensorFlow, https://www.tensorflow.org/)
* * `*.xml` (OpenVINO, https://software.intel.com/openvino-toolkit)
* @param[in] framework Explicit framework name tag to determine a format.
* @param[in] engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
* Use ENGINE_CLASSIC if you want to use other back-ends.
* @returns Net object.
*
* This function automatically detects an origin framework of trained model
@@ -1192,9 +1185,8 @@ CV__DNN_INLINE_NS_BEGIN
* @param[in] framework Name of origin framework.
* @param[in] bufferModel A buffer with a content of binary file with weights
* @param[in] bufferConfig A buffer with a content of text file contains network configuration.
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
* Use ENGINE_CLASSIC if you want to use other back-ends.
* @returns Net object.
*/
CV_EXPORTS_W Net readNet(const String& framework, const std::vector<uchar>& bufferModel,
@@ -1237,7 +1229,7 @@ CV__DNN_INLINE_NS_BEGIN
/** @brief Reads a network model <a href="https://onnx.ai/">ONNX</a>.
* @param onnxFile path to the .onnx file with text description of the network architecture.
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
* @returns Network object that ready to do forward, throw an exception in failure cases.
*/
@@ -1247,7 +1239,7 @@ CV__DNN_INLINE_NS_BEGIN
* in-memory buffer.
* @param buffer memory address of the first byte of the buffer.
* @param sizeBuffer size of the buffer.
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
* @returns Network object that ready to do forward, throw an exception
* in failure cases.
*/
@@ -1256,7 +1248,7 @@ CV__DNN_INLINE_NS_BEGIN
/** @brief Reads a network model from <a href="https://onnx.ai/">ONNX</a>
* in-memory buffer.
* @param buffer in-memory buffer that stores the ONNX model bytes.
* @param engine select DNN engine to be used. With auto selection the new engine is used first and falls back to classic.
* @param engine select DNN engine to be used. ENGINE_AUTO (the default) resolves to ENGINE_OPENCV; ENGINE_ORT selects the ONNX Runtime wrapper (ONNX models only, requires WITH_ONNXRUNTIME=ON).
* Please pay attention that the new DNN does not support non-CPU back-ends for now.
* @returns Network object that ready to do forward, throw an exception
* in failure cases.
@@ -45,6 +45,24 @@ Ptr<Layer> __LayerStaticRegisterer_func_##type(LayerParams &params) \
{ return Ptr<Layer>(new class(params)); } \
static cv::dnn::details::_LayerStaticRegisterer __LayerStaticRegisterer_##type(#type, __LayerStaticRegisterer_func_##type);
/** @brief Registers an LayerInfo (metadata node) class for the new graph engine.
* @param type string, containing the operation type name.
* @param class C++ class derived from LayerInfo, providing `static Ptr<LayerInfo> create(const LayerParams&)`.
* @details This macro must be placed inside the function code (e.g. initializeLayerFactory()).
*/
#define CV_DNN_REGISTER_OP_CLASS(type, class) \
cv::dnn::LayerFactory::registerOp(#type, cv::dnn::details::_opDynamicRegisterer<class>);
/** @brief Registers a backend executor class for the new graph engine.
* @param type string, containing the operation type name.
* @param backendId backend id the executor targets (e.g. DNN_BACKEND_OPENCV, DNN_BACKEND_CUDA).
* @param class C++ class derived from Layer, providing
* `static Ptr<Layer> create(const Ptr<LayerInfo>&, void* backendCtx)` (null Ptr if unsupported).
* @details This macro must be placed inside the function code.
*/
#define CV_DNN_REGISTER_EXEC_CLASS(type, backendId, class) \
cv::dnn::LayerFactory::registerExec(#type, backendId, cv::dnn::details::_execDynamicRegisterer<class>);
namespace details {
template<typename LayerClass>
@@ -53,6 +71,18 @@ Ptr<Layer> _layerDynamicRegisterer(LayerParams &params)
return Ptr<Layer>(LayerClass::create(params));
}
template<typename OpClass>
Ptr<LayerInfo> _opDynamicRegisterer(const LayerParams &params)
{
return Ptr<LayerInfo>(OpClass::create(params));
}
template<typename ExecClass>
Ptr<Layer> _execDynamicRegisterer(const Ptr<LayerInfo>& data, void* backendCtx)
{
return Ptr<Layer>(ExecClass::create(data, backendCtx));
}
//allows automatically register created layer on module load time
class _LayerStaticRegisterer
{
+13
View File
@@ -76,6 +76,19 @@ public:
*/
static Ptr<Layer> createLayerInstance(const String &type, LayerParams& params);
// Builds the abstract (metadata) node for an operation type.
typedef Ptr<LayerInfo>(*OpConstructor)(const LayerParams& params);
//! Builds a backend-specific executor from an LayerInfo; returns null Ptr if unsupported.
typedef Ptr<Layer>(*ExecConstructor)(const Ptr<LayerInfo>& data, void* backendCtx);
static void registerOp(const String& type, OpConstructor constructor);
static Ptr<LayerInfo> createOp(const String& type, const LayerParams& params);
static void registerExec(const String& type, int backendId, ExecConstructor constructor);
static Ptr<Layer> createExec(const String& type, int backendId,
const Ptr<LayerInfo>& data, void* backendCtx);
private:
LayerFactory();
};
@@ -45,7 +45,7 @@ public class DnnForwardAndRetrieve extends OpenCVTestCase {
public void testForwardAndRetrieve()
{
// Verifies forwardAndRetrieve nested list marshalling using a small ONNX model instead of the removed Caffe importer.
Net net = Dnn.readNetFromONNX(modelFileName, Dnn.ENGINE_CLASSIC);
Net net = Dnn.readNetFromONNX(modelFileName, Dnn.ENGINE_OPENCV);
net.setPreferableBackend(Dnn.DNN_BACKEND_OPENCV);
// split_0.onnx declares a single 4D input named "image" of shape [1, 3, 2, 2].
+2 -2
View File
@@ -366,7 +366,7 @@ class dnn_test(NewOpenCVTests):
for backend, target in self.dnnBackendsAndTargets:
printParams(backend, target)
net = cv.dnn.readNet(model, engine=cv.dnn.ENGINE_CLASSIC)
net = cv.dnn.readNet(model, engine=cv.dnn.ENGINE_OPENCV)
net.setPreferableBackend(backend)
net.setPreferableTarget(target)
@@ -415,7 +415,7 @@ class dnn_test(NewOpenCVTests):
for backend, target in self.dnnBackendsAndTargets:
printParams(backend, target)
net = cv.dnn.readNet(model_path, "", "", engine=cv.dnn.ENGINE_CLASSIC)
net = cv.dnn.readNet(model_path, "", "", engine=cv.dnn.ENGINE_OPENCV)
node_name = net.getLayerNames()[0]
w = net.getParam(node_name, 0) # returns the original tensor of three-dimensional shape
+7
View File
@@ -83,6 +83,13 @@ static const GemmParam_t test_matmul_configs[] = {
{ {16, 197, 64 }, {16, 64, 197} },
{ {16, 50, 64}, {16, 64, 50} },
{ {16, 50, 50}, {16, 50, 64} },
// transformer token generation cases (thin-M GEMV, fastGemmThin path)
{ {1, 1, 768}, {1, 768, 768} },
{ {1, 1, 768}, {1, 768, 3072} },
{ {1, 1, 3072}, {1, 3072, 768} },
{ {1, 2, 768}, {1, 768, 768} },
{ {1, 4, 768}, {1, 768, 768} },
};
struct GemmParamId
-6
View File
@@ -145,12 +145,6 @@ PERF_TEST_P_(DNNTestNetwork, SSD)
{
applyTestTag(CV_TEST_TAG_DEBUG_VERYLONG);
// SSD_VGG16's specialized preprocessing is handled by the new engine importer only.
auto engine_forced = static_cast<dnn::EngineType>(
utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", dnn::ENGINE_AUTO));
if (engine_forced == dnn::ENGINE_CLASSIC)
throw SkipTestException("SSD_VGG16 is supported on the new DNN engine only");
processNet("dnn/onnx/models/ssd_vgg16.onnx", "", cv::Size(300, 300));
}
@@ -227,11 +227,11 @@ namespace cv { namespace dnn { namespace cuda4dnn { namespace csl { namespace cu
CUDA4DNN_CHECK_CUDNN(cudnnSetConvolutionGroupCount(descriptor, group_count));
#if CUDNN_MAJOR >= 8
/* cuDNN 7 and below use FMA math by default. cuDNN 8 includes TF32 Tensor Ops
* in the default setting. TF32 convolutions have lower precision than FP32.
* Hence, we set the math type to CUDNN_FMA_MATH to reproduce old behavior.
/* cuDNN 8 default math includes TF32 Tensor Ops for FP32 convolutions (Ampere+),
* giving near-FP16 throughput at slightly reduced precision. ONNXRuntime enables
* this by default; we follow suit. (Was CUDNN_FMA_MATH to force exact FP32.)
*/
CUDA4DNN_CHECK_CUDNN(cudnnSetConvolutionMathType(descriptor, CUDNN_FMA_MATH));
CUDA4DNN_CHECK_CUDNN(cudnnSetConvolutionMathType(descriptor, CUDNN_DEFAULT_MATH));
#endif
if (std::is_same<T, half>::value)
@@ -25,17 +25,14 @@ namespace cv { namespace dnn { namespace cuda4dnn {
using wrapper_type = GetCUDABackendWrapperType<T>;
void forward(
const std::vector<cv::Ptr<BackendWrapper>>& inputs,
const std::vector<cv::Ptr<BackendWrapper>>& outputs,
const std::vector<cuda::GpuMatND>& inputs,
const std::vector<cuda::GpuMatND>& outputs,
csl::Workspace& workspace) override
{
for (int i = 0; i < inputs.size(); i++)
{
auto input_wrapper = inputs[i].dynamicCast<wrapper_type>();
auto input = input_wrapper->getView();
auto output_wrapper = outputs[i].dynamicCast<wrapper_type>();
auto output = output_wrapper->getSpan();
auto input = csl::viewOf<T>(inputs[i]);
auto output = csl::spanOf<T>(outputs[i]);
static_cast<const Op<T>*>(this)->calculate(output, input);
}
@@ -32,17 +32,14 @@ namespace cv { namespace dnn { namespace cuda4dnn {
}
void forward(
const std::vector<cv::Ptr<BackendWrapper>>& inputs,
const std::vector<cv::Ptr<BackendWrapper>>& outputs,
const std::vector<cuda::GpuMatND>& inputs,
const std::vector<cuda::GpuMatND>& outputs,
csl::Workspace& workspace) override
{
CV_Assert(inputs.size() == 1 && outputs.size() == 1);
auto input_wrapper = inputs[0].dynamicCast<wrapper_type>();
auto input = input_wrapper->getView();
auto output_wrapper = outputs[0].dynamicCast<wrapper_type>();
auto output = output_wrapper->getSpan();
auto input = csl::viewOf<T>(inputs[0]);
auto output = csl::spanOf<T>(outputs[0]);
std::size_t inner_size = input.size_range(2, input.rank());
kernels::scaleN_with_biasN<T>(stream, output, input, inner_size, weightsTensor, biasTensor);
@@ -286,8 +286,8 @@ namespace cv { namespace dnn { namespace cuda4dnn {
}
void forward(
const std::vector<cv::Ptr<BackendWrapper>>& inputs,
const std::vector<cv::Ptr<BackendWrapper>>& outputs,
const std::vector<cuda::GpuMatND>& inputs,
const std::vector<cuda::GpuMatND>& outputs,
csl::Workspace& workspace) override
{
/* input[0] = conv input, input[1] = bias (from fused eltwise layer) */
@@ -296,8 +296,7 @@ namespace cv { namespace dnn { namespace cuda4dnn {
csl::WorkspaceAllocator allocator(workspace);
auto input_wrapper = inputs[0].dynamicCast<wrapper_type>();
auto input = input_wrapper->getView();
auto input = csl::viewOf<T>(inputs[0]);
if (!transformed_shape.empty())
{
@@ -309,8 +308,7 @@ namespace cv { namespace dnn { namespace cuda4dnn {
auto conv_scratchpad = allocator.get_instance();
auto output_wrapper = outputs[0].dynamicCast<wrapper_type>();
auto output = output_wrapper->getSpan();
auto output = csl::spanOf<T>(outputs[0]);
if (fusion_location == InternalFusionLocation::CUDNN)
{
@@ -320,8 +318,7 @@ namespace cv { namespace dnn { namespace cuda4dnn {
convoluter.convolve_with_bias_activation(output, input, filtersTensor, biasTensor, conv_scratchpad);
else if (fusion_mode == ConvolutionConfiguration::FusionMode::ELTWISE_SUM_THEN_ACTIVATION)
{
auto eltwise_wrapper = inputs[1].dynamicCast<wrapper_type>();
auto eltwise = eltwise_wrapper->getView();
auto eltwise = csl::viewOf<T>(inputs[1]);
CV_Assert(is_shape_same(eltwise, output));
convoluter.convolve_with_bias_eltwise_activation(output, input, filtersTensor, biasTensor, eltwise, conv_scratchpad);
@@ -357,8 +354,7 @@ namespace cv { namespace dnn { namespace cuda4dnn {
fusion_mode == ConvolutionConfiguration::FusionMode::ELTWISE_SUM_THEN_ACTIVATION ||
fusion_mode == ConvolutionConfiguration::FusionMode::ACTIVATION_THEN_ELTWISE_SUM);
auto eltwise_wrapper = inputs[1].dynamicCast<wrapper_type>();
auto eltwise = eltwise_wrapper->getView();
auto eltwise = csl::viewOf<T>(inputs[1]);
CV_Assert(is_shape_same(eltwise, output));
std::size_t inner_size = output.size_range(2, output.rank());
@@ -472,8 +468,7 @@ namespace cv { namespace dnn { namespace cuda4dnn {
fusion_mode == ConvolutionConfiguration::FusionMode::ELTWISE_SUM_THEN_ACTIVATION ||
fusion_mode == ConvolutionConfiguration::FusionMode::ACTIVATION_THEN_ELTWISE_SUM);
auto eltwise_wrapper = inputs[1].dynamicCast<wrapper_type>();
auto eltwise = eltwise_wrapper->getView();
auto eltwise = csl::viewOf<T>(inputs[1]);
CV_Assert(is_shape_same(eltwise, output));
/* we pass `eltwise` as `bias` (with `inner_size` as one) to bias-activation kernels */
@@ -59,8 +59,8 @@ namespace cv { namespace dnn { namespace cuda4dnn {
}
void forward(
const std::vector<cv::Ptr<BackendWrapper>>& inputs,
const std::vector<cv::Ptr<BackendWrapper>>& outputs,
const std::vector<cuda::GpuMatND>& inputs,
const std::vector<cuda::GpuMatND>& outputs,
csl::Workspace& workspace) override
{
CV_Assert(outputs.size() == 1);
@@ -68,16 +68,12 @@ namespace cv { namespace dnn { namespace cuda4dnn {
CV_Assert(coeffs.size() == 0 || op == EltwiseOpType::SUM);
CV_Assert(coeffs.size() == 0 || inputs.size() == coeffs.size());
auto output_wrapper = outputs[0].dynamicCast<wrapper_type>();
auto output = output_wrapper->getSpan();
auto output = csl::spanOf<T>(outputs[0]);
if (inputs.size() == 2)
{
auto input_wrapper_x = inputs[0].dynamicCast<wrapper_type>();
auto input_x = input_wrapper_x->getView();
auto input_wrapper_y = inputs[1].dynamicCast<wrapper_type>();
auto input_y = input_wrapper_y->getView();
auto input_x = csl::viewOf<T>(inputs[0]);
auto input_y = csl::viewOf<T>(inputs[1]);
switch (op)
{
@@ -97,20 +93,17 @@ namespace cv { namespace dnn { namespace cuda4dnn {
case EltwiseOpType::POW: kernels::eltwise_pow_2<T>(stream, output, input_x, input_y); break;
}
} else if (inputs.size() == 1) {
auto input_wrapper_0 = inputs[0].dynamicCast<wrapper_type>();
auto input_0 = input_wrapper_0->getView();
auto input_0 = csl::viewOf<T>(inputs[0]);
csl::tensor_ops::copy(stream, output, input_0);
} else {
auto input_wrapper_0 = inputs[0].dynamicCast<wrapper_type>();
auto input_0 = input_wrapper_0->getView();
auto input_0 = csl::viewOf<T>(inputs[0]);
/* we first make a copy and then apply EltwiseOp cumulatively */
csl::tensor_ops::copy(stream, output, input_0);
for (int i = 1; i < inputs.size(); i++)
{
auto input_wrapper = inputs[i].dynamicCast<wrapper_type>();
auto input = input_wrapper->getView();
auto input = csl::viewOf<T>(inputs[i]);
switch (op)
{
@@ -43,17 +43,14 @@ namespace cv { namespace dnn { namespace cuda4dnn {
}
void forward(
const std::vector<cv::Ptr<BackendWrapper>>& inputs,
const std::vector<cv::Ptr<BackendWrapper>>& outputs,
const std::vector<cuda::GpuMatND>& inputs,
const std::vector<cuda::GpuMatND>& outputs,
csl::Workspace& workspace) override
{
for (int i = 0; i < inputs.size(); i++)
{
auto input_wrapper = inputs[i].dynamicCast<wrapper_type>();
auto input = input_wrapper->getView();
auto output_wrapper = outputs[i].dynamicCast<wrapper_type>();
auto output = output_wrapper->getSpan();
auto input = csl::viewOf<T>(inputs[i]);
auto output = csl::spanOf<T>(outputs[i]);
std::size_t batch_size = input.size_range(0, axis);
@@ -111,6 +111,22 @@ namespace cv { namespace dnn { namespace cuda4dnn {
);
}
void forward(
const std::vector<cuda::GpuMatND>& inputs,
const std::vector<cuda::GpuMatND>& outputs,
csl::Workspace& workspace) override
{
CV_Assert(inputs.size() == 1 && outputs.size() == 2);
auto input_data = csl::viewOf<T>(inputs[0]);
auto output_data = csl::spanOf<T>(outputs[0]);
auto output_indices = csl::spanOf<T_INDEX>(outputs[1]);
kernels::max_pooling_with_indices<T, T_INDEX>(
stream, output_data, output_indices, input_data, window_size, strides, padding_left
);
}
private:
csl::Stream stream;
@@ -224,14 +224,13 @@ namespace cv { namespace dnn { namespace cuda4dnn {
}
void forward(
const std::vector<cv::Ptr<BackendWrapper>>& inputs,
const std::vector<cv::Ptr<BackendWrapper>>& outputs,
const std::vector<cuda::GpuMatND>& inputs,
const std::vector<cuda::GpuMatND>& outputs,
csl::Workspace& workspace) override
{
CV_Assert(inputs.size() == 1 && outputs.size() == 1);
auto input_wrapper = inputs[0].dynamicCast<wrapper_type>();
auto input = input_wrapper->getView();
auto input = csl::viewOf<T>(inputs[0]);
if (!transformedInput.empty())
{
@@ -239,8 +238,7 @@ namespace cv { namespace dnn { namespace cuda4dnn {
input = csl::TensorView<T>(transformedInput);
}
auto output_wrapper = outputs[0].dynamicCast<wrapper_type>();
auto output = output_wrapper->getSpan();
auto output = csl::spanOf<T>(outputs[0]);
pooler.pool(input, output);
}
@@ -23,8 +23,8 @@ namespace cv { namespace dnn { namespace cuda4dnn {
ReshapeOp(csl::Stream stream_) : stream(std::move(stream_)) { }
void forward(
const std::vector<cv::Ptr<BackendWrapper>>& inputs,
const std::vector<cv::Ptr<BackendWrapper>>& outputs,
const std::vector<cuda::GpuMatND>& inputs,
const std::vector<cuda::GpuMatND>& outputs,
csl::Workspace& workspace) override
{
/* sometimes the output shape is passed as extra inputs; hence, >= instead of == */
@@ -32,11 +32,8 @@ namespace cv { namespace dnn { namespace cuda4dnn {
for (int i = 0; i < outputs.size(); i++)
{
auto input_wrapper = inputs[i].dynamicCast<wrapper_type>();
auto input = input_wrapper->getView();
auto output_wrapper = outputs[i].dynamicCast<wrapper_type>();
auto output = output_wrapper->getSpan();
auto input = csl::viewOf<T>(inputs[i]);
auto output = csl::spanOf<T>(outputs[i]);
if (input.get() != output.get())
{
+11 -2
View File
@@ -11,7 +11,7 @@ CV__DNN_INLINE_NS_BEGIN
using std::vector;
using std::string;
using PLayer = Ptr<Layer>;
using PLayer = Ptr<LayerInfo>;
using PGraph = Ptr<Graph>;
/* Inserts layout conversion operations (if needed) into the model graph and subgraphs.
@@ -121,13 +121,15 @@ struct BlockLayoutTransformer
std::vector<DataLayout> inputLayoutsOrig, inputLayoutsNew, outputLayouts;
size_t nchanges = 0;
for (const PLayer& layer: currProg) {
for (size_t opidx = 0; opidx < currProg.size(); opidx++) {
const PLayer& layer = currProg[opidx];
const vector<Arg>& inputs = layer->inputs;
const vector<Arg>& outputs = layer->outputs;
size_t ninputs = inputs.size(), noutputs = outputs.size();
std::string op_name = layer->type;
std::string name = layer->name;
vector<PGraph>* subgraphs = layer->subgraphs();
bool deviceOp = g->opBackend((int)opidx) != DNN_BACKEND_OPENCV;
//std::cout << "name: " << name << ", op_name: " << op_name << ", inp0 layout: " << layoutToString(layouts[inputs[0].idx]) << "\n";
if (subgraphs) {
@@ -164,6 +166,13 @@ struct BlockLayoutTransformer
CV_Assert(inputLayoutsNew.size() == ninputs);
CV_Assert(outputLayouts.size() == noutputs);
if (deviceOp) {
for (size_t i = 0; i < ninputs; i++)
inputLayoutsNew[i] = defaultLayout;
for (size_t i = 0; i < noutputs; i++)
outputLayouts[i] = defaultLayout;
}
newInputs.clear();
bool changedInputs = false;
for (size_t i = 0; i < ninputs; i++) {
+49 -43
View File
@@ -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<Graph>& body)
{
std::unordered_set<int> bodyDefined;
for (Arg ba : body->inputs())
bodyDefined.insert(ba.idx);
for (const Ptr<LayerInfo>& blayer : body->prog()) {
if (!blayer) continue;
for (Arg bo : blayer->outputs)
bodyDefined.insert(bo.idx);
}
std::unordered_set<int> closureBumped;
for (const Ptr<LayerInfo>& 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<int> 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<typename _Tp> std::ostream&
dumpArgVec(std::ostream& strm, const std::string& name, const vector<_Tp>& vec) const
{
@@ -183,7 +226,7 @@ struct BufferAllocator
}
}
}
const std::vector<Ptr<Layer> >& prog = graph->prog();
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
for (const auto& layer: prog) {
bool inplace = false;
Arg reuseArg;
@@ -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<int> bodyDefined;
for (Arg ba : body->inputs())
bodyDefined.insert(ba.idx);
for (const Ptr<Layer>& blayer : body->prog()) {
if (!blayer) continue;
for (Arg bo : blayer->outputs)
bodyDefined.insert(bo.idx);
}
std::unordered_set<int> closureBumped;
for (const Ptr<Layer>& 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<int> 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) {
+8 -3
View File
@@ -36,14 +36,14 @@ struct ConstArgs
void processGraph(Ptr<Graph>& graph)
{
const std::vector<Ptr<Layer> >& prog = graph->prog();
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
size_t i, nops = prog.size();
std::vector<Arg> removed_args;
std::vector<Arg> saved_tail_inputs;
for (i = 0; i < nops; i++) {
const Ptr<Layer>& layer = prog[i];
Layer* layer_ptr = const_cast<Layer*>(layer.get());
const Ptr<LayerInfo>& layer = prog[i];
LayerInfo* layer_ptr = const_cast<LayerInfo*>(layer.get());
std::vector<Ptr<Graph> >* subgraphs = layer->subgraphs();
if (subgraphs) {
for (Ptr<Graph>& g: *subgraphs) {
@@ -68,6 +68,7 @@ struct ConstArgs
Conv2Layer* conv = dynamic_cast<Conv2Layer*>(layer_ptr);
ConvTranspose2Layer* deconv = dynamic_cast<ConvTranspose2Layer*>(layer_ptr);
BatchNorm2Layer* bn = dynamic_cast<BatchNorm2Layer*>(layer_ptr);
ChannelsPReLULayer* prelu = dynamic_cast<ChannelsPReLULayer*>(layer_ptr);
//ActivationLayer* activ = dynamic_cast<ActivationLayer*>(layer_ptr);
if (tail_const) {
@@ -88,6 +89,10 @@ struct ConstArgs
} else if (bn && bn->freezeScaleBias()) {
// batch norm with constant parameters
unuse_tail = true;
} else if (prelu && ninputs == 2) {
prelu->setSlope(netimpl->__tensors__[inputs[1].idx]);
prelu->inputs.resize(1);
unuse_tail = true;
}/* else if (activ && dynamic_cast<ReLU6Layer>(activ)) {
// [TODO] ...
unuse_tail = true;
+8 -6
View File
@@ -30,7 +30,7 @@ struct ConstFolding
netimpl->scratchBufs.clear();
}
Layer* getLayer(std::vector<Ptr<Layer> >& newprog, int op_idx) const
LayerInfo* getLayer(std::vector<Ptr<LayerInfo> >& newprog, int op_idx) const
{
return op_idx >= 0 ? newprog.at(op_idx).get() : 0;
}
@@ -47,16 +47,16 @@ struct ConstFolding
{
netimpl->scratchBufs.clear();
bool modified = false;
const std::vector<Ptr<Layer> >& prog = graph->prog();
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
size_t i, nops = prog.size();
std::vector<Ptr<Layer> > newprog;
std::vector<Ptr<LayerInfo> > newprog;
std::vector<Arg> removed_args;
std::vector<Mat> inpMats, tempMats;
std::vector<int> inpTypes, outTypes, tempTypes;
std::vector<MatShape> inpShapes, outShapes, tempShapes;
for (i = 0; i < nops; i++) {
const Ptr<Layer>& layer = prog[i];
const Ptr<LayerInfo>& layer = prog[i];
std::vector<Ptr<Graph> >* subgraphs = layer->subgraphs();
if (subgraphs) {
for (Ptr<Graph>& g: *subgraphs) {
@@ -98,8 +98,10 @@ struct ConstFolding
netimpl->allocateLayerOutputs(layer, inpTypes, inpShapes, outTypes,
outShapes, outOrigData, outMats, tempTypes, tempShapes, tempMats,
netimpl->scratchBufs, false);
layer->finalize(inpMats, outMats);
layer->forward(inpMats, outMats, tempMats);
Ptr<Layer> execLayer = layer.dynamicCast<Layer>();
CV_Assert(execLayer); // const-folded ops are CPU-executable (monolithic) layers
execLayer->finalize(inpMats, outMats);
execLayer->forward(inpMats, outMats, tempMats);
CV_Assert(outMats.size() == noutputs);
for (j = 0; j < noutputs; j++) {
Arg out = outputs[j];
+32 -32
View File
@@ -32,35 +32,35 @@ struct ModelFusionAttention
return it->second[0];
}
bool isReshape(const vector<Ptr<Layer>>& prog, int idx) const
bool isReshape(const vector<Ptr<LayerInfo>>& prog, int idx) const
{
if (idx < 0 || idx >= (int)prog.size() || !prog[idx])
return false;
return dynamic_cast<Reshape2Layer*>(prog[idx].get()) != nullptr;
}
bool isTranspose(const vector<Ptr<Layer>>& prog, int idx) const
bool isTranspose(const vector<Ptr<LayerInfo>>& prog, int idx) const
{
if (idx < 0 || idx >= (int)prog.size() || !prog[idx])
return false;
return dynamic_cast<TransposeLayer*>(prog[idx].get()) != nullptr;
}
bool isSoftmax(const vector<Ptr<Layer>>& prog, int idx) const
bool isSoftmax(const vector<Ptr<LayerInfo>>& prog, int idx) const
{
if (idx < 0 || idx >= (int)prog.size() || !prog[idx])
return false;
return prog[idx]->type == "Softmax";
}
bool isMatMul(const vector<Ptr<Layer>>& prog, int idx) const
bool isMatMul(const vector<Ptr<LayerInfo>>& prog, int idx) const
{
if (idx < 0 || idx >= (int)prog.size() || !prog[idx])
return false;
return dynamic_cast<MatMulLayer*>(prog[idx].get()) != nullptr;
}
static bool isProjCandidate(const Ptr<Layer>& l)
static bool isProjCandidate(const Ptr<LayerInfo>& l)
{
if (l->blobs.empty() || l->inputs.size() != 1) return false;
if (dynamic_cast<MatMulLayer*>(l.get()))
@@ -74,7 +74,7 @@ struct ModelFusionAttention
// Returns the projection weight in [K, N] (input_hidden, output_hidden)
// layout, transposing if the source is a Gemm with trans_b.
static Mat getProjWeight(const Ptr<Layer>& l)
static Mat getProjWeight(const Ptr<LayerInfo>& l)
{
const Mat& W = l->blobs[0];
GemmLayer* g = dynamic_cast<GemmLayer*>(l.get());
@@ -86,7 +86,7 @@ struct ModelFusionAttention
return W;
}
bool isScalarBinOp(const vector<Ptr<Layer>>& prog, int idx,
bool isScalarBinOp(const vector<Ptr<LayerInfo>>& prog, int idx,
NaryEltwiseLayer::OPERATION op, float* val) const
{
if (idx < 0 || idx >= (int)prog.size() || !prog[idx])
@@ -109,18 +109,18 @@ struct ModelFusionAttention
return false;
}
bool isScalarMul(const vector<Ptr<Layer>>& prog, int idx, float* val) const
bool isScalarMul(const vector<Ptr<LayerInfo>>& prog, int idx, float* val) const
{
return isScalarBinOp(prog, idx, NaryEltwiseLayer::OPERATION::PROD, val);
}
bool isScalarDiv(const vector<Ptr<Layer>>& prog, int idx, float* val) const
bool isScalarDiv(const vector<Ptr<LayerInfo>>& prog, int idx, float* val) const
{
return isScalarBinOp(prog, idx, NaryEltwiseLayer::OPERATION::DIV, val);
}
// True if `arg` is produced by the dynamic scale chain Sqrt<-Cast<-Div(1,.)<-Sqrt<-Cast<-Slice<-Shape; visited ops are appended to `chain_ops`.
bool isRuntimeQKScaleChain(const vector<Ptr<Layer>>& prog, Arg arg,
bool isRuntimeQKScaleChain(const vector<Ptr<LayerInfo>>& prog, Arg arg,
std::set<int>& chain_ops) const
{
const std::vector<std::string> expected = {
@@ -133,7 +133,7 @@ struct ModelFusionAttention
if (it == producer_.end()) return false;
int idx = it->second;
if (idx < 0 || idx >= (int)prog.size() || !prog[idx]) return false;
const Ptr<Layer>& l = prog[idx];
const Ptr<LayerInfo>& l = prog[idx];
if (want == "NaryEltwise") {
NaryEltwiseLayer* elt = dynamic_cast<NaryEltwiseLayer*>(l.get());
if (!elt || elt->op != NaryEltwiseLayer::OPERATION::DIV) return false;
@@ -171,7 +171,7 @@ struct ModelFusionAttention
// Accept Add op with exactly two inputs; identify the non-constant runtime
// input (the mask tensor). Returns false if the Add doesn't match.
bool isMaskAdd(const vector<Ptr<Layer>>& prog, int idx, Arg* out_mask) const
bool isMaskAdd(const vector<Ptr<LayerInfo>>& prog, int idx, Arg* out_mask) const
{
if (idx < 0 || idx >= (int)prog.size() || !prog[idx])
return false;
@@ -186,7 +186,7 @@ struct ModelFusionAttention
// Extract a scalar integer from a const-valued arg, possibly wrapped in an
// Unsqueeze of a scalar const. Returns -1 if extraction fails.
int extractConstInt(const vector<Ptr<Layer>>& prog, Arg a) const
int extractConstInt(const vector<Ptr<LayerInfo>>& prog, Arg a) const
{
auto readScalar = [&](Arg x) -> int {
if (!netimpl->isConstArg(x)) return -1;
@@ -207,7 +207,7 @@ struct ModelFusionAttention
return -1;
}
void collectShapeChain(const vector<Ptr<Layer>>& prog, int concat_idx,
void collectShapeChain(const vector<Ptr<LayerInfo>>& prog, int concat_idx,
std::set<int>& chain) const
{
if (concat_idx < 0 || concat_idx >= (int)prog.size() || !prog[concat_idx])
@@ -238,7 +238,7 @@ struct ModelFusionAttention
}
template <class Pred>
int findMatchingConsumer(const vector<Ptr<Layer>>& prog, Arg out,
int findMatchingConsumer(const vector<Ptr<LayerInfo>>& prog, Arg out,
Pred pred, std::set<int>* extra_shape_ops) const
{
auto it = consumers_.find(out.idx);
@@ -258,7 +258,7 @@ struct ModelFusionAttention
return matched;
}
int followProjChain(const vector<Ptr<Layer>>& prog,
int followProjChain(const vector<Ptr<LayerInfo>>& prog,
int proj_matmul_idx,
int* out_reshape_idx,
int* out_num_heads,
@@ -268,7 +268,7 @@ struct ModelFusionAttention
if (proj_matmul_idx < 0) return -1;
Arg proj_out = prog[proj_matmul_idx]->outputs[0];
int reshape_idx = findMatchingConsumer(prog, proj_out,
[](Layer* L){ return dynamic_cast<Reshape2Layer*>(L) != nullptr; },
[](LayerInfo* L){ return dynamic_cast<Reshape2Layer*>(L) != nullptr; },
extra_ops_to_remove);
if (!isReshape(prog, reshape_idx)) return -1;
@@ -311,9 +311,9 @@ struct ModelFusionAttention
// Combined-QKV attention: QKV proj -> Reshape ->
// Transpose -> 3 Gathers -> QK^T -> Softmax(no mask) -> *V.
bool tryFuseCombinedQKV(const vector<Ptr<Layer>>& prog, int qkv_matmul_idx,
bool tryFuseCombinedQKV(const vector<Ptr<LayerInfo>>& prog, int qkv_matmul_idx,
std::set<int>& removed_ops,
vector<std::pair<int, Ptr<Layer>>>& replacements)
vector<std::pair<int, Ptr<LayerInfo>>>& replacements)
{
if (qkv_matmul_idx < 0 || qkv_matmul_idx >= (int)prog.size() || !prog[qkv_matmul_idx])
return false;
@@ -456,7 +456,7 @@ struct ModelFusionAttention
int out_trans_idx = singleConsumer(prog[av_matmul_idx]->outputs[0]);
if (!isTranspose(prog, out_trans_idx)) return false;
int out_reshape_idx = findMatchingConsumer(prog, prog[out_trans_idx]->outputs[0],
[](Layer* L){ return dynamic_cast<Reshape2Layer*>(L) != nullptr; },
[](LayerInfo* L){ return dynamic_cast<Reshape2Layer*>(L) != nullptr; },
&extra_ops);
if (!isReshape(prog, out_reshape_idx)) return false;
@@ -487,7 +487,7 @@ struct ModelFusionAttention
attn_params.blobs.push_back(W_qkv);
if (has_bias) attn_params.blobs.push_back(bias_qkv);
Ptr<Layer> attn_layer = LayerFactory::createLayerInstance(attn_params.type, attn_params);
Ptr<LayerInfo> attn_layer = LayerFactory::createLayerInstance(attn_params.type, attn_params);
CV_Assert(attn_layer);
Arg shared_input = prog[qkv_matmul_idx]->inputs[0];
attn_layer->inputs = { shared_input };
@@ -514,7 +514,7 @@ struct ModelFusionAttention
// CLIP-branch trace: arg -> [Transpose3D(K^T)] -> Reshape3D -> Transpose ->
// Reshape4D -> [Mul(Q scale)] -> [Add(bias)] -> proj_MatMul.
int traceClipBranch(const vector<Ptr<Layer>>& prog, Arg arg,
int traceClipBranch(const vector<Ptr<LayerInfo>>& prog, Arg arg,
bool is_q_branch, bool is_k_branch,
Mat& out_W, Mat& out_bias, int& out_num_heads,
float& out_q_scale,
@@ -660,9 +660,9 @@ struct ModelFusionAttention
// 3 separate q/k/v projections, Q scaled, K^T at
// the QK^T matmul, output reshaped+transposed back to (B,S,H*D).
bool tryFuseClipAttention(const vector<Ptr<Layer>>& prog, int softmax_idx,
bool tryFuseClipAttention(const vector<Ptr<LayerInfo>>& prog, int softmax_idx,
std::set<int>& removed_ops,
vector<std::pair<int, Ptr<Layer>>>& replacements)
vector<std::pair<int, Ptr<LayerInfo>>>& replacements)
{
if (softmax_idx < 0 || softmax_idx >= (int)prog.size() || !prog[softmax_idx])
return false;
@@ -782,7 +782,7 @@ struct ModelFusionAttention
attn_params.blobs.push_back(W_qkv);
if (!bias_qkv.empty()) attn_params.blobs.push_back(bias_qkv);
Ptr<Layer> attn_layer =
Ptr<LayerInfo> attn_layer =
LayerFactory::createLayerInstance(attn_params.type, attn_params);
if (!attn_layer) return false;
attn_layer->inputs = { shared_input };
@@ -804,7 +804,7 @@ struct ModelFusionAttention
bool fuseGraph(Ptr<Graph>& graph)
{
const vector<Ptr<Layer>>& prog = graph->prog();
const vector<Ptr<LayerInfo>>& prog = graph->prog();
size_t nops = prog.size();
producer_.clear();
@@ -885,7 +885,7 @@ struct ModelFusionAttention
Arg k_tr_out = prog[transpose_idx[k_slot]]->outputs[0];
// Tolerate a Shape consumer alongside the Mul/MatMul: the runtime-scale chain (Shape->Slice->Cast->Sqrt...) branches off the Q/K transpose.
int k_next = findMatchingConsumer(prog, k_tr_out,
[](Layer* L) {
[](LayerInfo* L) {
return dynamic_cast<NaryEltwiseLayer*>(L) != nullptr ||
dynamic_cast<MatMulLayer*>(L) != nullptr;
},
@@ -939,7 +939,7 @@ struct ModelFusionAttention
if (vit_style) {
int q_next = findMatchingConsumer(prog, q_tr_out,
[](Layer* L) {
[](LayerInfo* L) {
return dynamic_cast<NaryEltwiseLayer*>(L) != nullptr ||
dynamic_cast<MatMulLayer*>(L) != nullptr;
},
@@ -1020,7 +1020,7 @@ struct ModelFusionAttention
Arg out_tr_out = prog[out_transpose_idx]->outputs[0];
int out_reshape_idx = findMatchingConsumer(prog, out_tr_out,
[](Layer* L){ return dynamic_cast<Reshape2Layer*>(L) != nullptr; },
[](LayerInfo* L){ return dynamic_cast<Reshape2Layer*>(L) != nullptr; },
&extra_ops);
if (!isReshape(prog, out_reshape_idx)) continue;
@@ -1094,7 +1094,7 @@ struct ModelFusionAttention
if (has_bias)
attn_params.blobs.push_back(bias_qkv);
Ptr<Layer> attn_layer = LayerFactory::createLayerInstance(
Ptr<LayerInfo> attn_layer = LayerFactory::createLayerInstance(
attn_params.type, attn_params);
CV_Assert(attn_layer);
@@ -1157,7 +1157,7 @@ struct ModelFusionAttention
}
if (modified) {
vector<Ptr<Layer>> newprog;
vector<Ptr<LayerInfo>> newprog;
std::sort(attention_replacements_.begin(), attention_replacements_.end(),
[](auto& a, auto& b) { return a.first < b.first; });
@@ -1183,7 +1183,7 @@ struct ModelFusionAttention
private:
std::map<int, int> producer_;
std::map<int, vector<int>> consumers_;
vector<std::pair<int, Ptr<Layer>>> attention_replacements_;
vector<std::pair<int, Ptr<LayerInfo>>> attention_replacements_;
};
void Net::Impl::fuseAttention()
+16 -16
View File
@@ -30,7 +30,7 @@ struct ModelFusionBasic
}
template<typename _LayerType> _LayerType*
getLayer(std::vector<Ptr<Layer> >& newprog, int op_idx) const
getLayer(std::vector<Ptr<LayerInfo> >& newprog, int op_idx) const
{
return op_idx >= 0 ? dynamic_cast<_LayerType*>(newprog.at(op_idx).get()) : 0;
}
@@ -39,14 +39,14 @@ struct ModelFusionBasic
{
vector<Arg> removed_args;
bool modified = false;
const std::vector<Ptr<Layer> >& prog = graph->prog();
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
size_t i, nargs = netimpl->args.size(), nops = prog.size();
std::vector<int> producer_of(nargs, -1);
std::vector<Ptr<Layer> > newprog;
std::vector<Ptr<LayerInfo> > newprog;
std::vector<Arg> fused_inputs;
for (i = 0; i < nops; i++) {
const Ptr<Layer>& layer = prog[i];
const Ptr<LayerInfo>& layer = prog[i];
Layer* layer_ptr = (Layer*)layer.get();
int fused_layer_idx = -1;
std::vector<Ptr<Graph> >* subgraphs = layer->subgraphs();
@@ -74,7 +74,7 @@ struct ModelFusionBasic
int conv_layer_idx = producer_of.at(bn_inp.idx);
Conv2Layer* conv = getLayer<Conv2Layer>(newprog, conv_layer_idx);
if (conv) {
bool ok = conv->fuseBatchNorm(layer);
bool ok = conv->fuseBatchNorm(layer.dynamicCast<Layer>());
if (ok) {
fused_layer_idx = conv_layer_idx;
removed_args.push_back(bn_inp);
@@ -122,7 +122,7 @@ struct ModelFusionBasic
int conv_layer_idx = producer_of.at(activ_inp.idx);
Conv2Layer* conv = getLayer<Conv2Layer>(newprog, conv_layer_idx);
if (conv) {
bool ok = conv->fuseActivation(layer);
bool ok = conv->fuseActivation(layer.dynamicCast<Layer>());
if (ok) {
fused_layer_idx = conv_layer_idx;
removed_args.push_back(activ_inp);
@@ -209,7 +209,7 @@ struct ModelFusionBasic
gnparams.type = "GroupNormalization";
gnparams.set("epsilon", instnorm->epsilon);
gnparams.set("num_groups", num_groups);
Ptr<Layer> gnlayer = GroupNormLayer::create(gnparams);
Ptr<LayerInfo> gnlayer = GroupNormLayer::create(gnparams);
gnlayer->netimpl = netimpl;
gnlayer->inputs = {orig_inp, mul_scale_arg, add_bias_arg};
newprog[instnorm_idx] = gnlayer;
@@ -219,9 +219,9 @@ struct ModelFusionBasic
removed_args.push_back(reshape2_inp);
removed_args.push_back(reshape2_out);
removed_args.push_back(mul_out);
newprog[reshape1_idx] = Ptr<Layer>();
newprog[reshape2_idx] = Ptr<Layer>();
newprog[mul_idx] = Ptr<Layer>();
newprog[reshape1_idx] = Ptr<LayerInfo>();
newprog[reshape2_idx] = Ptr<LayerInfo>();
newprog[mul_idx] = Ptr<LayerInfo>();
break;
}
}
@@ -237,7 +237,7 @@ struct ModelFusionBasic
if (fused_layer_idx >= 0) {
modified = true;
Layer* fused_layer = newprog[fused_layer_idx];
Layer* fused_layer = (Layer*)newprog[fused_layer_idx].get();
fused_layer->outputs = outputs;
for (Arg new_out: outputs)
producer_of[new_out.idx] = fused_layer_idx;
@@ -293,16 +293,16 @@ struct FuseBNPass
void fuseGraph(Ptr<Graph>& graph)
{
const std::vector<Ptr<Layer> >& prog = graph->prog();
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
size_t nops = prog.size(), nargs = netimpl->args.size();
std::vector<Ptr<Layer> > newprog;
std::vector<Ptr<LayerInfo> > newprog;
newprog.reserve(nops);
std::vector<int> producer_of((int)nargs, -1);
bool modified = false;
for (size_t i = 0; i < nops; i++) {
const Ptr<Layer>& layer = prog[i];
Layer* layer_ptr = const_cast<Layer*>(layer.get());
const Ptr<LayerInfo>& layer = prog[i];
Layer* layer_ptr = (Layer*)layer.get();
std::vector<Ptr<Graph> >* subgraphs = layer->subgraphs();
if (subgraphs)
@@ -324,7 +324,7 @@ struct FuseBNPass
usecounts[conv_inp0.idx] = 0;
if (bn_inp0.idx >= 0)
usecounts[bn_inp0.idx]++;
newprog[bn_idx] = Ptr<Layer>();
newprog[bn_idx] = Ptr<LayerInfo>();
modified = true;
}
}
@@ -43,7 +43,7 @@ struct ModelFusionMatMulToGemm
bool fuseGraph(Ptr<Graph>& graph)
{
const vector<Ptr<Layer>>& prog = graph->prog();
const vector<Ptr<LayerInfo>>& prog = graph->prog();
size_t nops = prog.size();
bool modified = false;
@@ -56,11 +56,11 @@ struct ModelFusionMatMulToGemm
}
}
vector<Ptr<Layer>> newprog = prog;
vector<Ptr<LayerInfo>> newprog = prog;
bool changed = false;
for (size_t i = 0; i < nops; i++) {
const Ptr<Layer>& layer = newprog[i];
const Ptr<LayerInfo>& layer = newprog[i];
if (!layer) continue;
MatMulLayer* mm = dynamic_cast<MatMulLayer*>(layer.get());
@@ -120,7 +120,7 @@ struct ModelFusionMatMulToGemm
gp.blobs.push_back(B);
if (have_bias) gp.blobs.push_back(layer->blobs[1]);
Ptr<Layer> gemm = LayerFactory::createLayerInstance("Gemm", gp);
Ptr<LayerInfo> gemm = LayerFactory::createLayerInstance("Gemm", gp);
if (!gemm) continue;
gemm->inputs = layer->inputs;
gemm->outputs = layer->outputs;
+34 -33
View File
@@ -32,7 +32,7 @@ struct ModelFusionQDQ
}
template<typename _LayerType> _LayerType*
getLayer(std::vector<Ptr<Layer> >& newprog, int op_idx) const
getLayer(std::vector<Ptr<LayerInfo> >& newprog, int op_idx) const
{
return op_idx >= 0 ? dynamic_cast<_LayerType*>(newprog.at(op_idx).get()) : 0;
}
@@ -48,10 +48,10 @@ struct ModelFusionQDQ
return params;
}
Ptr<Layer> createFusedLayer(const LayerParams& src) const
Ptr<LayerInfo> createFusedLayer(const LayerParams& src) const
{
LayerParams params = src;
Ptr<Layer> layer = LayerFactory::createLayerInstance(params.type, params);
Ptr<LayerInfo> layer = LayerFactory::createLayerInstance(params.type, params);
if (!layer.empty())
layer->netimpl = netimpl;
return layer;
@@ -94,7 +94,7 @@ struct ModelFusionQDQ
size_t ninputs,
const std::vector<Arg>& inputs,
const std::vector<int>& producer_of,
std::vector<Ptr<Layer> >& newprog,
std::vector<Ptr<LayerInfo> >& newprog,
Arg& q_data_in,
Arg& out_scale,
Arg& out_zp,
@@ -118,10 +118,10 @@ struct ModelFusionQDQ
{
vector<Arg> removed_args;
bool modified = false;
const std::vector<Ptr<Layer> >& prog = graph->prog();
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
size_t i, nargs = netimpl->args.size(), nops = prog.size();
std::vector<int> producer_of(nargs, -1);
std::vector<Ptr<Layer> > newprog;
std::vector<Ptr<LayerInfo> > newprog;
std::vector<Arg> fused_inputs;
std::set<int> skip_indices;
std::vector<Arg> override_outputs;
@@ -129,7 +129,7 @@ struct ModelFusionQDQ
for (i = 0; i < nops; i++) {
if (skip_indices.count((int)i)) continue;
const Ptr<Layer>& layer = prog[i];
const Ptr<LayerInfo>& layer = prog[i];
Layer* layer_ptr = (Layer*)layer.get();
int fused_layer_idx = -1;
std::vector<Ptr<Graph> >* subgraphs = layer->subgraphs();
@@ -219,7 +219,7 @@ struct ModelFusionQDQ
removed_args.push_back(add_inp);
for (int dq_prog_idx : dq_prog_indices)
newprog[dq_prog_idx] = Ptr<Layer>();
newprog[dq_prog_idx] = Ptr<LayerInfo>();
break;
}
@@ -289,7 +289,7 @@ struct ModelFusionQDQ
fused_inputs.assign(1, dq->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(relu_in);
newprog[dq_idx] = Ptr<Layer>();
newprog[dq_idx] = Ptr<LayerInfo>();
break;
}
}
@@ -363,7 +363,7 @@ struct ModelFusionQDQ
fused_inputs.assign(1, dq->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(sig_in);
newprog[dq_idx] = Ptr<Layer>();
newprog[dq_idx] = Ptr<LayerInfo>();
break;
}
}
@@ -450,7 +450,7 @@ struct ModelFusionQDQ
}
int prod_idx = producer_of.at(add2->inputs[k].idx);
Layer* prod = prod_idx >= 0 && !newprog[prod_idx].empty()
? newprog[prod_idx].get() : nullptr;
? (Layer*)newprog[prod_idx].get() : nullptr;
if (!prod || !getInt8OutputParams(prod, in_scales2[k], in_zps2[k]))
elt_in_int82 = false;
}
@@ -479,7 +479,7 @@ struct ModelFusionQDQ
if (relu_out_uc <= 1) {
fused_layer_idx = add_idx2;
newprog[add_idx2] = eltInt8;
newprog[relu_layer_idx2] = Ptr<Layer>();
newprog[relu_layer_idx2] = Ptr<LayerInfo>();
removed_args.push_back(q_inp); // relu_out
removed_args.push_back(relu_in2); // add_out
for (size_t dk = 0; dk < add2->inputs.size(); dk++) {
@@ -487,7 +487,7 @@ struct ModelFusionQDQ
}
for (int dq_prog_idx : dq_prog_indices2) {
if (dq_prog_idx >= 0)
newprog[dq_prog_idx] = Ptr<Layer>();
newprog[dq_prog_idx] = Ptr<LayerInfo>();
}
} else {
int new_idx = (int)newprog.size();
@@ -665,13 +665,13 @@ struct ModelFusionQDQ
if (conv->inputs.size() == 3) {
removed_args.push_back(conv->inputs[2]);
if (dq_bias_idx >= 0)
newprog[dq_bias_idx] = Ptr<Layer>();
newprog[dq_bias_idx] = Ptr<LayerInfo>();
}
if (usecounts.at(conv_x.idx) == 1) {
removed_args.push_back(conv_x);
newprog[dq_x_idx] = Ptr<Layer>();
newprog[dq_x_idx] = Ptr<LayerInfo>();
}
newprog[dq_w_idx] = Ptr<Layer>();
newprog[dq_w_idx] = Ptr<LayerInfo>();
break;
}
}
@@ -770,8 +770,8 @@ struct ModelFusionQDQ
removed_args.push_back(q_data_in);
removed_args.push_back(mm_x);
removed_args.push_back(mm_w);
newprog[dq_x_idx] = Ptr<Layer>();
newprog[dq_w_idx] = Ptr<Layer>();
newprog[dq_x_idx] = Ptr<LayerInfo>();
newprog[dq_w_idx] = Ptr<LayerInfo>();
break;
}
}
@@ -896,9 +896,9 @@ struct ModelFusionQDQ
removed_args.push_back(add_bias->inputs[mm_inp_k]); // matmul out
removed_args.push_back(mm_x);
removed_args.push_back(mm_w);
newprog[mm2_idx] = Ptr<Layer>();
newprog[dq_x_idx] = Ptr<Layer>();
newprog[dq_w_idx] = Ptr<Layer>();
newprog[mm2_idx] = Ptr<LayerInfo>();
newprog[dq_x_idx] = Ptr<LayerInfo>();
newprog[dq_w_idx] = Ptr<LayerInfo>();
break;
}
}
@@ -1026,8 +1026,8 @@ struct ModelFusionQDQ
removed_args.push_back(q_data_in);
removed_args.push_back(gemm_a);
removed_args.push_back(gemm_b);
newprog[dq_a_idx] = Ptr<Layer>();
newprog[dq_b_idx] = Ptr<Layer>();
newprog[dq_a_idx] = Ptr<LayerInfo>();
newprog[dq_b_idx] = Ptr<LayerInfo>();
break;
}
}
@@ -1089,7 +1089,7 @@ struct ModelFusionQDQ
fused_inputs.assign(1, dq->inputs[0]);
removed_args.push_back(q_data_in);
removed_args.push_back(pool_in);
newprog[dq_idx] = Ptr<Layer>();
newprog[dq_idx] = Ptr<LayerInfo>();
break;
}
}
@@ -1107,7 +1107,7 @@ struct ModelFusionQDQ
Arg ql_zp = inputs[2];
int shuffle_idx = producer_of.at(ql_data.idx);
Layer* shuffle_layer = (shuffle_idx >= 0 && !newprog[shuffle_idx].empty())
? newprog[shuffle_idx].get() : nullptr;
? (Layer*)newprog[shuffle_idx].get() : nullptr;
bool is_shuffle = shuffle_layer && shuffle_layer->isDataShuffling();
@@ -1142,7 +1142,7 @@ struct ModelFusionQDQ
fused_inputs.push_back(shuffle_layer->inputs[si]);
removed_args.push_back(ql_data);
removed_args.push_back(shuffle_inp);
newprog[dq_idx] = Ptr<Layer>();
newprog[dq_idx] = Ptr<LayerInfo>();
break;
}
}
@@ -1156,14 +1156,15 @@ struct ModelFusionQDQ
Arg activ_inp = inputs[0];
int producer_idx = producer_of.at(activ_inp.idx);
if (producer_idx >= 0 && !newprog[producer_idx].empty()) {
Layer* producer_layer = newprog[producer_idx].get();
Layer* producer_layer = dynamic_cast<Layer*>(newprog[producer_idx].get());
float prod_sc = 0.f;
int prod_zp = 0;
if (getInt8OutputParams(producer_layer, prod_sc, prod_zp) &&
if (producer_layer &&
getInt8OutputParams(producer_layer, prod_sc, prod_zp) &&
prod_sc == activ_int8->input_sc &&
prod_zp == activ_int8->input_zp) {
Ptr<ActivationLayer> activ_layer = layer.dynamicCast<ActivationLayer>();
if (newprog[producer_idx]->setActivation(activ_layer)) {
if (producer_layer->setActivation(activ_layer)) {
setInt8OutputParams(producer_layer,
activ_int8->output_sc,
activ_int8->output_zp);
@@ -1179,7 +1180,7 @@ struct ModelFusionQDQ
if (fused_layer_idx >= 0) {
modified = true;
Layer* fused_layer = newprog[fused_layer_idx];
Layer* fused_layer = (Layer*)newprog[fused_layer_idx].get();
const std::vector<Arg>& final_outputs = override_outputs.empty() ? outputs : override_outputs;
fused_layer->outputs = final_outputs;
if (!fused_inputs.empty())
@@ -1224,7 +1225,7 @@ struct ModelFusionQDQ
while (cur >= 0 && !newprog[cur].empty()) {
ql = dynamic_cast<QuantizeLinearLayer*>(newprog[cur].get());
if (ql) break;
Layer* l = newprog[cur].get();
Layer* l = (Layer*)newprog[cur].get();
if (l->inputs.empty()) { ql = nullptr; break; }
cur = producer_of.at(l->inputs[0].idx);
}
@@ -1303,7 +1304,7 @@ struct ModelFusionQDQ
conv->float_input = true;
conv->inputs[0] = ql_data;
newprog[ql_idx] = Ptr<Layer>();
newprog[ql_idx] = Ptr<LayerInfo>();
modified = true;
}
@@ -1338,7 +1339,7 @@ struct ModelFusionQDQ
if (inp.idx >= 0 && inp.idx < (int)nargs)
uc[inp.idx]--;
}
layer = Ptr<Layer>();
layer = Ptr<LayerInfo>();
changed = true;
}
}
@@ -46,7 +46,7 @@ struct ModelFusionReshapeTranspose
bool fuseGraph(Ptr<Graph>& graph)
{
const vector<Ptr<Layer>>& prog = graph->prog();
const vector<Ptr<LayerInfo>>& prog = graph->prog();
size_t nops = prog.size();
bool modified = false;
@@ -72,7 +72,7 @@ struct ModelFusionReshapeTranspose
vector<bool> dropped(nops, false);
for (size_t i = 0; i < nops; i++) {
const Ptr<Layer>& layer = prog[i];
const Ptr<LayerInfo>& layer = prog[i];
if (!layer || dropped[i]) continue;
if (layer->inputs.empty() || layer->outputs.empty()) continue;
@@ -95,7 +95,7 @@ struct ModelFusionReshapeTranspose
if (it != producer.end()) {
int prod_idx = it->second;
if (prod_idx >= 0 && !dropped[prod_idx]) {
const Ptr<Layer>& pl = prog[prod_idx];
const Ptr<LayerInfo>& pl = prog[prod_idx];
TransposeLayer* prevTr = dynamic_cast<TransposeLayer*>(pl.get());
Arg prevOut = layer->inputs[0];
bool single_consumer = usecounts[prevOut.idx] == 1
@@ -135,7 +135,7 @@ struct ModelFusionReshapeTranspose
if (it != producer.end()) {
int prod_idx = it->second;
if (prod_idx >= 0 && !dropped[prod_idx]) {
const Ptr<Layer>& pl = prog[prod_idx];
const Ptr<LayerInfo>& pl = prog[prod_idx];
Reshape2Layer* prevRs = dynamic_cast<Reshape2Layer*>(pl.get());
Arg prevOut = layer->inputs[0];
bool single_consumer = usecounts[prevOut.idx] == 1
@@ -154,7 +154,7 @@ struct ModelFusionReshapeTranspose
}
if (modified) {
vector<Ptr<Layer>> newprog;
vector<Ptr<LayerInfo>> newprog;
newprog.reserve(nops);
for (size_t i = 0; i < nops; i++) {
if (!dropped[i] && prog[i])
@@ -166,7 +166,7 @@ struct ModelFusionReshapeTranspose
return modified;
}
void redirectConsumers(const vector<Ptr<Layer>>& prog,
void redirectConsumers(const vector<Ptr<LayerInfo>>& prog,
const vector<bool>& dropped,
size_t start_idx, Arg from, Arg to)
{
@@ -40,7 +40,7 @@ struct ModelFusionScaleSoftmax
bool fuseGraph(Ptr<Graph>& graph)
{
const vector<Ptr<Layer>>& prog = graph->prog();
const vector<Ptr<LayerInfo>>& prog = graph->prog();
size_t nops = prog.size();
bool modified = false;
@@ -70,7 +70,7 @@ struct ModelFusionScaleSoftmax
vector<bool> dropped(nops, false);
for (size_t i = 0; i < nops; i++) {
const Ptr<Layer>& layer = prog[i];
const Ptr<LayerInfo>& layer = prog[i];
if (!layer || dropped[i]) continue;
SoftmaxLayer* sm = dynamic_cast<SoftmaxLayer*>(layer.get());
@@ -83,7 +83,7 @@ struct ModelFusionScaleSoftmax
int prod_idx = it->second;
if (prod_idx < 0 || dropped[prod_idx]) continue;
const Ptr<Layer>& pl = prog[prod_idx];
const Ptr<LayerInfo>& pl = prog[prod_idx];
NaryEltwiseLayer* elt = dynamic_cast<NaryEltwiseLayer*>(pl.get());
if (!elt) continue;
const auto op = elt->op;
@@ -123,7 +123,7 @@ struct ModelFusionScaleSoftmax
}
if (modified) {
vector<Ptr<Layer>> newprog;
vector<Ptr<LayerInfo>> newprog;
newprog.reserve(nops);
for (size_t i = 0; i < nops; i++) {
if (!dropped[i] && prog[i])
+11 -11
View File
@@ -35,7 +35,7 @@ using std::string;
namespace {
static bool readGemmWeight(const Ptr<Layer>& l, bool trans_b, Mat& W_out)
static bool readGemmWeight(const Ptr<LayerInfo>& l, bool trans_b, Mat& W_out)
{
if (l->blobs.empty()) return false;
const Mat& W = l->blobs[0];
@@ -48,7 +48,7 @@ static bool readGemmWeight(const Ptr<Layer>& l, bool trans_b, Mat& W_out)
return true;
}
static bool readGemmBias(const Ptr<Layer>& l, Mat& b_out)
static bool readGemmBias(const Ptr<LayerInfo>& l, Mat& b_out)
{
if (l->blobs.size() < 2) { b_out.release(); return true; }
const Mat& b = l->blobs[1];
@@ -76,10 +76,10 @@ struct ModelFusionSharedGemm
bool flatten_a = true;
};
bool inspectGemm(const vector<Ptr<Layer>>& prog, int idx, GemmInfo& info) const
bool inspectGemm(const vector<Ptr<LayerInfo>>& prog, int idx, GemmInfo& info) const
{
if (idx < 0 || idx >= (int)prog.size() || !prog[idx]) return false;
const Ptr<Layer>& l = prog[idx];
const Ptr<LayerInfo>& l = prog[idx];
GemmLayer* g = dynamic_cast<GemmLayer*>(l.get());
if (!g) return false;
@@ -125,7 +125,7 @@ struct ModelFusionSharedGemm
bool fuseGraph(Ptr<Graph>& graph)
{
const vector<Ptr<Layer>>& prog = graph->prog();
const vector<Ptr<LayerInfo>>& prog = graph->prog();
size_t nops = prog.size();
struct Key { int input_idx; int trans_b; int K; };
@@ -145,7 +145,7 @@ struct ModelFusionSharedGemm
bool modified = false;
std::set<int> removed_ops;
vector<std::pair<int, vector<Ptr<Layer>>>> insertions; // (insert_pos, fused-and-slice layers)
vector<std::pair<int, vector<Ptr<LayerInfo>>>> insertions; // (insert_pos, fused-and-slice layers)
for (auto& group : groups) {
auto infos = group.second;
@@ -226,7 +226,7 @@ struct ModelFusionSharedGemm
fp.blobs.push_back(W_concat);
if (all_have_bias) fp.blobs.push_back(b_concat);
Ptr<Layer> fused = LayerFactory::createLayerInstance("Gemm", fp);
Ptr<LayerInfo> fused = LayerFactory::createLayerInstance("Gemm", fp);
if (!fused) continue;
string fused_out_name = fp.name + "_out";
@@ -235,7 +235,7 @@ struct ModelFusionSharedGemm
fused->outputs = { fused_out_arg };
fused->netimpl = netimpl;
vector<Ptr<Layer>> slices;
vector<Ptr<LayerInfo>> slices;
int col_cursor = 0;
for (size_t s = 0; s < infos.size(); s++) {
int N_s = infos[s].N;
@@ -257,7 +257,7 @@ struct ModelFusionSharedGemm
Arg axes_arg = netimpl->newConstArg(sp.name + "_axes", axes);
Arg steps_arg = netimpl->newConstArg(sp.name + "_steps", steps);
Ptr<Layer> slice = LayerFactory::createLayerInstance("Slice2", sp);
Ptr<LayerInfo> slice = LayerFactory::createLayerInstance("Slice2", sp);
if (!slice) { uniform = false; break; }
slice->inputs = { fused_out_arg, starts_arg, ends_arg, axes_arg, steps_arg };
slice->outputs = prog[infos[s].layer_idx]->outputs;
@@ -267,7 +267,7 @@ struct ModelFusionSharedGemm
if (!uniform) continue;
for (auto& info : infos) removed_ops.insert(info.layer_idx);
vector<Ptr<Layer>> bundle;
vector<Ptr<LayerInfo>> bundle;
bundle.push_back(fused);
for (auto& s : slices) bundle.push_back(s);
insertions.emplace_back(insert_pos, std::move(bundle));
@@ -279,7 +279,7 @@ struct ModelFusionSharedGemm
std::sort(insertions.begin(), insertions.end(),
[](auto& a, auto& b) { return a.first < b.first; });
vector<Ptr<Layer>> newprog;
vector<Ptr<LayerInfo>> newprog;
size_t ins_idx = 0;
for (size_t i = 0; i < nops; i++) {
while (ins_idx < insertions.size() &&
@@ -38,7 +38,7 @@ struct ModelFusionTransposeMatMul
bool fuseGraph(Ptr<Graph>& graph)
{
const vector<Ptr<Layer>>& prog = graph->prog();
const vector<Ptr<LayerInfo>>& prog = graph->prog();
size_t nops = prog.size();
bool modified = false;
@@ -68,7 +68,7 @@ struct ModelFusionTransposeMatMul
vector<bool> dropped(nops, false);
for (size_t i = 0; i < nops; i++) {
const Ptr<Layer>& layer = prog[i];
const Ptr<LayerInfo>& layer = prog[i];
if (!layer || dropped[i]) continue;
MatMulLayer* mm = dynamic_cast<MatMulLayer*>(layer.get());
@@ -83,7 +83,7 @@ struct ModelFusionTransposeMatMul
int prod_idx = it->second;
if (prod_idx < 0 || dropped[prod_idx]) continue;
const Ptr<Layer>& pl = prog[prod_idx];
const Ptr<LayerInfo>& pl = prog[prod_idx];
TransposeLayer* tr = dynamic_cast<TransposeLayer*>(pl.get());
if (!tr || pl->outputs.size() != 1) continue;
if (!isLastTwoSwap(tr->perm)) continue;
@@ -103,7 +103,7 @@ struct ModelFusionTransposeMatMul
}
if (modified) {
vector<Ptr<Layer>> newprog;
vector<Ptr<LayerInfo>> newprog;
newprog.reserve(nops);
for (size_t i = 0; i < nops; i++) {
if (!dropped[i] && prog[i])
+18
View File
@@ -48,6 +48,11 @@
namespace cv {
namespace dnn {
#ifdef HAVE_CUDA
void registerConv2CudaBackend(); // defined in layers/conv2_layer.cpp (plain cv::dnn namespace)
#endif
CV__DNN_INLINE_NS_BEGIN
static Mutex* __initialization_mutex = NULL;
@@ -76,6 +81,10 @@ public:
} // namespace
#endif
#ifdef HAVE_CUDA
void registerCudaCommonExecs(); // op_cuda.cpp (inline namespace)
#endif
void initializeLayerFactory()
{
CV_TRACE_FUNCTION();
@@ -84,8 +93,15 @@ void initializeLayerFactory()
static ProtobufShutdown protobufShutdown; CV_UNUSED(protobufShutdown);
#endif
#ifdef HAVE_CUDA
// New graph engine: per-op CUDA executors.
registerConv2CudaBackend();
registerCudaCommonExecs();
#endif
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);
@@ -216,6 +232,7 @@ void initializeLayerFactory()
CV_DNN_REGISTER_LAYER_CLASS(Attention, AttentionLayer);
CV_DNN_REGISTER_LAYER_CLASS(SDPA, SDPALayer);
CV_DNN_REGISTER_LAYER_CLASS(AttentionOnnxAi, AttentionOnnxAiLayer);
CV_DNN_REGISTER_LAYER_CLASS(CausalConvWithState, CausalConvWithStateLayer);
CV_DNN_REGISTER_LAYER_CLASS(RotaryEmbedding, RotaryEmbeddingLayer);
CV_DNN_REGISTER_LAYER_CLASS(GroupNormalization, GroupNormLayer);
CV_DNN_REGISTER_LAYER_CLASS(Cast, CastLayer);
@@ -252,6 +269,7 @@ void initializeLayerFactory()
CV_DNN_REGISTER_LAYER_CLASS(LSTM2, LSTM2Layer);
CV_DNN_REGISTER_LAYER_CLASS(GRU, GRULayer);
CV_DNN_REGISTER_LAYER_CLASS(CumSum, CumSumLayer);
CV_DNN_REGISTER_LAYER_CLASS(CumProd, CumProdLayer);
CV_DNN_REGISTER_LAYER_CLASS(Einsum, EinsumLayer);
CV_DNN_REGISTER_LAYER_CLASS(Hardmax, HardmaxLayer);
CV_DNN_REGISTER_LAYER_CLASS(GatherND, GatherNDLayer);
+62 -31
View File
@@ -10,46 +10,78 @@ namespace dnn {
CV__DNN_INLINE_NS_BEGIN
Layer::Layer() {
LayerInfo::LayerInfo() {
netimpl = nullptr;
preferableTarget = DNN_TARGET_CPU;
}
Layer::Layer(const LayerParams& params)
LayerInfo::LayerInfo(const LayerParams& params)
: blobs(params.blobs)
, name(params.name)
, type(params.type)
{
netimpl = nullptr;
preferableTarget = DNN_TARGET_CPU;
}
void Layer::setParamsFrom(const LayerParams& params)
LayerInfo::~LayerInfo() {}
void LayerInfo::setParamsFrom(const LayerParams& params)
{
blobs = params.blobs;
name = params.name;
type = params.type;
}
int Layer::inputNameToIndex(String)
int LayerInfo::inputNameToIndex(String)
{
return -1;
}
int Layer::outputNameToIndex(const String&)
int LayerInfo::outputNameToIndex(const String&)
{
return 0;
}
Layer::Layer() {
netimpl = nullptr;
preferableTarget = DNN_TARGET_CPU;
}
Layer::Layer(const LayerParams& params)
: LayerInfo(params)
{
preferableTarget = DNN_TARGET_CPU;
}
bool Layer::supportBackend(int backendId)
{
return backendId == DNN_BACKEND_OPENCV;
}
Ptr<BackendNode> Layer::initCUDA(
void* context,
const std::vector<Ptr<BackendWrapper>>& inputs,
const std::vector<Ptr<BackendWrapper>>& outputs)
{
#ifdef HAVE_CUDA
// Adapt the classic wrapper-based entry point to the array-based one, so ops ported to the
// new graph engine only need to override initCUDA(context, inputs, outputs) with GpuMatND.
std::vector<cuda::GpuMatND> inGpu(inputs.size()), outGpu(outputs.size());
for (size_t i = 0; i < inputs.size(); i++)
inGpu[i] = inputs[i].dynamicCast<CUDABackendWrapper>()->getDeviceMatND();
for (size_t i = 0; i < outputs.size(); i++)
outGpu[i] = outputs[i].dynamicCast<CUDABackendWrapper>()->getDeviceMatND();
return initCUDA(context, inGpu, outGpu);
#else
CV_UNUSED(context); CV_UNUSED(inputs); CV_UNUSED(outputs);
CV_Error(Error::StsNotImplemented, "CUDA pipeline of " + type + " layers is not defined.");
return Ptr<BackendNode>();
#endif
}
Ptr<BackendNode> Layer::initCUDA(
void*,
const std::vector<Ptr<BackendWrapper>>&,
const std::vector<Ptr<BackendWrapper>>&)
InputArrayOfArrays,
InputArrayOfArrays)
{
CV_Error(Error::StsNotImplemented, "CUDA pipeline of " + type + " layers is not defined.");
return Ptr<BackendNode>();
@@ -94,13 +126,19 @@ Ptr<BackendNode> Layer::initCann(const std::vector<Ptr<BackendWrapper> > &inputs
bool Layer::setActivation(const Ptr<ActivationLayer>&) { return false; }
bool Layer::tryFuse(Ptr<Layer>&) { return false; }
void Layer::getScaleShift(Mat& scale, Mat& shift) const
void Layer::forwardCUDA(InputArrayOfArrays, OutputArrayOfArrays, void*)
{
CV_Error(Error::StsNotImplemented, "CUDA forward of " + type + " layers is not defined.");
}
void LayerInfo::getScaleShift(Mat& scale, Mat& shift) const
{
scale = Mat();
shift = Mat();
}
void Layer::getScaleZeropoint(float& scale, int& zeropoint) const
void LayerInfo::getScaleZeropoint(float& scale, int& zeropoint) const
{
scale = 1.f;
zeropoint = 0;
@@ -247,7 +285,7 @@ void Layer::run(const std::vector<Mat>& inputs, std::vector<Mat>& outputs, std::
Layer::~Layer() {}
bool Layer::getMemoryShapes(const std::vector<MatShape>& inputs,
bool LayerInfo::getMemoryShapes(const std::vector<MatShape>& inputs,
const int requiredOutputs,
std::vector<MatShape>& outputs,
std::vector<MatShape>& internals) const
@@ -257,7 +295,7 @@ bool Layer::getMemoryShapes(const std::vector<MatShape>& inputs,
return false;
}
void Layer::getTypes(const std::vector<MatType>&inputs,
void LayerInfo::getTypes(const std::vector<MatType>&inputs,
const int requiredOutputs,
const int requiredInternals,
std::vector<MatType>&outputs,
@@ -265,20 +303,13 @@ void Layer::getTypes(const std::vector<MatType>&inputs,
{
CV_Assert(inputs.size());
for (auto input : inputs)
{
if (preferableTarget == DNN_TARGET_CUDA_FP16 || preferableTarget == DNN_TARGET_CUDA)
CV_CheckTypeEQ(input, CV_32F, "");
else if (preferableTarget == DNN_TARGET_OPENCL_FP16)
CV_CheckType(input, input == CV_16F || input == CV_8S || input == CV_8U || input == CV_64F || input == CV_64S, "");
else
CV_CheckType(input, input == CV_32F || input == CV_64F || input == CV_8S || input == CV_8U || input == CV_64S, "");
}
CV_CheckType(input, input == CV_32F || input == CV_64F || input == CV_8S || input == CV_8U || input == CV_64S, "");
outputs.assign(requiredOutputs, inputs[0]);
internals.assign(requiredInternals, inputs[0]);
}
int Layer::getLayouts(const std::vector<DataLayout>& actualInputs,
int LayerInfo::getLayouts(const std::vector<DataLayout>& actualInputs,
std::vector<DataLayout>& desiredInputs,
const int requiredOutputs,
std::vector<DataLayout>& outputs) const
@@ -288,43 +319,43 @@ int Layer::getLayouts(const std::vector<DataLayout>& actualInputs,
return 0;
}
int64 Layer::getFLOPS(const std::vector<MatShape>&,
int64 LayerInfo::getFLOPS(const std::vector<MatShape>&,
const std::vector<MatShape>&) const
{
return 0;
}
bool Layer::updateMemoryShapes(const std::vector<MatShape>& inputs)
bool LayerInfo::updateMemoryShapes(const std::vector<MatShape>& inputs)
{
return true;
}
std::vector<Ptr<Graph> >* Layer::subgraphs() const
std::vector<Ptr<Graph> >* LayerInfo::subgraphs() const
{
return nullptr;
}
bool Layer::alwaysSupportInplace() const
bool LayerInfo::alwaysSupportInplace() const
{
return false;
}
bool Layer::dynamicOutputShapes() const
bool LayerInfo::dynamicOutputShapes() const
{
return false;
}
bool Layer::isDataShuffling() const
bool LayerInfo::isDataShuffling() const
{
return false;
}
std::ostream& Layer::dumpAttrs(std::ostream& strm, int) const
std::ostream& LayerInfo::dumpAttrs(std::ostream& strm, int) const
{
return strm;
}
std::ostream& Layer::dump(std::ostream& strm, int indent, bool comma) const
std::ostream& LayerInfo::dump(std::ostream& strm, int indent, bool comma) const
{
CV_Assert(netimpl);
size_t ninputs = inputs.size();
@@ -376,7 +407,7 @@ std::ostream& Layer::dump(std::ostream& strm, int indent, bool comma) const
std::vector<std::string> names;
if (opname == "If")
names = {"then", "else"};
else if (opname == "Loop")
else if (opname == "Loop" || opname == "Scan")
names = {"body"};
else {
CV_Error(Error::StsError,
+66
View File
@@ -104,6 +104,72 @@ Ptr<Layer> LayerFactory::createLayerInstance(const String& type, LayerParams& pa
}
}
typedef std::map<std::string, LayerFactory::OpConstructor> OpFactory_Impl;
typedef std::map<std::string, std::map<int, LayerFactory::ExecConstructor> > ExecFactory_Impl;
static OpFactory_Impl& getOpFactoryImpl()
{
static OpFactory_Impl impl;
return impl;
}
static ExecFactory_Impl& getExecFactoryImpl()
{
static ExecFactory_Impl impl;
return impl;
}
void LayerFactory::registerOp(const String& type, OpConstructor constructor)
{
CV_TRACE_FUNCTION();
CV_TRACE_ARG_VALUE(type, "type", type.c_str());
CV_Assert(constructor);
cv::AutoLock lock(getLayerFactoryMutex());
getOpFactoryImpl()[type] = constructor; // last registration wins
}
Ptr<LayerInfo> LayerFactory::createOp(const String& type, const LayerParams& params)
{
CV_TRACE_FUNCTION();
CV_TRACE_ARG_VALUE(type, "type", type.c_str());
cv::AutoLock lock(getLayerFactoryMutex());
OpFactory_Impl& impl = getOpFactoryImpl();
OpFactory_Impl::const_iterator it = impl.find(type);
if (it != impl.end())
return it->second(params);
return Ptr<LayerInfo>(); // NULL: no LayerInfo constructor for this type yet
}
void LayerFactory::registerExec(const String& type, int backendId, ExecConstructor constructor)
{
CV_TRACE_FUNCTION();
CV_TRACE_ARG_VALUE(type, "type", type.c_str());
CV_Assert(constructor);
cv::AutoLock lock(getLayerFactoryMutex());
getExecFactoryImpl()[type][backendId] = constructor;
}
Ptr<Layer> LayerFactory::createExec(const String& type, int backendId,
const Ptr<LayerInfo>& data, void* backendCtx)
{
CV_TRACE_FUNCTION();
CV_TRACE_ARG_VALUE(type, "type", type.c_str());
ExecConstructor ctor = nullptr;
{
cv::AutoLock lock(getLayerFactoryMutex());
ExecFactory_Impl& impl = getExecFactoryImpl();
ExecFactory_Impl::const_iterator it = impl.find(type);
if (it != impl.end()) {
auto bit = it->second.find(backendId);
if (bit != it->second.end())
ctor = bit->second;
}
}
if (ctor)
return ctor(data, backendCtx);
return Ptr<Layer>();
}
CV__DNN_INLINE_NS_END
}} // namespace cv::dnn
+7 -4
View File
@@ -53,8 +53,11 @@ static void avgPool32f(const void* inp_, void* out_,
float* out = (float*)out_ + nc0*planesize;
float iksize = 1.f/ksize;
#if CV_SIMD || CV_SIMD_SCALABLE
#if CV_SIMD
int nlanes = VTraits<v_float32>::vlanes();
// RVV (CV_SIMD_SCALABLE) disabled for the m1 switch: with the fixed C0=8 the
// block is narrower than the register at VLEN>=512, tripping this assert. Runs
// scalar on RVV; re-enable via v_setvlmax<v_float32>(C0) (#29493, cf #29180).
CV_Assert(C0 == nlanes || C0 == nlanes*2 || C0 % (nlanes*4) == 0);
v_float32 z = vx_setzero_f32();
v_float32 vscale0 = vx_setall_f32(iksize);
@@ -69,12 +72,12 @@ static void avgPool32f(const void* inp_, void* out_,
y0 >= inner_y0 && y0 < inner_y1 ? inner_x0 : W;
int yi_ = y0*SY - padY0;
#if !(CV_SIMD || CV_SIMD_SCALABLE)
#if !(CV_SIMD)
memset(out, 0, W*C0*sizeof(out[0]));
#endif
for(;;) {
#if CV_SIMD || CV_SIMD_SCALABLE
#if CV_SIMD
if (nlanes == C0) {
for (; x0 < x1; x0++) {
int xi_ = x0*SX - padX0;
@@ -162,7 +165,7 @@ static void avgPool32f(const void* inp_, void* out_,
break;
x1 = inner_x1;
#if CV_SIMD || CV_SIMD_SCALABLE
#if CV_SIMD
if (nlanes == C0) {
for (; x0 < x1; x0++) {
int xi_ = x0*SX - padX0;
+23 -1
View File
@@ -7,6 +7,10 @@
#include "../precomp.hpp"
#include "layers_common.hpp"
#include "../net_impl.hpp"
#include "../op_cuda.hpp"
#ifdef HAVE_CUDA
#include "../cuda4dnn/primitives/batch_norm.hpp"
#endif
namespace cv {
namespace dnn {
@@ -355,9 +359,27 @@ public:
bool supportBackend(int backendId) CV_OVERRIDE
{
return backendId == DNN_BACKEND_OPENCV;
return backendId == DNN_BACKEND_OPENCV
#ifdef HAVE_CUDA
|| backendId == DNN_BACKEND_CUDA
#endif
;
}
#ifdef HAVE_CUDA
// Channel-wise scale+shift on CUDA; reused by the new graph engine via CUDALegacyExec.
Ptr<BackendNode> initCUDA(void* context_,
InputArrayOfArrays,
InputArrayOfArrays) CV_OVERRIDE
{
auto context = reinterpret_cast<cuda4dnn::csl::CSLContext*>(context_);
Mat scale, bias;
getScaleBias(scale, bias); // per-channel FP32 scale and shift
return make_cuda_node<cuda4dnn::BatchNormOp>(
preferableTarget, std::move(context->stream), scale, bias);
}
#endif
MatShape getOutShape(const MatShape& inpShape) const
{
return inpShape;
+17 -4
View File
@@ -118,8 +118,16 @@ namespace
ushort* out = dst_bits.ptr<ushort>(r);
for (int i = 0; i < cols_x_cn; ++i)
{
// float32 -> bfloat16 with round-to-nearest-even (matches ONNX).
Cv32suf u; u.f = in[i];
out[i] = (ushort)(u.u >> 16);
const uint32_t x = u.u;
if ((x & 0x7fffffffu) > 0x7f800000u) // NaN: keep it NaN
out[i] = (ushort)((x >> 16) | 0x0040u);
else
{
const uint32_t bias = 0x7fffu + ((x >> 16) & 1u);
out[i] = (ushort)((x + bias) >> 16);
}
}
}
return;
@@ -210,7 +218,6 @@ public:
const int in0CN = in0Type >= 0 ? CV_MAT_CN(in0Type) : 1;
int planDepth = targetDepth;
if (planDepth == CV_16F) planDepth = CV_32F;
if (planDepth == CV_16BF) planDepth = CV_16U;
const int outType = CV_MAKETYPE(planDepth, in0CN);
outputs.assign(1, outType);
}
@@ -243,6 +250,13 @@ public:
return false;
}
// bf16 needs the float->bfloat16 bit reduction; a plain OpenCL convertTo
// would numerically round instead. Fall back to the CPU path.
if (runtimeTargetDepth == CV_16BF)
{
return false;
}
if (inputs[0].depth() == outputs[0].depth())
inputs[0].copyTo(outputs[0]);
else
@@ -291,8 +305,7 @@ public:
}
CV_CheckGE(runtimeTargetDepth, 0, "Cast: failed to resolve target data type at runtime");
int plannedDDepth = (runtimeTargetDepth == CV_16F) ? CV_32F :
(runtimeTargetDepth == CV_16BF ? CV_16U : runtimeTargetDepth);
int plannedDDepth = (runtimeTargetDepth == CV_16F) ? CV_32F : runtimeTargetDepth;
if (dst0.depth() != plannedDDepth)
dst0.create(dst0.size(), CV_MAKETYPE(plannedDDepth, src0.channels()));
@@ -0,0 +1,146 @@
// 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 "layers_common.hpp"
#include <opencv2/dnn/shape_utils.hpp>
#include <cmath>
namespace cv {
namespace dnn {
/*
Implementation of CausalConvWithState, as defined in ONNX specification:
https://onnx.ai/onnx/operators/onnx__CausalConvWithState.html
Opset 27 is covered.
*/
class CausalConvWithStateLayerImpl CV_FINAL : public CausalConvWithStateLayer
{
public:
CausalConvWithStateLayerImpl(const LayerParams& params)
{
setParamsFrom(params);
std::string act = params.get<std::string>("activation", "");
silu = (act == "silu" || act == "swish");
CV_Check(act, act.empty() || silu, "CausalConvWithState: unsupported activation");
}
bool supportBackend(int backendId) CV_OVERRIDE { return backendId == DNN_BACKEND_OPENCV; }
static bool present(const std::vector<Mat>& in, size_t i) { return in.size() > i && !in[i].empty(); }
void getTypes(const std::vector<MatType>& inputs, const int requiredOutputs, const int,
std::vector<MatType>& outputs, std::vector<MatType>& internals) const CV_OVERRIDE
{
CV_CheckType(inputs[0], inputs[0] == CV_32F || inputs[0] == CV_16F, "");
outputs.assign(requiredOutputs, inputs[0]);
internals.clear();
}
bool getMemoryShapes(const std::vector<MatShape>& inputs, const int,
std::vector<MatShape>& outputs, std::vector<MatShape>&) const CV_OVERRIDE
{
CV_CheckGE(inputs.size(), (size_t)2, "CausalConvWithState needs input and weight");
CV_CheckEQ(inputs[0].dims, 3, "input must be [batch, channels, seq]");
CV_CheckEQ(inputs[1].dims, 3, "weight must be [channels, 1, kernel]");
const int B = inputs[0][0], C = inputs[0][1], T = inputs[0][2], K = inputs[1][2];
outputs.assign(1, MatShape{B, C, T});
outputs.push_back(MatShape{B, C, K - 1});
return false;
}
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays) CV_OVERRIDE
{
std::vector<Mat> rawIn, rawOut;
inputs_arr.getMatVector(rawIn);
outputs_arr.getMatVector(rawOut);
const bool fp16 = rawIn[0].depth() == CV_16F;
std::vector<Mat> in32, out32;
if (fp16)
{
in32.resize(rawIn.size());
for (size_t i = 0; i < rawIn.size(); ++i)
if (!rawIn[i].empty()) rawIn[i].convertTo(in32[i], CV_32F);
out32.resize(rawOut.size());
for (size_t i = 0; i < rawOut.size(); ++i)
out32[i].create(rawOut[i].dims, rawOut[i].size.p, CV_32F);
}
std::vector<Mat>& inputs = fp16 ? in32 : rawIn;
std::vector<Mat>& outputs = fp16 ? out32 : rawOut;
const Mat& input = inputs[0];
const Mat& weight = inputs[1];
const bool has_bias = present(inputs, 2);
const bool has_past = present(inputs, 3);
const int B = input.size[0], C = input.size[1], T = input.size[2];
const int K = weight.size[2];
const int P = K - 1; // state / left-pad width
const float* Ip = input.ptr<float>();
const float* Wp = weight.ptr<float>();
const float* Bp = has_bias ? inputs[2].ptr<float>() : nullptr;
const float* Sp = has_past ? inputs[3].ptr<float>() : nullptr;
float* Op = outputs[0].ptr<float>();
float* PSp = outputs[1].ptr<float>();
parallel_for_(Range(0, B * C), [&](const Range& r)
{
std::vector<float> pad(P + T);
for (int bc = r.start; bc < r.end; ++bc)
{
const int c = bc % C;
const float* x = Ip + (size_t)bc * T;
const float* w = Wp + (size_t)c * K;
for (int j = 0; j < P; ++j)
pad[j] = has_past ? Sp[(size_t)bc * P + j] : 0.f;
for (int t = 0; t < T; ++t)
pad[P + t] = x[t];
const float bias = has_bias ? Bp[c] : 0.f;
float* o = Op + (size_t)bc * T;
for (int t = 0; t < T; ++t)
{
float acc = bias;
for (int k = 0; k < K; ++k)
acc += w[k] * pad[t + k];
o[t] = acc;
}
if (silu)
for (int t = 0; t < T; ++t)
o[t] = o[t] / (1.f + std::exp(-o[t]));
float* ps = PSp + (size_t)bc * P;
for (int j = 0; j < P; ++j)
ps[j] = pad[T + j];
}
});
if (fp16)
for (size_t i = 0; i < rawOut.size(); ++i)
out32[i].convertTo(rawOut[i], CV_16F);
}
int64 getFLOPS(const std::vector<MatShape>& inputs, const std::vector<MatShape>&) const CV_OVERRIDE
{
const int64 B = inputs[0][0], C = inputs[0][1], T = inputs[0][2], K = inputs[1][2];
return B * C * T * (2 * K + 4);
}
private:
bool silu = false;
};
Ptr<CausalConvWithStateLayer> CausalConvWithStateLayer::create(const LayerParams& params)
{
return makePtr<CausalConvWithStateLayerImpl>(params);
}
}} // namespace cv::dnn
+8 -4
View File
@@ -396,7 +396,7 @@ MatShape deconvInferShape(const MatShape& inpShape, const MatShape& wshape,
const std::vector<int>& adjustPads,
AutoPadding autoPad)
{
bool blockLayout = true;
bool blockLayout = (inpShape.layout == DATA_LAYOUT_BLOCK);
int ndims = inpShape.dims;
int nspatialdims = ndims - 2 - int(blockLayout);
CV_Assert(nspatialdims >= 1);
@@ -413,9 +413,13 @@ MatShape deconvInferShape(const MatShape& inpShape, const MatShape& wshape,
kshape_[i] = wshape[i + 2];
}
int C0 = inpShape[ndims - 1];
int K_out = ngroups * wshape[1];
outshape[1] = (K_out + C0 - 1) / C0;
if (blockLayout) {
int C0 = inpShape[ndims - 1];
outshape[1] = (K_out + C0 - 1) / C0;
} else {
outshape[1] = K_out;
}
CV_Assert(strides.empty() || (int)strides.size() == nspatialdims);
CV_Assert(dilations.empty() || (int)dilations.size() == nspatialdims);
@@ -439,7 +443,7 @@ MatShape deconvInferShape(const MatShape& inpShape, const MatShape& wshape,
}
outshape[i + 2] = outsz;
}
outshape.C = K_out;
outshape.C = blockLayout ? K_out : 0;
return outshape;
}
+148
View File
@@ -11,6 +11,13 @@
#include <algorithm>
#include <cstring>
#include "../op_cuda.hpp"
#include <opencv2/dnn/layer.details.hpp> // CV_DNN_REGISTER_EXEC_CLASS
#ifdef HAVE_CUDA
#include "../cuda4dnn/primitives/convolution.hpp"
using namespace cv::dnn::cuda4dnn;
#endif
namespace cv
{
namespace dnn
@@ -115,6 +122,10 @@ public:
int wtype = accuracy < 0 ? CV_32F : accuracy;
wshape0 = weights_.shape();
#ifdef HAVE_CUDA
// Retain the original NCHW filter for the CUDA (cuDNN) path.
weights_.convertTo(origWeights, CV_32F);
#endif
bool depthwise = ngroups == wshape0[0] && wshape0[1] == 1;
if (depthwise) {
@@ -660,9 +671,96 @@ public:
}
}
#ifdef HAVE_CUDA
bool cudaSupported() const
{
if (origWeights.empty() || wshape0.dims != 4) // [Cout, Cin/group, kh, kw] (2D conv)
return false;
if (auto_pad != AUTO_PAD_NONE && auto_pad != AUTO_PAD_VALID)
return false;
if (activationFunc != nullptr || !activ.empty())
return false;
if (fastActivation != FAST_ACTIV_NONE && fastActivation != FAST_ACTIV_RELU &&
fastActivation != FAST_ACTIV_LEAKY_RELU && fastActivation != FAST_ACTIV_CLIP)
return false;
return true;
}
Ptr<BackendNode> initCudaConvNode(void* context_, const MatShape& inpShape,
const MatShape& outShape, int targetId)
{
csl::CSLContext context = *reinterpret_cast<csl::CSLContext*>(context_);
const int nspatial = wshape0.dims - 2;
ConvolutionConfiguration config;
for (int i = 0; i < nspatial; i++) {
config.kernel_size.push_back((size_t)wshape0[2 + i]);
config.strides.push_back(strides.empty() ? 1 : (size_t)strides[i]);
config.dilations.push_back(dilations.empty() ? 1 : (size_t)dilations[i]);
}
if (auto_pad == AUTO_PAD_VALID) {
config.padMode = ConvolutionConfiguration::PaddingMode::VALID;
} else {
config.padMode = ConvolutionConfiguration::PaddingMode::MANUAL;
for (int i = 0; i < nspatial; i++) {
config.pads_begin.push_back(pads.empty() ? 0 : (size_t)pads[i]);
config.pads_end.push_back(pads.empty() ? 0 : (size_t)pads[i + nspatial]);
}
}
config.input_shape.assign(inpShape.begin(), inpShape.end());
config.output_shape.assign(outShape.begin(), outShape.end());
config.groups = (size_t)ngroups;
Mat filters = origWeights, biasMat = bias;
if (fusedBatchNorm) {
filters = origWeights.clone();
const int Cout = wshape0[0];
const size_t inner = filters.total() / (size_t)Cout;
const float* sc = fusedScale.ptr<float>();
float* wp = filters.ptr<float>();
for (int co = 0; co < Cout; co++) {
float s = sc[co];
for (size_t k = 0; k < inner; k++)
wp[co * inner + k] *= s;
}
biasMat = fusedBias; // already b*scale + bn_bias
}
config.activation_type = ConvolutionConfiguration::ActivationType::IDENTITY;
config.relu_negative_slope = 0.f;
config.crelu_floor = 0.f; config.crelu_ceil = 0.f;
config.power_exp = 1.f; config.power_scale = 1.f; config.power_shift = 0.f;
bool hasAct = fastActivation != FAST_ACTIV_NONE;
if (fastActivation == FAST_ACTIV_RELU) {
config.activation_type = ConvolutionConfiguration::ActivationType::RELU;
} else if (fastActivation == FAST_ACTIV_LEAKY_RELU) {
config.activation_type = ConvolutionConfiguration::ActivationType::RELU;
config.relu_negative_slope = activParams.empty() ? 0.f : activParams[0];
} else if (fastActivation == FAST_ACTIV_CLIP) {
config.activation_type = ConvolutionConfiguration::ActivationType::CLIPPED_RELU;
config.crelu_floor = activParams.size() > 0 ? activParams[0] : 0.f;
config.crelu_ceil = activParams.size() > 1 ? activParams[1] : 6.f;
}
if (addResidual && hasAct)
config.fusion_mode = ConvolutionConfiguration::FusionMode::ELTWISE_SUM_THEN_ACTIVATION;
else if (addResidual)
config.fusion_mode = ConvolutionConfiguration::FusionMode::ELTWISE_SUM;
else if (hasAct)
config.fusion_mode = ConvolutionConfiguration::FusionMode::ACTIVATION;
else
config.fusion_mode = ConvolutionConfiguration::FusionMode::NONE;
return make_cuda_node<cuda4dnn::ConvolutionOp>(
targetId, std::move(context.stream), std::move(context.cudnn_handle),
config, filters, biasMat);
}
#endif
std::vector<int> emptyKernelShape;
Ptr<Layer> activ, batchNorm;
Mat weights, bias, fusedScale, fusedBias;
Mat origWeights; // original NCHW filter (FP32), kept for the CUDA path
MatShape wshape0, prevInpshape;
ConvState cs;
bool fusedBatchNorm;
@@ -684,4 +782,54 @@ Ptr<Conv2Layer> Conv2Layer::create(const LayerParams& params)
return Ptr<Conv2Layer>(new Conv2LayerImpl(params));
}
#ifdef HAVE_CUDA
class CUDAConv2Layer : public Layer
{
public:
CUDAConv2Layer(const Ptr<Conv2LayerImpl>& conv_, void* ctx_) : conv(conv_), ctx(ctx_) {}
static Ptr<Layer> create(const Ptr<LayerInfo>& data, void* backendCtx)
{
Ptr<Conv2LayerImpl> conv = data.dynamicCast<Conv2LayerImpl>();
if (!conv || !backendCtx || !conv->cudaSupported())
return Ptr<Layer>();
Ptr<CUDAConv2Layer> layer(new CUDAConv2Layer(conv, backendCtx));
layer->name = conv->name;
layer->type = conv->type;
layer->inputs = conv->inputs;
layer->outputs = conv->outputs;
return layer;
}
void forwardCUDA(InputArrayOfArrays inputs_,
OutputArrayOfArrays outputs_,
void* workspace) CV_OVERRIDE
{
std::vector<cuda::GpuMatND> inputs, outputs;
inputs_.getGpuMatNDVector(inputs);
outputs_.getGpuMatNDVector(outputs);
CV_Assert(!inputs.empty() && !outputs.empty());
auto& ws = *reinterpret_cast<cuda4dnn::csl::Workspace*>(workspace);
if (!node) {
node = conv->initCudaConvNode(ctx, inputs[0].size, outputs[0].size, preferableTarget);
cudaNode = node.dynamicCast<CUDABackendNode>();
CV_Assert(cudaNode);
ws.require(cudaNode->get_workspace_memory_in_bytes());
}
cudaNode->forward(inputs, outputs, ws);
}
Ptr<Conv2LayerImpl> conv;
void* ctx;
Ptr<BackendNode> node;
Ptr<CUDABackendNode> cudaNode;
};
void registerConv2CudaBackend()
{
CV_DNN_REGISTER_EXEC_CLASS(Conv2, DNN_BACKEND_CUDA, CUDAConv2Layer);
}
#endif
}}
@@ -38,14 +38,7 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
parallel_for_(Range(0, NC1), [&](const Range& range)
{
constexpr int MAX_CONV_DIMS = ConvState::MAX_CONV_DIMS;
#if CV_SIMD_SCALABLE
// RVV: universal intrinsics use LMUL=2, so the float vector width tracks
// the hardware VLEN at runtime. The block size C0 == defaultC0 == vlanes()
// (e.g. 16 at VLEN=256), so read it at runtime instead of fixing it to 8 (#28852).
const int C0 = (int)cs.inpshape.back();
#else
constexpr int C0 = 8;
#endif
CV_Assert(cs.nspatialdims <= MAX_CONV_DIMS && MAX_CONV_DIMS == 3);
CV_Assert(C0 == cs.inpshape.back());
@@ -80,14 +73,7 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
const float* activParams = cs.activParams.data();
ActivationFunc activation = cs.activation;
float maxval = FLT_MAX, defaultAlpha = 0.f;
#if CV_SIMD_SCALABLE
// C0 is runtime on RVV; size scratch by the compile-time upper bound.
float scalebuf[VTraits<v_float32>::max_nlanes];
float biasbuf[VTraits<v_float32>::max_nlanes];
float alphabuf[VTraits<v_float32>::max_nlanes];
#else
float scalebuf[C0], biasbuf[C0], alphabuf[C0];
#endif
if (fastActivation == FAST_ACTIV_CLIP) {
CV_Assert(cs.activParams.size() == 2u);
maxval = activParams[1];
@@ -103,10 +89,13 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
defaultAlpha = 1.f;
}
#if CV_SIMD || CV_SIMD_SCALABLE
#if CV_SIMD
v_float32 v_maxval = vx_setall_f32(maxval);
v_float32 z = vx_setzero_f32();
const int nlanes = VTraits<v_float32>::vlanes();
// RVV (CV_SIMD_SCALABLE) disabled for the m1 switch: with the fixed C0=8 the
// block is narrower than the register at VLEN>=512, tripping this assert. Runs
// scalar on RVV; re-enable via v_setvlmax<v_float32>(C0) (#29493, cf #29180).
CV_Assert(C0 == nlanes || C0 == nlanes*2 || C0 % (nlanes*4) == 0);
#endif
@@ -140,12 +129,12 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
y0 >= inner_y0 && y0 < inner_y1 ? inner_x0 : W;
int yi_ = y0*SY - padY0;
#if !(CV_SIMD || CV_SIMD_SCALABLE)
#if !(CV_SIMD)
memset(out, 0, W*C0*sizeof(out[0]));
#endif
for(;;) {
#if CV_SIMD || CV_SIMD_SCALABLE
#if CV_SIMD
if (nlanes == C0) {
v_float32 sc0 = vx_load(scalebuf), b0 = vx_load(biasbuf);
v_float32 alpha0 = vx_load(alphabuf);
@@ -232,7 +221,7 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
break;
x1 = inner_x1;
#if CV_SIMD || CV_SIMD_SCALABLE
#if CV_SIMD
if (nlanes == C0) {
v_float32 sc0 = vx_load(scalebuf), b0 = vx_load(biasbuf), alpha0 = vx_load(alphabuf);
for (; x0 < x1; x0++) {
@@ -349,7 +338,7 @@ static void depthwiseConv32f(const void* inp__, const void* residual__,
x1 = W;
}
#if !(CV_SIMD || CV_SIMD_SCALABLE)
#if !(CV_SIMD)
if (residual) {
for (int x = 0; x < W*C0; x += C0) {
for (int c = 0; c < C0; c++) {
@@ -386,7 +386,13 @@ CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN
CONV_FINALIZE_OUT2(8, 9, CONV_ADD_NO_RESIDUAL2); \
}
#elif CV_SIMD_SCALABLE
// TODO(#29493): RVV conv is temporarily scalar. This universal path assumed
// K0 == vlanes(), which under m1 holds only at VLEN=256. Disabled as in #29180.
// Re-enable in a follow-up with a portable v_setvlmax<v_float32>(K0) universal
// intrinsic (no-op on fixed-width ISAs, sets the vl/mask on RVV/SVE) so one vector
// spans exactly the K0=8 block at any VLEN; the optimized multi-pixel case is a
// later RVV-HAL step. See PR #29493 discussion.
#elif 0 // CV_SIMD_SCALABLE: RVV temporarily disabled for the m1 switch (#29493)
/////////////////////////// scalable (RVV) implementation /////////////////////////////
// K0 == vlanes(), so each of the 10 spatial positions needs exactly one vector
@@ -603,8 +609,7 @@ static void scatterScalarOut(bool aligned_k, int k_base, int k_count, int K0shif
#define CONV_INIT_SCALAR_SUMS() \
v_float32x4 zz = v_setzero_f32(); \
v_float32x4 s0 = zz, s1 = zz
#elif CV_SIMD_SCALABLE
// RVV: K0 == vlanes(), so a single scalable vector spans the whole output block.
#elif 0 // CV_SIMD_SCALABLE: RVV temporarily disabled for the m1 switch (#29493)
#define CONV_INIT_SCALAR_SUMS() \
v_float32 zz = vx_setzero_f32(); \
v_float32 s0 = zz
@@ -642,7 +647,7 @@ static void scatterScalarOut(bool aligned_k, int k_base, int k_count, int K0shif
s0 = v_min(s0, _vmx); s1 = v_min(s1, _vmx); \
v_store((outbuf), s0); v_store((outbuf) + 4, s1); \
}
#elif CV_SIMD_SCALABLE
#elif 0 // CV_SIMD_SCALABLE: RVV temporarily disabled for the m1 switch (#29493)
#define CONV_FINALIZE_SCALAR_OUT(outbuf) \
{ \
v_float32 _vsc = vx_load(scalebuf); \
@@ -2202,17 +2207,11 @@ static void conv32fC8(const void* inp__, const void* residual__, void* out__,
parallel_for_(Range(0, total_tasks_gen), [&](const Range& range) {
constexpr int SPAT_BLOCK_SIZE = 10;
#if CV_SIMD_SCALABLE
// RVV: block size follows the runtime vector width (defaultC0 = vlanes(), LMUL=2).
const int C0 = (int)inpshape.back();
int C0shift = 0; while ((1 << C0shift) < C0) C0shift++;
const int K0 = C0, K0shift = C0shift;
constexpr int C0BUF = VTraits<v_float32>::max_nlanes; // compile-time scratch bound
#else
// The block size is a fixed property of the blocked layout on every platform,
// so C0/K0 are compile-time constants here (#29493).
constexpr int C0shift = 3, K0shift = C0shift;
constexpr int C0 = 1 << C0shift, K0 = C0;
constexpr int C0BUF = K0;
#endif
CV_Assert_N(inpshape.back() == C0, outshape.back() == K0);
@@ -2523,10 +2522,7 @@ static void conv32fC8(const void* inp__, const void* residual__, void* out__,
CONV_UPDATE_BLOCK1(5);
CONV_UPDATE_BLOCK1(6);
CONV_UPDATE_BLOCK1(7);
#elif CV_SIMD_SCALABLE
// RVV: K0 == vlanes(); weights are contiguous over kk for a fixed c0,
// so one vx_load gives the whole K0-wide weight vector. Broadcast the
// input lane and accumulate into the persistent block accumulator.
#elif 0 // CV_SIMD_SCALABLE: RVV temporarily disabled for the m1 switch (#29493)
for (int c0 = 0; c0 < C0; ++c0) {
v_float32 w = vx_load(wptr + c0*K0);
v_float32 x = vx_setall_f32(inptr[c0]);
@@ -2557,16 +2553,9 @@ static void conv32fC8(const void* inp__, const void* residual__, void* out__,
cv::dnn::ConvFunc getConvFunc_(int depth, int C0)
{
ConvFunc func = nullptr;
#if CV_SIMD_SCALABLE
// RVV: block size follows the runtime vector width; accept the supported pow2 widths.
if (depth == CV_32F && (C0 == 8 || C0 == 16 || C0 == 32 || C0 == 64)) {
func = conv32fC8;
}
#else
if (depth == CV_32F && C0 == 8) {
func = conv32fC8;
}
#endif
return func;
}
@@ -102,7 +102,7 @@ void run_fused_softmax(
const int tmax = is_causal ? std::min(past_seq_len + tq + 1, seq_len_kv) : seq_len_kv;
float maxVal = -FLT_MAX;
int tk = 0;
#if CV_SIMD
#if (CV_SIMD || CV_SIMD_SCALABLE)
const int w = VTraits<v_float32>::vlanes();
v_float32 v_max_val = vx_setall_f32(maxVal);
v_float32 v_softcap = vx_setall_f32(softcap);
@@ -173,7 +173,7 @@ void run_fused_softmax(
if (do_softmax) {
float sum = 0.f;
tk = 0;
#if CV_SIMD
#if (CV_SIMD || CV_SIMD_SCALABLE)
v_float32 v_sum = vx_setzero_f32();
v_float32 v_max_val_shift = vx_setall_f32(maxVal);
for (; tk <= seq_len_kv - w; tk += w) {
@@ -192,7 +192,7 @@ void run_fused_softmax(
float inv_sum = 1.f / sum;
tk = 0;
#if CV_SIMD
#if (CV_SIMD || CV_SIMD_SCALABLE)
v_float32 v_inv_sum = vx_setall_f32(inv_sum);
for (; tk <= seq_len_kv - w; tk += w) {
v_float32 v_val = vx_load(&data[offset + tk]);
@@ -318,6 +318,45 @@ static inline int fast_gemm_thin_lanes() {
#endif
}
#if CV_SIMD_SCALABLE
// Apply the alpha/beta epilogue to one accumulator row.
static inline void fast_gemm_thin_store_row(float* c, v_float32 acc,
v_float32 v_alpha, float beta) {
if (beta == 0.f)
vx_store(c, v_mul(acc, v_alpha));
else if (beta == 1.f)
vx_store(c, v_fma(acc, v_alpha, vx_load(c)));
else
vx_store(c, v_fma(acc, v_alpha, v_mul(vx_load(c), vx_setall_f32(beta))));
}
// Multiply MR (1..4) rows of A by one packed-B strip. Sizeless vector types
// cannot form the acc[] array the CV_SIMD path uses, so the accumulators are
// named variables selected by the compile-time MR (the `if (MR > n)` branches
// fold away) and stay in registers for the whole K loop. Accumulation remains
// in increasing-k order, bit-exact with the previous scratch-buffer code.
template<int MR>
static inline void fast_gemm_thin_block(int K, const float* A, int lda0, int lda1,
const float* b_strip, int NR,
v_float32 v_alpha, float beta,
float* c_strip, int ldc) {
v_float32 c0 = vx_setzero_f32(), c1 = vx_setzero_f32();
v_float32 c2 = vx_setzero_f32(), c3 = vx_setzero_f32();
for (int k = 0; k < K; k++) {
v_float32 bv = vx_load(b_strip + k * NR);
const float* a = A + k * lda1;
c0 = v_fma(bv, vx_setall_f32(a[0]), c0);
if (MR > 1) c1 = v_fma(bv, vx_setall_f32(a[lda0]), c1);
if (MR > 2) c2 = v_fma(bv, vx_setall_f32(a[2 * lda0]), c2);
if (MR > 3) c3 = v_fma(bv, vx_setall_f32(a[3 * lda0]), c3);
}
fast_gemm_thin_store_row(c_strip, c0, v_alpha, beta);
if (MR > 1) fast_gemm_thin_store_row(c_strip + ldc, c1, v_alpha, beta);
if (MR > 2) fast_gemm_thin_store_row(c_strip + 2 * ldc, c2, v_alpha, beta);
if (MR > 3) fast_gemm_thin_store_row(c_strip + 3 * ldc, c3, v_alpha, beta);
}
#endif // CV_SIMD_SCALABLE
static inline void fast_gemm_thin_strip(int M, int K, float alpha,
const float* A, int lda0, int lda1,
const float* b_strip,
@@ -353,37 +392,17 @@ static inline void fast_gemm_thin_strip(int M, int K, float alpha,
}
}
#elif CV_SIMD_SCALABLE
// Scalable vector types (e.g. RVV) are sizeless and cannot form arrays;
// back the per-row accumulators with a scalar scratch buffer.
// Process the strip in register-resident row blocks; the common thin case
// M <= 4 (transformer token generation) is a single pass.
const int NR = VTraits<v_float32>::vlanes();
float acc_buf[FAST_GEMM_THIN_MAX_M * VTraits<v_float32>::max_nlanes];
for (int m = 0; m < M; m++) vx_store(acc_buf + m * NR, vx_setzero_f32());
for (int k = 0; k < K; k++) {
v_float32 bv = vx_load(b_strip + k * NR);
for (int m = 0; m < M; m++) {
v_float32 am = vx_setall_f32(A[m * lda0 + k * lda1]);
v_float32 acc_m = vx_load(acc_buf + m * NR);
vx_store(acc_buf + m * NR, v_fma(bv, am, acc_m));
}
}
const v_float32 v_alpha = vx_setall_f32(alpha);
if (beta == 0.f) {
for (int m = 0; m < M; m++)
vx_store(c_strip + m * ldc, v_mul(vx_load(acc_buf + m * NR), v_alpha));
} else if (beta == 1.f) {
for (int m = 0; m < M; m++) {
v_float32 cv = vx_load(c_strip + m * ldc);
vx_store(c_strip + m * ldc, v_fma(vx_load(acc_buf + m * NR), v_alpha, cv));
}
} else {
const v_float32 v_beta = vx_setall_f32(beta);
for (int m = 0; m < M; m++) {
v_float32 cv = vx_load(c_strip + m * ldc);
cv = v_mul(cv, v_beta);
vx_store(c_strip + m * ldc, v_fma(vx_load(acc_buf + m * NR), v_alpha, cv));
}
int m = 0;
for (; m + 4 <= M; m += 4)
fast_gemm_thin_block<4>(K, A + m * lda0, lda0, lda1, b_strip, NR, v_alpha, beta, c_strip + m * ldc, ldc);
switch (M - m) {
case 1: fast_gemm_thin_block<1>(K, A + m * lda0, lda0, lda1, b_strip, NR, v_alpha, beta, c_strip + m * ldc, ldc); break;
case 2: fast_gemm_thin_block<2>(K, A + m * lda0, lda0, lda1, b_strip, NR, v_alpha, beta, c_strip + m * ldc, ldc); break;
case 3: fast_gemm_thin_block<3>(K, A + m * lda0, lda0, lda1, b_strip, NR, v_alpha, beta, c_strip + m * ldc, ldc); break;
}
#else
const int NR = FAST_GEMM_THIN_SCALAR_NR;
+152
View File
@@ -0,0 +1,152 @@
// 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 "layers_common.hpp"
#include <opencv2/dnn/shape_utils.hpp>
namespace cv {
namespace dnn {
/*
Implementation of CumProd, as defined in ONNX specification:
https://onnx.ai/onnx/operators/onnx__CumProd.html
Opset 26 is covered.
*/
class CumProdLayerImpl CV_FINAL : public CumProdLayer
{
public:
CumProdLayerImpl(const LayerParams& params)
{
axis_raw = params.get<int>("axis", 0);
exclusive_raw = params.get<int>("exclusive", 0);
reverse_raw = params.get<int>("reverse", 0);
setParamsFrom(params);
}
bool supportBackend(int backendId) CV_OVERRIDE { return backendId == DNN_BACKEND_OPENCV; }
bool getMemoryShapes(const std::vector<MatShape>& inputs, const int,
std::vector<MatShape>& outputs, std::vector<MatShape>&) const CV_OVERRIDE
{
outputs.assign(1, inputs[0]);
return false;
}
void getTypes(const std::vector<MatType>& inputs, const int, const int,
std::vector<MatType>& outputs, std::vector<MatType>&) const CV_OVERRIDE
{
CV_CheckType(inputs[0], inputs[0] == CV_32F || inputs[0] == CV_64F ||
inputs[0] == CV_32S || inputs[0] == CV_64S || inputs[0] == CV_16F, "");
outputs.assign(1, inputs[0]);
}
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE
{
if (inputs_arr.depth() == CV_16F)
{
forward_fallback(inputs_arr, outputs_arr, internals_arr);
return;
}
std::vector<Mat> inputs, outputs;
inputs_arr.getMatVector(inputs);
outputs_arr.getMatVector(outputs);
CV_CheckTypeEQ(inputs[0].depth(), outputs[0].depth(), "");
switch (inputs[0].depth())
{
case CV_32F: forwardImpl<float>(inputs, outputs); break;
case CV_32S: forwardImpl<int32_t>(inputs, outputs); break;
case CV_64S: forwardImpl<int64_t>(inputs, outputs); break;
case CV_64F: forwardImpl<double>(inputs, outputs); break;
default: CV_Error(Error::BadDepth, "");
}
}
template <typename T>
void forwardImpl(const std::vector<Mat>& inputs, std::vector<Mat>& outputs)
{
const Mat& src_mat = inputs[0];
const T* src_ptr = src_mat.ptr<T>();
int axis = inputs.size() > 1 ? parseAxis(inputs[1]) : axis_raw;
axis = normalize_axis(axis, src_mat.dims);
Mat& dst_mat = outputs[0];
T* dst_ptr = dst_mat.ptr<T>();
const bool exclusive = exclusive_raw == 1;
const bool reverse = reverse_raw == 1;
// View data as [outer_size, target_size, inner_size] around the scan axis.
const size_t outer_size = src_mat.total(0, axis);
const size_t target_size = src_mat.size[axis];
const size_t inner_size = src_mat.total(axis + 1);
const size_t outer_step_length = target_size * inner_size;
const int target_start = reverse ? (int)target_size - 1 : 0;
const int target_stop = reverse ? -1 : (int)target_size;
const int target_delta = reverse ? -1 : 1;
const int target_step = target_delta * (int)inner_size;
const int exclusive_delta = exclusive ? target_step : 0;
// Each outer slice holds independent scans, so parallelize over it.
parallel_for_(Range(0, (int)outer_size), [&](const Range& range)
{
for (int outer_idx = range.start; outer_idx < range.end; outer_idx++)
{
const size_t target_offset = (size_t)outer_idx * outer_step_length;
// First element: multiplicative identity when exclusive, else the source value.
size_t first_inner_offset = target_offset + (size_t)target_start * inner_size;
if (exclusive)
for (size_t inner_idx = 0; inner_idx < inner_size; inner_idx++)
dst_ptr[first_inner_offset + inner_idx] = (T)1;
else
for (size_t inner_idx = 0; inner_idx < inner_size; inner_idx++)
dst_ptr[first_inner_offset + inner_idx] = src_ptr[first_inner_offset + inner_idx];
for (int target_idx = target_start + target_delta; target_idx != target_stop; target_idx += target_delta)
{
const size_t inner_offset = target_offset + (size_t)target_idx * inner_size;
for (size_t inner_idx = 0; inner_idx < inner_size; inner_idx++)
{
dst_ptr[inner_offset + inner_idx] = dst_ptr[inner_offset - target_step + inner_idx] *
src_ptr[inner_offset - exclusive_delta + inner_idx];
}
}
}
});
}
int64 getFLOPS(const std::vector<MatShape>& inputs, const std::vector<MatShape>&) const CV_OVERRIDE
{
return (int64)total(inputs[0]); // one multiply per element
}
int parseAxis(const Mat& axis_mat)
{
CV_CheckEQ(axis_mat.total(), 1u, "Axis tensor should contain single value");
if (axis_mat.type() == CV_32SC1)
return axis_mat.at<int32_t>(0);
Mat axis_mat_int;
axis_mat.convertTo(axis_mat_int, CV_32SC1);
return axis_mat_int.at<int32_t>(0);
}
int axis_raw;
int exclusive_raw;
int reverse_raw;
};
Ptr<CumProdLayer> CumProdLayer::create(const LayerParams& params)
{
return makePtr<CumProdLayerImpl>(params);
}
}} // namespace cv::dnn
+17 -2
View File
@@ -310,8 +310,8 @@ public:
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(
void *context_,
const std::vector<Ptr<BackendWrapper>>& inputs,
const std::vector<Ptr<BackendWrapper>>& outputs
InputArrayOfArrays /*inputs*/,
InputArrayOfArrays /*outputs*/
) override
{
auto context = reinterpret_cast<csl::CSLContext*>(context_);
@@ -3742,6 +3742,14 @@ class ChannelsPReLUImpl CV_FINAL : public ElementWiseLayer<ChannelsPReLUFunctor>
public:
using ElementWiseLayer<ChannelsPReLUFunctor>::ElementWiseLayer;
void setSlope(const Mat& slope) CV_OVERRIDE
{
slope.reshape(1, (int)slope.total()).convertTo(func.scale, CV_32F);
#ifdef HAVE_OPENCL
func.scale_umat.release();
#endif
}
void forward(InputArrayOfArrays inputs_arr,
OutputArrayOfArrays outputs_arr,
OutputArrayOfArrays internals_arr) CV_OVERRIDE
@@ -3849,6 +3857,13 @@ private:
Ptr<Layer> ChannelsPReLULayer::create(const LayerParams& params)
{
if (params.blobs.empty())
{
// Slope comes as a second input; constArgs() fills the scale in later.
Ptr<ChannelsPReLUImpl> l(new ChannelsPReLUImpl(ChannelsPReLUFunctor()));
l->setParamsFrom(params);
return l;
}
CV_Assert(params.blobs.size() == 1);
Mat scale = params.blobs[0];
float slope = *scale.ptr<float>();
+7 -4
View File
@@ -281,15 +281,18 @@ public:
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(
void *context_,
const std::vector<Ptr<BackendWrapper>>& inputs,
const std::vector<Ptr<BackendWrapper>>& outputs
InputArrayOfArrays inputs_,
InputArrayOfArrays outputs
) override
{
auto context = reinterpret_cast<csl::CSLContext*>(context_);
if (inputs[0]->getHostMatDepth() == CV_Bool)
std::vector<cuda::GpuMatND> inputs;
inputs_.getGpuMatNDVector(inputs);
int depth = CV_MAT_DEPTH(inputs[0].type());
if (depth == CV_Bool)
return make_cuda_node_bool<cuda4dnn::ReshapeOp>(std::move(context->stream));
else
return make_cuda_node_with_type<cuda4dnn::ReshapeOp>(preferableTarget, inputs[0]->getHostMatDepth(), std::move(context->stream));
return make_cuda_node_with_type<cuda4dnn::ReshapeOp>(preferableTarget, depth, std::move(context->stream));
}
#endif
+5 -4
View File
@@ -476,18 +476,19 @@ public:
#ifdef HAVE_CUDA
// Y = A * B + C. B should be guaranteed as two dimensional.
Ptr<BackendNode> initCUDA(void *context_,
const std::vector<Ptr<BackendWrapper>>& inputs,
const std::vector<Ptr<BackendWrapper>>& outputs) CV_OVERRIDE {
InputArrayOfArrays inputs_,
InputArrayOfArrays outputs) CV_OVERRIDE {
CV_CheckFalse(trans_a, "DNN/Gemm/Cuda: does not support transA");
CV_CheckTrue(const_B, "DNN/Gemm/Cuda: input B (weight) is required to be constant");
auto context = reinterpret_cast<csl::CSLContext*>(context_);
auto wrapper_A = inputs[0].dynamicCast<CUDABackendWrapper>();
std::vector<cuda::GpuMatND> inputs;
inputs_.getGpuMatNDVector(inputs);
auto B = blobs[0];
auto C = have_bias && const_C ? blobs[1] : Mat(); // in most cases C is constant
if (!trans_b)
cv::transpose(B, B);
auto flatten_start_axis = normalize_axis(1, wrapper_A->getRank());
auto flatten_start_axis = normalize_axis(1, (int)inputs[0].size.size());
return make_cuda_node<cuda4dnn::InnerProductOp>(preferableTarget, std::move(context->stream), std::move(context->cublas_handle), flatten_start_axis, B, C);
}
#endif // HAVE_CUDA
+51 -4
View File
@@ -7,6 +7,10 @@
#include "../net_impl.hpp"
#include "conv2_common.hpp"
#include "opencv2/core/hal/intrin.hpp"
#include "../op_cuda.hpp"
#ifdef HAVE_CUDA
#include "../cuda4dnn/primitives/pooling.hpp"
#endif
namespace cv
{
@@ -50,9 +54,12 @@ static void maxPool32f(const void* inp_, void* out_, const ConvState& cs)
float* out = (float*)out_ + nc0*planesize;
const float INITVAL = -FLT_MAX;
#if CV_SIMD || CV_SIMD_SCALABLE
#if CV_SIMD
int nlanes = VTraits<v_float32>::vlanes();
v_float32 s_min = vx_setall_f32(INITVAL);
// RVV (CV_SIMD_SCALABLE) disabled for the m1 switch: with the fixed C0=8 the
// block is narrower than the register at VLEN>=512, tripping this assert. Runs
// scalar on RVV; re-enable via v_setvlmax<v_float32>(C0) (#29493, cf #29180).
CV_Assert(C0 == nlanes || C0 == nlanes*2 || C0 % (nlanes*4) == 0);
#endif
@@ -65,13 +72,13 @@ static void maxPool32f(const void* inp_, void* out_, const ConvState& cs)
y0 >= inner_y0 && y0 < inner_y1 ? inner_x0 : W;
int yi_ = y0*SY - padY0;
#if !(CV_SIMD || CV_SIMD_SCALABLE)
#if !(CV_SIMD)
for (int c = 0; c < C0*W; c++)
out[c] = INITVAL;
#endif
for(;;) {
#if CV_SIMD || CV_SIMD_SCALABLE
#if CV_SIMD
if (nlanes == C0) {
for (; x0 < x1; x0++) {
int xi_ = x0*SX - padX0;
@@ -136,7 +143,7 @@ static void maxPool32f(const void* inp_, void* out_, const ConvState& cs)
break;
x1 = inner_x1;
#if CV_SIMD || CV_SIMD_SCALABLE
#if CV_SIMD
if (nlanes == C0) {
for (; x0 < x1; x0++) {
int xi_ = x0*SX - padX0;
@@ -473,9 +480,49 @@ public:
virtual bool supportBackend(int backendId) CV_OVERRIDE
{
#ifdef HAVE_CUDA
if (backendId == DNN_BACKEND_CUDA) {
if (kernel_shape.size() != 2 || outputs.size() != 1)
return false;
for (int d : dilations) if (d != 1) return false;
return auto_pad == AUTO_PAD_NONE || auto_pad == AUTO_PAD_VALID;
}
#endif
return backendId == DNN_BACKEND_OPENCV;
}
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(void* context_,
InputArrayOfArrays inputs_,
InputArrayOfArrays) CV_OVERRIDE
{
auto context = reinterpret_cast<cuda4dnn::csl::CSLContext*>(context_);
std::vector<cuda::GpuMatND> inputs;
inputs_.getGpuMatNDVector(inputs);
MatShape inShape = inputs[0].size;
const int nspatial = (int)kernel_shape.size();
cuda4dnn::PoolingConfiguration config;
config.poolMode = cuda4dnn::PoolingConfiguration::PoolingMode::MAX;
config.window_size.assign(kernel_shape.begin(), kernel_shape.end());
for (int i = 0; i < nspatial; i++)
config.strides.push_back(strides.empty() ? 1 : (size_t)strides[i]);
config.padMode = (auto_pad == AUTO_PAD_VALID)
? cuda4dnn::PoolingConfiguration::PaddingMode::VALID
: cuda4dnn::PoolingConfiguration::PaddingMode::MANUAL;
if (config.padMode == cuda4dnn::PoolingConfiguration::PaddingMode::MANUAL) {
for (int i = 0; i < nspatial; i++) {
config.pads_begin.push_back(pads.empty() ? 0 : (size_t)pads[i]);
config.pads_end.push_back(pads.empty() ? 0 : (size_t)pads[i + nspatial]);
}
}
config.roundMode = ceil_mode ? cuda4dnn::PoolingConfiguration::RoundingMode::CEIL
: cuda4dnn::PoolingConfiguration::RoundingMode::FLOOR;
config.input_shape.assign(inShape.begin(), inShape.end());
return make_cuda_node<cuda4dnn::PoolingOp>(preferableTarget, std::move(context->cudnn_handle), config);
}
#endif
virtual int64_t getFLOPS(const std::vector<MatShape> &inputs,
const std::vector<MatShape> &outputs) const CV_OVERRIDE
{
@@ -417,7 +417,8 @@ public:
}
else if (!baseIsFloat && expIsFloat)
{
out_type = CV_32F;
// ONNX Pow output type follows the base (X): integer base -> integer output.
out_type = inputs[0];
}
else
{
@@ -1319,11 +1320,13 @@ public:
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(
void *context_,
const std::vector<Ptr<BackendWrapper>>& inputs,
const std::vector<Ptr<BackendWrapper>>& outputs
InputArrayOfArrays inputs_,
InputArrayOfArrays outputs
) override
{
auto context = reinterpret_cast<csl::CSLContext*>(context_);
std::vector<cuda::GpuMatND> inputs;
inputs_.getGpuMatNDVector(inputs);
cuda4dnn::EltwiseOpType op_ = cuda4dnn::EltwiseOpType::SUM;
switch (op) {
@@ -1360,7 +1363,7 @@ public:
default: return Ptr<BackendNode>(); // return empty cuda_node if the EltwiseOpType is unsupported type.
};
return make_cuda_node_with_type<cuda4dnn::EltwiseOp>(preferableTarget, inputs[0]->getHostMatDepth(), std::move(context->stream), op_, std::vector<float>());
return make_cuda_node_with_type<cuda4dnn::EltwiseOp>(preferableTarget, CV_MAT_DEPTH(inputs[0].type()), std::move(context->stream), op_, std::vector<float>());
}
#endif
+3 -2
View File
@@ -52,7 +52,8 @@ public:
}
else
{
out.assign(1, MatShape(1, 1));
// Reduced (mean/sum) loss is a rank-0 scalar in ONNX, not a [1] tensor.
out.assign(1, MatShape::scalar());
}
return false;
}
@@ -164,7 +165,7 @@ public:
}
}
const float out = (reduction == LOSS_REDUCTION_SUM) ? static_cast<float>(num) : static_cast<float>((den > 0.0) ? (num / den) : 0.0);
out_loss.at<float>(0) = out;
out_loss.ptr<float>()[0] = out;
}
}
+9 -7
View File
@@ -373,16 +373,18 @@ public:
#ifdef HAVE_CUDA
Ptr<BackendNode> initCUDA(
void *context_,
const std::vector<Ptr<BackendWrapper>>& inputs,
const std::vector<Ptr<BackendWrapper>>& outputs
InputArrayOfArrays inputs_,
InputArrayOfArrays outputs_
) override
{
auto context = reinterpret_cast<csl::CSLContext*>(context_);
if (type == ROI)
return make_cuda_node<cuda4dnn::ROIPoolingOp>(preferableTarget, std::move(context->stream), spatialScale);
auto input_wrapper = inputs[0].dynamicCast<CUDABackendWrapper>();
auto input_shape = input_wrapper->getShape();
std::vector<cuda::GpuMatND> inputs, outputs;
inputs_.getGpuMatNDVector(inputs);
outputs_.getGpuMatNDVector(outputs);
MatShape input_shape = inputs[0].size;
/* storing max indices is a special case and we deal with it separately */
if (computeMaxIdx) {
@@ -412,13 +414,13 @@ public:
config.input_shape.assign(std::begin(input_shape), std::end(input_shape));
int indicesType = outputs[1]->getHostMatDepth();
int indicesType = CV_MAT_DEPTH(outputs[1].type());
CV_CheckType(indicesType, indicesType == CV_32S || indicesType == CV_64S, "Unsupported indices type");
if (indicesType == CV_32S)
return make_cuda_node_with_indices<cuda4dnn::MaxPoolingOp, int32_t>(preferableTarget, inputs[0]->getHostMatDepth(), std::move(context->stream), config);
return make_cuda_node_with_indices<cuda4dnn::MaxPoolingOp, int32_t>(preferableTarget, CV_MAT_DEPTH(inputs[0].type()), std::move(context->stream), config);
else if (indicesType == CV_64S)
return make_cuda_node_with_indices<cuda4dnn::MaxPoolingOp, int64_t>(preferableTarget, inputs[0]->getHostMatDepth(), std::move(context->stream), config);
return make_cuda_node_with_indices<cuda4dnn::MaxPoolingOp, int64_t>(preferableTarget, CV_MAT_DEPTH(inputs[0].type()), std::move(context->stream), config);
CV_Error(Error::BadDepth, "Unsupported indices type");
return Ptr<BackendNode>();
+21 -35
View File
@@ -254,45 +254,31 @@ class LSTM2LayerImpl CV_FINAL : public LSTM2Layer
batchSize = input[0].size[0];
}
// ONNX LSTM inputs: X(0), W(1), R(2), B(3), sequence_lens(4),
// initial_h(5), initial_c(6), P(7). Inputs 3..7 are optional and
// may be present-but-empty (e.g. CNTK exports keep all 8 slots with
// empties for unused ones). Gather by presence/non-emptiness rather
// than by the raw input count so optional/empty inputs are ignored.
auto hasInput = [&](int idx) {
return idx < numInputs && !input[idx].empty();
};
std::vector<Mat> blobs_;
int hidShape [] = {1 + static_cast<int>(bidirectional), batchSize, numHidden};
int biasShape [] = {1 + static_cast<int>(bidirectional), 8 * numHidden};
blobs_.push_back(input[1].clone());
blobs_.push_back(input[2].clone());
switch (numInputs) {
case 3:
// X, W, R are given
// create bias
blobs_.push_back(Mat::zeros(2, biasShape, input[0].type()));
// create h0, c0
blobs_.push_back(Mat::zeros(3, hidShape, input[0].type()));
blobs_.push_back(Mat::zeros(3, hidShape, input[0].type()));
break;
case 4:
// X, W, R, B are given
blobs_.push_back(input[3]);
// create h0, c0
blobs_.push_back(Mat::zeros(3, hidShape, input[0].type()));
blobs_.push_back(Mat::zeros(3, hidShape, input[0].type()));
break;
case 7:
// X, W, R, B, h0, c0 are given
blobs_.push_back(input[3]);
blobs_.push_back(input[5]);
blobs_.push_back(input[6]);
break;
case 8:
// X, W, R, B, seqlen, h0, c0, P are given
blobs_.push_back(input[3]);
blobs_.push_back(input[5]);
blobs_.push_back(input[6]);
blobs_.push_back(input[7]);
break;
default:
CV_Error(Error::StsNotImplemented, "Insufficient inputs for LSTM layer. "
"Required inputs: X, W, R, B, seqLen, h0, c0 [, P for peephole]");
}
CV_Assert(numInputs >= 3); // X, W, R are mandatory
blobs_.push_back(input[1].clone()); // W
blobs_.push_back(input[2].clone()); // R
// B
blobs_.push_back(hasInput(3) ? input[3] : Mat::zeros(2, biasShape, input[0].type()));
// initial_h
blobs_.push_back(hasInput(5) ? input[5] : Mat::zeros(3, hidShape, input[0].type()));
// initial_c
blobs_.push_back(hasInput(6) ? input[6] : Mat::zeros(3, hidShape, input[0].type()));
// P (peephole) - only when the layer was configured to use it and the input is present
if (usePeephole && hasInput(7))
blobs_.push_back(input[7]);
// set outputs to 0
for (auto& out : output)
+76
View File
@@ -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 <opencv2/dnn.hpp>
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<int>("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<Ptr<Graph> >* subgraphs() const CV_OVERRIDE { return &body_; }
bool dynamicOutputShapes() const CV_OVERRIDE { return true; }
bool getMemoryShapes(const std::vector<MatShape>&,
const int requiredOutputs,
std::vector<MatShape>& outputs,
std::vector<MatShape>& 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<int>& scanInputAxes() const CV_OVERRIDE { return input_axes; }
const std::vector<int>& scanOutputAxes() const CV_OVERRIDE { return output_axes; }
const std::vector<int>& scanInputDirections() const CV_OVERRIDE { return input_dirs; }
const std::vector<int>& scanOutputDirections() const CV_OVERRIDE { return output_dirs; }
const std::vector<int>& scanOutputRanks() const CV_OVERRIDE { return output_ranks; }
private:
static void readIntList(const LayerParams& params, const std::string& name, std::vector<int>& 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<int>(i);
}
int num_scan_inputs = 0;
std::vector<int> input_axes, output_axes, input_dirs, output_dirs, output_ranks;
mutable std::vector<Ptr<Graph> > body_;
};
Ptr<ScanLayer> ScanLayer::create(const LayerParams& params)
{
return makePtr<ScanLayerImpl>(params);
}
CV__DNN_INLINE_NS_END
}} // namespace cv::dnn
@@ -49,7 +49,8 @@ public:
for (size_t i = 2; i < x.size(); ++i) y.push_back(x[i]);
out.push_back(y);
} else {
out.push_back(MatShape(1,1));
// Reduced (mean/sum) loss is a rank-0 scalar in ONNX, not a [1] tensor.
out.push_back(MatShape::scalar());
}
if (requiredOutputs >= 2)
@@ -325,7 +326,7 @@ public:
const float out = (reduction == LOSS_REDUCTION_SUM) ? static_cast<float>(num)
: static_cast<float>((den > 0.0) ? (num / den) : 0.0);
out_loss.at<float>(0) = out;
out_loss.ptr<float>()[0] = out;
}
}
+4
View File
@@ -142,6 +142,10 @@ void Net::finalizeNet()
return;
}
#endif
// New graph engine: explicitly select per-op executors for the chosen backend/target now,
// so the first forward() isn't slowed by it.
if (impl->mainGraph)
impl->finalize();
}
void Net::setInputsNames(const std::vector<String>& inputBlobNames)
+72 -32
View File
@@ -5,7 +5,6 @@
#include "precomp.hpp"
#include "net_impl.hpp"
#include "opencv2/core/hal/intrin.hpp"
#ifdef HAVE_ONNXRUNTIME
#include <onnxruntime_cxx_api.h>
@@ -74,14 +73,10 @@ Net::Impl::Impl()
// onnx_opset = 0;
accuracy = CV_32F;
// The block size is a network-wide memory-layout contract, so it must not depend
// on the SIMD width of the host: C0 == 8 is the mainstream, well-tested setting.
// (Deriving it from vlanes() made the layout hardware-dependent; see the #29493 discussion.)
defaultC0 = DEFAULT_C0;
#if CV_SIMD_SCALABLE
// RVV: the universal intrinsics use LMUL=2, so the float vector width is
// (VLEN/32)*2 (16 at VLEN=256). The blocked-layout kernels fill a full
// vector per channel block, so the net-wide block size must match that
// width rather than the fixed default of 8 (#28852).
defaultC0 = std::max((int)DEFAULT_C0, VTraits<v_float32>::vlanes());
#endif
enableFP16 = haveFP16 = false;
// FP16 is not ready yet in the new DNN engine
// Ticket: https://github.com/opencv/opencv/issues/26196
@@ -159,6 +154,9 @@ void Net::Impl::clear()
prepared = false;
finalizeLayers = true;
finalized = false;
fusedSnapshotValid = false;
fusedSnapshot.clear();
}
@@ -279,11 +277,11 @@ Ptr<Layer> Net::Impl::getLayer(int layerId) const
CV_Assert(0 <= layerId && layerId < totalLayers);
int graph_ofs = 0;
for (const Ptr<Graph>& graph : allgraphs) {
const std::vector<Ptr<Layer> >& prog = graph->prog();
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
int nops = (int)prog.size();
CV_Assert(layerId >= graph_ofs);
if (layerId < graph_ofs + nops)
return prog[layerId - graph_ofs];
return prog[layerId - graph_ofs].dynamicCast<Layer>();
graph_ofs += nops;
}
CV_Error_(Error::StsObjectNotFound, ("layer #%d is not found", layerId));
@@ -1201,7 +1199,18 @@ void Net::Impl::forward(std::vector<std::vector<Mat>>& outputBlobs,
FPDenormalsIgnoreHintScope fp_denormals_ignore_scope;
if (mainGraph)
CV_Error(Error::StsNotImplemented, "The new dnn engine doesn't support inference until a specified layer. If you want to run the whole model, please don't set the outputName argument in the forward() call. If you want to run the model until a specified layer, please use the old dnn engine");
{
// In the new engine every requested name maps to a single graph output tensor,
// so each nested list holds exactly one blob.
std::vector<std::string> names(outBlobNames.begin(), outBlobNames.end());
std::vector<Mat> flat;
forwardWithMultipleOutputs(flat, names);
CV_Assert(flat.size() == outBlobNames.size());
outputBlobs.resize(outBlobNames.size());
for (size_t i = 0; i < outBlobNames.size(); i++)
outputBlobs[i].assign(1, flat[i]);
return;
}
std::vector<LayerPin> pins;
for (int i = 0; i < outBlobNames.size(); i++)
@@ -1413,6 +1422,9 @@ void Net::Impl::getLayerShapes(const ShapesVec& netInputShapes,
LayerShapes& shapes)
{
if (mainGraph) {
// Fusion emits block-layout layers; their TransformLayout conversions are
// only inserted by finalize(), so shape inference must run post-finalize.
finalize();
std::vector<MatShape> shapeCache;
std::vector<int> typeCache;
CV_Assert(layerId == 0);
@@ -1636,21 +1648,20 @@ void Net::Impl::setInput(InputArray blob, const String& name, double scalefactor
Mat Net::Impl::getParam(int layer, int numParam) const
{
LayerData& ld = getLayerData(layer);
std::vector<Mat>& layerBlobs = getLayerInstance(ld)->blobs;
std::vector<Mat>& layerBlobs = getLayer(layer)->blobs;
CV_Assert(numParam < (int)layerBlobs.size());
return layerBlobs[numParam];
}
void Net::Impl::setParam(int layer, int numParam, const Mat& blob)
{
LayerData& ld = getLayerData(layer);
// FIXIT we should not modify "execution" instance
std::vector<Mat>& layerBlobs = getLayerInstance(ld)->blobs;
std::vector<Mat>& layerBlobs = getLayer(layer)->blobs;
CV_Assert(numParam < (int)layerBlobs.size());
// we don't make strong checks, use this function carefully
layerBlobs[numParam] = blob;
if (mainGraph)
finalizeLayers = true;
}
void Net::Impl::setParam(const std::string& outputTensorName, int numParam, const Mat& blob)
@@ -1662,12 +1673,19 @@ void Net::Impl::setParam(const std::string& outputTensorName, int numParam, cons
if (excl != std::string::npos)
it = argnames.find(outputTensorName.substr(excl + 1));
}
if (it == argnames.end())
if (it == argnames.end()) {
// Not a tensor name; try it as a layer name.
int lid = getLayerId(outputTensorName);
if (lid >= 0) {
setParam(lid, numParam, blob);
return;
}
CV_Error_(Error::StsObjectNotFound,
("DNN: tensor '%s' not found in the graph", outputTensorName.c_str()));
}
int targetIdx = (int)it->second;
const std::vector<Ptr<Layer>>& prog = mainGraph->prog();
const std::vector<Ptr<LayerInfo>>& prog = mainGraph->prog();
for (const auto& layer : prog) {
bool produces = false;
for (const Arg& out : layer->outputs)
@@ -2373,8 +2391,8 @@ std::vector<String> Net::Impl::getLayerNames() const
if (mainGraph) {
res.reserve(totalLayers);
for (const Ptr<Graph>& graph: allgraphs) {
const std::vector<Ptr<Layer> >& prog = graph->prog();
for (const Ptr<Layer>& layer: prog)
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
for (const Ptr<LayerInfo>& layer: prog)
res.push_back(layer->name);
}
} else {
@@ -2404,7 +2422,7 @@ std::vector<int> Net::Impl::getUnconnectedOutLayers() const
int graph_ofs = 0;
for (const auto& graph : allgraphs) {
const std::vector<Ptr<Layer>>& prog = graph->prog();
const std::vector<Ptr<LayerInfo>>& prog = graph->prog();
for (int i = 0; i < (int)prog.size(); i++) {
for (const auto& layerOut : prog[i]->outputs) {
if (outArgIdxs.count(layerOut.idx)) {
@@ -2466,6 +2484,21 @@ std::vector<String> Net::Impl::getUnconnectedOutLayersNames() /*const*/
}
// The new graph engine has no FP16 execution path yet: it runs FP32 on CPU regardless
// of the requested (e.g. OpenCL FP16) target. Map FP16 input types to FP32 so that
// shape/FLOPS inference through Layer::getTypes() does not reject them.
static std::vector<MatType> filterFP16InputTypes(const std::vector<MatType>& types)
{
std::vector<MatType> result = types;
for (MatType& t : result)
{
if (t == CV_16F)
t = CV_32F;
}
return result;
}
int64 Net::Impl::getFLOPSGraph(const Ptr<Graph>& graph,
const std::vector<MatShape>& shapeCache,
const std::vector<MatType>& typeCache) const
@@ -2474,9 +2507,9 @@ int64 Net::Impl::getFLOPSGraph(const Ptr<Graph>& graph,
return 0;
int64 flops = 0;
const std::vector<Ptr<Layer>>& prog = graph->prog();
const std::vector<Ptr<LayerInfo>>& prog = graph->prog();
for (const Ptr<Layer>& layer : prog) {
for (const Ptr<LayerInfo>& layer : prog) {
if (!layer)
continue;
@@ -2524,10 +2557,15 @@ int64 Net::Impl::getFLOPS(const std::vector<MatShape>& netInputShapes,
const std::vector<MatType>& netInputTypes) /*const*/
{
if (mainGraph) {
finalize();
// The new graph engine executes in FP32 on CPU regardless of the requested
// target, so FP16 input types (e.g. coming from an OpenCL FP16 target) would be
// rejected by Layer::getTypes(). Normalize them to FP32 for shape/FLOPS inference.
std::vector<MatType> inputTypes = filterFP16InputTypes(netInputTypes);
LayerShapes shapes;
std::vector<MatShape> shapeCache;
std::vector<MatType> typeCache;
tryInferShapes(netInputShapes, netInputTypes, shapes, shapeCache, typeCache);
tryInferShapes(netInputShapes, inputTypes, shapes, shapeCache, typeCache);
return getFLOPSGraph(mainGraph, shapeCache, typeCache);
}
@@ -2553,17 +2591,19 @@ int64 Net::Impl::getFLOPS(
const std::vector<MatType>& netInputTypes) /*const*/
{
if (mainGraph) {
finalize();
std::vector<MatType> inputTypes = filterFP16InputTypes(netInputTypes);
LayerShapes shapes;
std::vector<MatShape> shapeCache;
std::vector<MatType> typeCache;
tryInferShapes(netInputShapes, netInputTypes, shapes, shapeCache, typeCache);
tryInferShapes(netInputShapes, inputTypes, shapes, shapeCache, typeCache);
CV_Assert(0 <= layerId && layerId < (int)totalLayers);
int localIdx = layerId;
for (const Ptr<Graph>& graph : allgraphs) {
int progSize = (int)graph->prog().size();
if (localIdx < progSize) {
const Ptr<Layer>& layer = graph->prog()[localIdx];
const Ptr<LayerInfo>& layer = graph->prog()[localIdx];
if (!layer)
return 0;
@@ -2662,8 +2702,8 @@ void Net::Impl::collectLayerInfo(std::vector<String>& names, std::vector<String>
names.reserve(totalLayers);
types.reserve(totalLayers);
for (const Ptr<Graph>& graph : allgraphs) {
const std::vector<Ptr<Layer>>& prog = graph->prog();
for (const Ptr<Layer>& layer : prog) {
const std::vector<Ptr<LayerInfo>>& prog = graph->prog();
for (const Ptr<LayerInfo>& layer : prog) {
names.push_back(layer ? layer->name : "null");
types.push_back(layer ? layer->type : "null");
}
@@ -2977,8 +3017,8 @@ void Net::Impl::getLayerTypes(std::vector<String>& layersTypes) const
if (mainGraph) {
std::set<std::string> layersTypesSet;
for (const Ptr<Graph>& g: allgraphs) {
const std::vector<Ptr<Layer> >& prog = g->prog();
for (const Ptr<Layer>& layer: prog) {
const std::vector<Ptr<LayerInfo> >& prog = g->prog();
for (const Ptr<LayerInfo>& layer: prog) {
if (!layer)
continue;
layersTypesSet.insert(layer->type);
@@ -3010,8 +3050,8 @@ int Net::Impl::getLayersCount(const String& layerType) const
if (mainGraph) {
int count = 0;
for (const Ptr<Graph>& g: allgraphs) {
const std::vector<Ptr<Layer> >& prog = g->prog();
for (const Ptr<Layer>& layer: prog) {
const std::vector<Ptr<LayerInfo> >& prog = g->prog();
for (const Ptr<LayerInfo>& layer: prog) {
if (!layer)
continue;
if (layer->type == layerType)
+38 -3
View File
@@ -143,6 +143,19 @@ struct Net::Impl : public detail::NetImplBase
bool enableFP16, haveFP16;
bool prepared; // need to rerun graph transformations/optimizations
bool finalizeLayers; // need to initialize each layer
bool finalized = false; // executors have been selected for the current backend/target
// Post-fusion (pre block-layout) snapshot so finalize() can re-run from a clean
// state on a backend/target change; useBlockLayout() is destructive and must
// run after backend assignment (see deviceOp handling in graph_block_layout.cpp).
struct FusedGraphSnapshot {
Ptr<Graph> graph;
std::vector<Ptr<LayerInfo> > prog;
std::vector<std::vector<Arg> > inputs;
std::vector<std::vector<Arg> > outputs;
};
bool fusedSnapshotValid = false;
std::vector<FusedGraphSnapshot> fusedSnapshot;
TracingMode tracingMode;
ProfilingMode profilingMode;
std::vector<int64_t> dimvalues;
@@ -280,6 +293,22 @@ struct Net::Impl : public detail::NetImplBase
std::unique_ptr<CudaInfo_t> cudaInfo;
void initCUDABackend(const std::vector<LayerPin>& blobsToKeep_);
// New graph engine: per-Arg device-resident tensors owned directly by the net (no backend
// wrappers). Sized lazily via GpuMatND::fit() and reused across forwards. Dirty flags track
// which copy (host cv::Mat vs device GpuMatND) is authoritative so transfers happen only at
// CPU<->CUDA boundaries; intermediates stay device-resident across consecutive CUDA ops.
std::vector<cuda::GpuMatND> cudaArgBuffers;
std::vector<uchar> cudaArgHostDirty; // 1: host copy is authoritative -> needs H2D before device read
std::vector<uchar> cudaArgDeviceDirty; // 1: device copy is authoritative -> needs D2H before host read
// Device element type for a host tensor (half for float tensors under the FP16 target).
int cudaDeviceType(const Mat& hostMat) const;
// Returns the device buffer for @p arg, fit() to the host Mat's shape and device type.
cuda::GpuMatND& getCudaArgBuffer(Arg arg, const Mat& hostMat);
void cudaSetHostDirty(Arg arg); // mark host authoritative (e.g. after a CPU op wrote it)
void cudaUploadArg(Arg arg, const Mat& hostMat); // H2D if host dirty
void cudaDownloadArg(Arg arg, Mat& hostMat); // D2H if device dirty
#endif
#ifdef HAVE_ONNXRUNTIME
@@ -420,12 +449,18 @@ struct Net::Impl : public detail::NetImplBase
int findDim(const std::string& name, bool insert=false);
void prepareForInference();
void finalize();
// Selects executors for a single graph (recursing into subgraphs).
void finalizeGraph(const Ptr<Graph>& graph, bool useCUDA);
// Save/restore the fused graph so finalize() is re-entrant across backend changes.
void saveFusedSnapshot();
void restoreFusedSnapshot();
// pre-allocates memory for output tensors.
// if useBufferPool==true, the method uses 'buffers'
// for outputs (according to bufidxs)
// instead of allocating fresh outputs
void allocateLayerOutputs(const Ptr<Layer>& layer,
void allocateLayerOutputs(const Ptr<LayerInfo>& layer,
const std::vector<int>& inpTypes,
const std::vector<MatShape>& inpShapes,
std::vector<int>& outTypes,
@@ -523,9 +558,9 @@ struct Net::Impl : public detail::NetImplBase
}; // Net::Impl
inline Net::Impl* getNetImpl(const Layer* layer)
inline Net::Impl* getNetImpl(const LayerInfo* op)
{
return reinterpret_cast<Net::Impl*>(layer->netimpl);
return reinterpret_cast<Net::Impl*>(op->netimpl);
}
Net readNetFromONNX2(const String&);
+536 -74
View File
@@ -5,10 +5,13 @@
#include "precomp.hpp"
#include "net_impl.hpp"
#include "opencv2/core/hal/intrin.hpp"
#include <limits>
#ifdef HAVE_CUDA
#include <opencv2/core/cuda_stream_accessor.hpp>
#endif
#ifdef HAVE_ONNXRUNTIME
#include <onnxruntime_cxx_api.h>
#endif
@@ -317,30 +320,30 @@ public:
return g;
}*/
virtual const std::vector<Arg>& append(Ptr<Layer>& layer,
virtual const std::vector<Arg>& append(Ptr<LayerInfo>& op,
const std::vector<std::string>& outnames) override
{
CV_Assert(layer);
CV_Assert(op);
int i, noutputs = (int)outnames.size();
//CV_Assert(layer->minNumOutputs() <= noutputs && noutputs <= layer->maxNumOutputs());
//CV_Assert(op->minNumOutputs() <= noutputs && noutputs <= op->maxNumOutputs());
layer->outputs.resize(noutputs);
op->outputs.resize(noutputs);
for (i = 0; i < noutputs; i++) {
Arg outarg = netimpl_->getArg(outnames[i]);
ArgKind kind = netimpl_->argKind(outarg);
CV_Assert(kind == DNN_ARG_TEMP || kind == DNN_ARG_OUTPUT);
layer->outputs[i] = outarg;
op->outputs[i] = outarg;
}
prog_.push_back(layer);
return layer->outputs;
prog_.push_back(op);
return op->outputs;
}
virtual Arg append(Ptr<Layer>& layer,
virtual Arg append(Ptr<LayerInfo>& op,
const std::string& outname) override
{
std::vector<std::string> outnames = {outname};
const std::vector<Arg>& outputs = append(layer, outnames);
const std::vector<Arg>& outputs = append(op, outnames);
CV_Assert(outputs.size() == 1);
return outputs[0];
}
@@ -379,8 +382,8 @@ public:
for (size_t i = 0; i < nlayers; i++) {
prindent(strm, argindent);
strm << "// op #" << i << "\n";
const Ptr<Layer>& layer = prog_[i];
layer->dump(strm, argindent, i+1 < nlayers);
const Ptr<LayerInfo>& op = prog_[i];
op->dump(strm, argindent, i+1 < nlayers);
}
prindent(strm, subindent);
strm << "]\n";
@@ -401,15 +404,30 @@ public:
netimpl_->checkArgs(outputs);
outputs_ = outputs;
}
virtual const std::vector<Ptr<Layer> >& prog() const override { return prog_; }
virtual void setProg(const std::vector<Ptr<Layer> >& newprog) override { prog_ = newprog; }
virtual const std::vector<Ptr<LayerInfo> >& prog() const override { return prog_; }
virtual int opBackend(int opidx) const override
{
return (opidx >= 0 && opidx < (int)execBackend_.size()) ? execBackend_[opidx]
: DNN_BACKEND_OPENCV;
}
virtual void setProg(const std::vector<Ptr<LayerInfo> >& newprog) override
{
prog_ = newprog;
exec_.clear();
execBackend_.clear();
inH2D_.clear();
outD2H_.clear();
}
protected:
Net::Impl* netimpl_;
std::string name_;
std::vector<Arg> inputs_;
std::vector<Arg> outputs_;
std::vector<Ptr<Layer> > prog_;
std::vector<Ptr<LayerInfo> > prog_;
std::vector<Ptr<Layer> > exec_;
std::vector<int> execBackend_;
std::vector<std::vector<uchar> > inH2D_;
std::vector<std::vector<uchar> > outD2H_;
};
Ptr<Graph> Graph::create(void* netimpl, const std::string& name,
@@ -546,16 +564,6 @@ void Net::Impl::prepareForInference()
#endif
if (!prepared) {
#if CV_SIMD_SCALABLE
// RVV (#28852): keep quantized graphs at C0=8. The int8 kernels are hardwired to
// C0=8 (VNNI/NEON weight packing + per-channel quantization) and run scalar on RVV,
// so a wider block gives no benefit and breaks them. Only fp32 graphs use the wider
// vlanes()-based defaultC0. Signed-int8 (CV_8S) args are the quantization signature
// (uint8 image inputs are CV_8U, so they don't trigger this).
for (const ArgData& a : args) {
if (a.type == CV_8S) { defaultC0 = 8; break; }
}
#endif
fuseQDQ();
constFold();
fuseBN();
@@ -567,16 +575,174 @@ void Net::Impl::prepareForInference()
fuseTransposeMatMul();
fuseScaleSoftmax();
fuseBasic();
useBlockLayout();
assignBuffers();
totalLayers = updateGraphOfs(mainGraph, 0, true);
prepared = true;
finalizeLayers = true;
}
}
void Net::Impl::finalizeGraph(const Ptr<Graph>& graph, bool useCUDA)
{
GraphImpl* g = static_cast<GraphImpl*>(graph.get());
const std::vector<Ptr<LayerInfo> >& prog = g->prog_;
size_t i, nops = prog.size();
g->exec_.assign(nops, Ptr<Layer>());
g->execBackend_.assign(nops, DNN_BACKEND_OPENCV);
#ifdef HAVE_CUDA
// Whole-graph CUDA gate: run on CUDA only if *every* op has a CUDA executor; if any op is
// unsupported (or the graph has control-flow subgraphs) run the entire graph on CPU. This
// avoids partial CPU<->CUDA execution and its host/device coherence hazards for now.
std::vector<Ptr<Layer> > cudaExecs;
bool graphOnCuda = false;
if (useCUDA && cudaInfo) {
graphOnCuda = true;
cudaExecs.assign(nops, Ptr<Layer>());
for (i = 0; i < nops; i++) {
const Ptr<LayerInfo>& op = prog[i];
if (!op)
continue;
if (op->subgraphs()) { graphOnCuda = false; break; } // control-flow bodies stay on CPU
Ptr<Layer> e = LayerFactory::createExec(op->type, DNN_BACKEND_CUDA, op, &cudaInfo->context);
if (!e) { graphOnCuda = false; break; }
cudaExecs[i] = e;
}
if (!graphOnCuda) {
cudaExecs.clear();
CV_LOG_INFO(NULL, cv::format("DNN/NewEngine: graph '%s' has layer(s) without CUDA support; "
"running the whole graph on CPU", graph->name().c_str()));
}
}
#endif
for (i = 0; i < nops; i++) {
const Ptr<LayerInfo>& op = prog[i];
if (!op)
continue;
// recurse into subgraphs (If/Loop bodies) first
const std::vector<Ptr<Graph> >* subs = op->subgraphs();
if (subs) {
for (const Ptr<Graph>& sub : *subs)
finalizeGraph(sub, useCUDA);
}
Ptr<Layer> exec;
int backend = DNN_BACKEND_OPENCV;
#ifdef HAVE_CUDA
if (graphOnCuda && cudaExecs[i]) {
exec = cudaExecs[i];
exec->preferableTarget = preferableTarget;
backend = DNN_BACKEND_CUDA;
}
#endif
if (!exec) {
exec = LayerFactory::createExec(op->type, DNN_BACKEND_OPENCV, op, nullptr);
if (!exec)
exec = op.dynamicCast<Layer>();
backend = DNN_BACKEND_OPENCV;
}
CV_Assert(exec);
g->exec_[i] = exec;
g->execBackend_[i] = backend;
CV_LOG_INFO(NULL, cv::format("DNN/NewEngine: finalize op #%zu '%s' (%s) -> %s",
i, op->name.c_str(), op->type.c_str(),
backend == DNN_BACKEND_CUDA ? "CUDA" : "CPU"));
}
}
void Net::Impl::saveFusedSnapshot()
{
fusedSnapshot.clear();
for (const Ptr<Graph>& g : allgraphs) {
FusedGraphSnapshot snap;
snap.graph = g;
const std::vector<Ptr<LayerInfo> >& prog = g->prog();
snap.prog = prog;
snap.inputs.reserve(prog.size());
snap.outputs.reserve(prog.size());
for (const Ptr<LayerInfo>& op : prog) {
snap.inputs.push_back(op ? op->inputs : std::vector<Arg>());
snap.outputs.push_back(op ? op->outputs : std::vector<Arg>());
}
fusedSnapshot.push_back(std::move(snap));
}
}
void Net::Impl::restoreFusedSnapshot()
{
// Roll the graph back to its post-fusion state: undo the layer-input rewiring
// and remove the TransformLayout ops inserted by a previous useBlockLayout().
for (const FusedGraphSnapshot& snap : fusedSnapshot) {
for (size_t i = 0; i < snap.prog.size(); i++) {
const Ptr<LayerInfo>& op = snap.prog[i];
if (!op)
continue;
op->inputs = snap.inputs[i];
op->outputs = snap.outputs[i];
}
snap.graph->setProg(snap.prog);
}
totalLayers = updateGraphOfs(mainGraph, 0, true);
}
void Net::Impl::finalize()
{
#ifdef HAVE_ONNXRUNTIME
if (ort_session)
return; // ONNX Runtime manages its own execution session
#endif
if (!mainGraph)
return;
if (!prepared)
prepareForInference();
if (finalized)
return;
// Snapshot the fused graph once so finalize() can re-run cleanly on a
// backend/target change (block layout + buffer assignment are destructive).
if (!fusedSnapshotValid) {
saveFusedSnapshot();
fusedSnapshotValid = true;
} else {
restoreFusedSnapshot();
}
bool useCUDA = false;
#ifdef HAVE_CUDA
cudaArgBuffers.clear();
cudaArgHostDirty.clear();
cudaArgDeviceDirty.clear();
if (preferableBackend == DNN_BACKEND_CUDA && haveCUDA()) {
useCUDA = true;
if (!cudaInfo) {
cuda4dnn::csl::CSLContext context;
context.stream = cuda4dnn::csl::Stream(true);
context.cublas_handle = cuda4dnn::csl::cublas::Handle(context.stream);
context.cudnn_handle = cuda4dnn::csl::cudnn::Handle(context.stream);
auto d2h_stream = cuda4dnn::csl::Stream(true);
cudaInfo = std::unique_ptr<CudaInfo_t>(new CudaInfo_t(std::move(context), std::move(d2h_stream)));
}
}
#endif
CV_LOG_INFO(NULL, cv::format("DNN/NewEngine: finalize() backend=%d target=%d useCUDA=%d over %zu graph(s)",
preferableBackend, preferableTarget, (int)useCUDA, allgraphs.size()));
for (const Ptr<Graph>& g : allgraphs)
finalizeGraph(g, useCUDA);
useBlockLayout();
assignBuffers();
totalLayers = updateGraphOfs(mainGraph, 0, true);
for (const Ptr<Graph>& g : allgraphs)
finalizeGraph(g, useCUDA);
finalized = true;
}
void Net::Impl::allocateLayerOutputs(
const Ptr<Layer>& layer,
const Ptr<LayerInfo>& layer,
const std::vector<int>& inpTypes,
const std::vector<MatShape>& inpShapes,
std::vector<int>& outTypes,
@@ -680,6 +846,7 @@ void Net::Impl::forwardMainGraph(InputArrayOfArrays inputs, OutputArrayOfArrays
if (!mainGraph) {
CV_Error(Error::StsNullPtr, "the model was not loaded");
}
finalize(); // select per-op executors for the chosen backend/target (idempotent)
// ************ uncomment one of the lines below for debugging **********
//tracingMode = DNN_TRACE_OP;
//tracingMode = DNN_TRACE_ALL;
@@ -700,6 +867,24 @@ void Net::Impl::forwardMainGraph(InputArrayOfArrays inputs, OutputArrayOfArrays
kvCacheManager.applyRoutes();
}
// Assign a single result to an output array, including a (pre-allocated) vector
// of Mat/UMat, which _OutputArray::assign(Mat) does not handle.
static void assignSingleOutput(OutputArrayOfArrays outputBlobs, const Mat& result)
{
_InputArray::KindFlag k = outputBlobs.kind();
if (k == _InputArray::STD_VECTOR_MAT) {
std::vector<Mat>& v = outputBlobs.getMatVecRef();
v.resize(1);
result.copyTo(v[0]);
} else if (k == _InputArray::STD_VECTOR_UMAT) {
std::vector<UMat>& v = outputBlobs.getUMatVecRef();
v.resize(1);
result.copyTo(v[0]);
} else {
outputBlobs.assign(result);
}
}
void Net::Impl::forwardWithSingleOutput(const std::string& outname, OutputArrayOfArrays outputBlobs)
{
#ifdef HAVE_ONNXRUNTIME
@@ -726,7 +911,7 @@ void Net::Impl::forwardWithSingleOutput(const std::string& outname, OutputArrayO
std::vector<int> outIdxs(1, outIdx);
std::vector<Mat> outs = runOrtSession(netInputLayer->blobs, outIdxs);
CV_Assert(outs.size() == 1);
outputBlobs.assign(outs[0]);
assignSingleOutput(outputBlobs, outs[0]);
return;
}
#endif
@@ -754,7 +939,7 @@ void Net::Impl::forwardWithSingleOutput(const std::string& outname, OutputArrayO
const std::vector<Arg>& gr_outputs = mainGraph->outputs();
for (size_t i = 0; i < gr_outputs.size(); i++) {
if (gr_outputs[i].idx == targetArg.idx) {
outputBlobs.assign(outs[i]);
assignSingleOutput(outputBlobs, outs[i]);
return;
}
}
@@ -771,9 +956,9 @@ void Net::Impl::forwardWithSingleOutput(const std::string& outname, OutputArrayO
if (result.shape().layout == DATA_LAYOUT_BLOCK) {
Mat converted;
transformLayout(result, converted, originalLayout, originalLayout, defaultC0);
outputBlobs.assign(converted);
assignSingleOutput(outputBlobs, converted);
} else {
outputBlobs.assign(result.clone());
assignSingleOutput(outputBlobs, result.clone());
}
return;
}
@@ -782,7 +967,7 @@ void Net::Impl::forwardWithSingleOutput(const std::string& outname, OutputArrayO
std::vector<Mat> inps, outs;
forwardMainGraph(inps, outs);
CV_Assert(!outs.empty());
outputBlobs.assign(outs[0]);
assignSingleOutput(outputBlobs, outs[0]);
}
void Net::Impl::forwardWithMultipleOutputs(OutputArrayOfArrays outblobs, const std::vector<std::string>& outnames)
@@ -859,45 +1044,44 @@ void Net::Impl::forwardWithMultipleOutputs(OutputArrayOfArrays outblobs, const s
const std::vector<Arg>& outargs = mainGraph->outputs();
std::vector<int> outidxs;
int i, j, noutputs = (int)outargs.size();
if (!outnames.empty()) {
CV_CheckEQ((int)outnames.size(), noutputs, "the number of requested and actual outputs must be the same");
if (noutputs == 1 && outnames[0].empty())
;
else {
for (i = 0; i < noutputs; i++) {
const std::string& outname = outnames[i];
for (j = 0; j < noutputs; j++) {
const ArgData& adata = args.at(outargs[j].idx);
if (adata.name == outname) {
outidxs.push_back((int)j);
break;
}
}
if (j == noutputs) {
CV_Error_(Error::StsObjectNotFound, ("the required output '%s' is not found", outname.c_str()));
if (outnames.empty() || (noutputs == 1 && outnames.size() == 1 && outnames[0].empty())) {
for (i = 0; i < noutputs; i++)
outidxs.push_back(i);
} else {
for (i = 0; i < (int)outnames.size(); i++) {
const std::string& outname = outnames[i];
for (j = 0; j < noutputs; j++) {
const ArgData& adata = args.at(outargs[j].idx);
if (adata.name == outname) {
outidxs.push_back((int)j);
break;
}
}
if (j == noutputs) {
CV_Error_(Error::StsObjectNotFound, ("the required output '%s' is not found", outname.c_str()));
}
}
}
std::vector<Mat> inps={}, outs;
forwardMainGraph(inps, outs);
CV_Assert(outs.size() == noutputs);
int nout = (int)outidxs.size();
std::vector<Mat>* outMats = nullptr;
std::vector<UMat>* outUMats = nullptr;
_InputArray::KindFlag outKind = outblobs.kind();
if (outKind == _InputArray::STD_VECTOR_MAT) {
outMats = &outblobs.getMatVecRef();
outMats->resize(noutputs);
outMats->resize(nout);
} else if (outKind == _InputArray::STD_VECTOR_UMAT) {
outUMats = &outblobs.getUMatVecRef();
outUMats->resize(noutputs);
outUMats->resize(nout);
} else if (outKind == _InputArray::MAT || outKind == _InputArray::UMAT) {
CV_Assert(noutputs == 1);
CV_Assert(nout == 1);
} else {
CV_Error(Error::StsBadArg, "outputs must be Mat, UMat, a vector of Mat's or a vector of UMat's");
}
for (i = 0; i < noutputs; i++) {
int j = outidxs.empty() ? i : outidxs[i];
for (i = 0; i < nout; i++) {
int j = outidxs[i];
Mat src = outs[j];
if (outMats) {
src.copyTo(outMats->at(i));
@@ -1127,6 +1311,168 @@ void Net::Impl::setGraphInput(Ptr<Graph>& graph, size_t idx, const Mat& m)
}
}
#ifdef HAVE_CUDA
// cv::cuda::Stream view over the (non-owning) cuda4dnn inference stream, so GpuMatND transfers
// are ordered against the op compute that runs on the same cudaStream_t.
static inline cuda::Stream wrapCudaStream(cuda4dnn::csl::Stream& s)
{
return cuda::StreamAccessor::wrapStream(s.get());
}
// GpuMatND upload/download require at least one dimension (rank-0 shapes trip GpuMatND::setFields).
// View a rank-0 scalar as a 1-element 1D header over the same data; higher-rank Mats pass through.
static inline Mat asAtLeast1D(const Mat& m)
{
if (m.shape().dims != 0)
return m;
int one = 1;
return Mat(1, &one, m.type(), const_cast<uchar*>(m.data));
}
// Device element type for a host tensor: float tensors are stored as half under the FP16 target,
// everything else mirrors the host type. fit() reuses the existing allocation when large enough,
// so buffers persist across forwards.
int Net::Impl::cudaDeviceType(const Mat& hostMat) const
{
if (preferableTarget == DNN_TARGET_CUDA_FP16 && CV_MAT_DEPTH(hostMat.type()) == CV_32F)
return CV_MAKETYPE(CV_16F, CV_MAT_CN(hostMat.type()));
return hostMat.type();
}
cuda::GpuMatND& Net::Impl::getCudaArgBuffer(Arg arg, const Mat& hostMat)
{
int idx = arg.idx;
if ((int)cudaArgBuffers.size() != (int)args.size()) {
cudaArgBuffers.assign(args.size(), cuda::GpuMatND());
cudaArgHostDirty.assign(args.size(), 1);
cudaArgDeviceDirty.assign(args.size(), 0);
}
// GpuMatND (and cuda4dnn tensors) require at least one dimension; represent a rank-0 scalar
// tensor as a 1-element 1D tensor so element-wise ops still see the single value.
MatShape shape = hostMat.shape();
if (shape.dims == 0)
shape = MatShape({1});
cudaArgBuffers[idx].fit(shape, cudaDeviceType(hostMat));
return cudaArgBuffers[idx];
}
void Net::Impl::cudaSetHostDirty(Arg arg)
{
int idx = arg.idx;
if (idx >= 0 && idx < (int)cudaArgHostDirty.size()) {
cudaArgHostDirty[idx] = 1;
cudaArgDeviceDirty[idx] = 0;
}
}
void Net::Impl::cudaUploadArg(Arg arg, const Mat& hostMat)
{
int idx = arg.idx;
Mat src = asAtLeast1D(hostMat);
cuda::GpuMatND& g = getCudaArgBuffer(arg, src);
if (cudaArgHostDirty[idx]) {
cuda::Stream s = wrapCudaStream(cudaInfo->context.stream);
if (g.type() == src.type()) {
g.upload(src, s);
} else {
// FP32 -> FP16 (device stores half): convert on host, then copy up.
Mat tmp;
src.convertTo(tmp, CV_MAT_DEPTH(g.type()));
g.upload(tmp, s);
}
cudaArgHostDirty[idx] = 0;
cudaArgDeviceDirty[idx] = 0;
}
}
void Net::Impl::cudaDownloadArg(Arg arg, Mat& hostMat)
{
int idx = arg.idx;
if (idx < 0 || idx >= (int)cudaArgDeviceDirty.size())
return;
if (cudaArgDeviceDirty[idx]) {
cuda::GpuMatND& g = cudaArgBuffers[idx];
cuda::Stream s = wrapCudaStream(cudaInfo->context.stream);
Mat dst = asAtLeast1D(hostMat); // fill the scalar's storage through a 1D header
if (g.type() == dst.type()) {
g.download(dst, s);
cudaInfo->context.stream.synchronize(); // host read follows immediately
} else {
// device stores half: copy down, then convert up to the host FP32 tensor.
Mat tmp;
g.download(tmp, s);
cudaInfo->context.stream.synchronize();
tmp.convertTo(dst, dst.type());
}
cudaArgDeviceDirty[idx] = 0;
cudaArgHostDirty[idx] = 0;
}
}
static void forwardOpCUDA(Net::Impl* netimpl, GraphImpl* gimpl, size_t opidx,
const std::vector<Arg>& inputs, const std::vector<Arg>& outputs,
std::vector<Mat>& inpMats, std::vector<Mat>& outMats)
{
Ptr<Layer> exec = gimpl->exec_[opidx];
CV_Assert(exec && netimpl->cudaInfo);
std::vector<cuda::GpuMatND> inpG(inputs.size()), outG(outputs.size());
for (size_t i = 0; i < inputs.size(); i++) {
netimpl->cudaUploadArg(inputs[i], inpMats[i]); // H2D only if host-authoritative
inpG[i] = netimpl->cudaArgBuffers[inputs[i].idx];
}
for (size_t i = 0; i < outputs.size(); i++) {
outG[i] = netimpl->getCudaArgBuffer(outputs[i], outMats[i]);
int oidx = outputs[i].idx; // op writes the device buffer
netimpl->cudaArgDeviceDirty[oidx] = 1;
netimpl->cudaArgHostDirty[oidx] = 0;
}
exec->forwardCUDA(inpG, outG, &netimpl->cudaInfo->workspace);
}
#endif
// 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<Range> r(m.dims, Range::all());
r[axis] = Range(idx, idx + 1);
Mat sub = m(r).clone();
std::vector<int> 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<Mat>& 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<int> 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<Range> 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>& graph, InputArrayOfArrays inputs_,
OutputArrayOfArrays outputs_, bool isMainGraph)
{
@@ -1135,7 +1481,8 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
CV_Error_(Error::StsObjectNotFound, ("graph '%s' does not belong to the model", graph->name().c_str()));
}
std::ostream& strm_ = dump_strm ? *dump_strm : std::cout;
const std::vector<Ptr<Layer> >& prog = graph->prog();
GraphImpl* gimpl = static_cast<GraphImpl*>(graph.get());
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
size_t i, nops = prog.size();
const std::vector<Arg>& gr_inputs = graph->inputs();
const std::vector<Arg>& gr_outputs = graph->outputs();
@@ -1159,13 +1506,31 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
setGraphInput(graph, i, m);
}
}
#ifdef HAVE_CUDA
// Graph inputs are host-authoritative and may be updated between forward() calls
// (setInput writes them outside this function, so the loop above is skipped when
// forward() is called with no explicit inputs). Mark their device copies stale each
// forward so the current input is re-uploaded; otherwise a second forward with a
// changed input would read the previous forward's stale device data.
if (cudaInfo) {
for (i = 0; i < n_gr_inputs; i++)
cudaSetHostDirty(gr_inputs[i]);
}
#endif
for (size_t opidx = 0; opidx < nops; opidx++) {
const Ptr<Layer>& layer = prog.at(opidx);
if (!layer) // in theory we shouldn't have any 'nops' at this stage, but just in case we skip them.
const Ptr<LayerInfo>& op = prog.at(opidx);
if (!op) // in theory we shouldn't have any 'nops' at this stage, but just in case we skip them.
continue;
const std::vector<Arg>& inputs = layer->inputs;
const std::vector<Arg>& outputs = layer->outputs;
Ptr<Layer> layer = (opidx < gimpl->exec_.size()) ? gimpl->exec_[opidx] : Ptr<Layer>();
if (!layer)
layer = op.dynamicCast<Layer>();
CV_Assert(layer);
int opBackend = (opidx < gimpl->execBackend_.size()) ? gimpl->execBackend_[opidx]
: DNN_BACKEND_OPENCV;
CV_UNUSED(opBackend); // only consumed by the HAVE_CUDA dispatch below
const std::vector<Arg>& inputs = op->inputs;
const std::vector<Arg>& outputs = op->outputs;
size_t ninputs = inputs.size(), noutputs = outputs.size();
inpMats.resize(ninputs);
@@ -1176,6 +1541,13 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
for (i = 0; i < ninputs; i++) {
Arg inp = inputs[i];
#ifdef HAVE_CUDA
// CPU op: bring any device-resident input back to host before reading its
// shape/data. This must happen before allocateLayerOutputs(), which may fit()
// an in-place op's output onto this very buffer and rewrite its header.
if (opBackend != DNN_BACKEND_CUDA)
cudaDownloadArg(inp, argTensor(inp));
#endif
const Mat& m = argTensor(inp);
inpMats[i] = m;
inpTypes[i] = m.type();
@@ -1184,15 +1556,15 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
if (tracingMode != DNN_TRACE_NONE) {
strm_ << "-----------\n";
strm_ << "'" << graph->name() << "' [" << opidx << "/" << nops << "]. " << layer->type << " node: " << layer->name << "\n";
strm_ << "'" << graph->name() << "' [" << opidx << "/" << nops << "]. " << op->type << " node: " << op->name << "\n";
for (i = 0; i < ninputs; i++) {
Arg inp = inputs[i];
traceArg(strm_, "Input", i, inp, false);
}
}
bool dynamicOutShapes = layer->dynamicOutputShapes();
bool dynamicOutShapes = op->dynamicOutputShapes();
if (!dynamicOutShapes) {
allocateLayerOutputs(layer, inpTypes, inpShapes, outTypes, outShapes, outOrigData, outMats,
allocateLayerOutputs(op, inpTypes, inpShapes, outTypes, outShapes, outOrigData, outMats,
tempTypes, tempShapes, tempMats, scratchBufs, true);
} else {
outMats.resize(noutputs);
@@ -1205,15 +1577,31 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
timestamp = getTickCount();
std::vector<Ptr<Graph> >* subgraphs = layer->subgraphs();
std::vector<Ptr<Graph> >* subgraphs = op->subgraphs();
if (!subgraphs) {
if (finalizeLayers)
layer->finalize(inpMats, outMats);
layer->forward(inpMats, outMats, tempMats);
#ifdef HAVE_CUDA
if (opBackend == DNN_BACKEND_CUDA) {
if (finalizeLayers)
layer->finalize(inpMats, outMats);
forwardOpCUDA(this, gimpl, opidx, inputs, outputs, inpMats, outMats);
} else
#endif
{
// Device-resident inputs were already synced to host in the capture loop above.
if (finalizeLayers)
layer->finalize(inpMats, outMats);
layer->forward(inpMats, outMats, tempMats);
#ifdef HAVE_CUDA
// CPU produced fresh host data; invalidate any stale device copy of its outputs.
for (size_t k = 0; k < noutputs; k++)
cudaSetHostDirty(outputs[k]);
#endif
}
}
else {
Ptr<IfLayer> iflayer = layer.dynamicCast<IfLayer>();
Ptr<LoopLayer> loopLayer = layer.dynamicCast<LoopLayer>();
Ptr<ScanLayer> scanLayer = layer.dynamicCast<ScanLayer>();
if (iflayer) {
int branch = iflayer->branch(inpMats[0]);
Ptr<Graph> subgraph = subgraphs->at(branch);
@@ -1306,9 +1694,76 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
outMats[n_state + i] = stacked;
}
}
else if (scanLayer) {
CV_Assert(subgraphs->size() == 1);
Ptr<Graph> 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<int>& iax = scanLayer->scanInputAxes();
const std::vector<int>& oax = scanLayer->scanOutputAxes();
const std::vector<int>& idir = scanLayer->scanInputDirections();
const std::vector<int>& odir = scanLayer->scanOutputDirections();
const std::vector<int>& orank = scanLayer->scanOutputRanks();
std::vector<Mat> scanIn(M);
std::vector<int> inAxis(M);
std::vector<char> 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<Mat> state(S);
for (int i = 0; i < S; i++) state[i] = inpMats[i];
std::vector<std::vector<Mat> > history(K);
std::vector<Mat> 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()));
("unknown layer type '%s' with subgraphs", op->type.c_str()));
}
}
CV_Assert(outMats.size() == noutputs);
@@ -1391,6 +1846,13 @@ void Net::Impl::forwardGraph(Ptr<Graph>& graph, InputArrayOfArrays inputs_,
outputsVec.resize(n_gr_outputs);
for (i = 0; i < n_gr_outputs; i++) {
Arg out = gr_outputs[i];
#ifdef HAVE_CUDA
// A graph output produced on the device must be brought back to host before it is read.
if (out.idx >= 0) {
Mat& t = argTensor(out);
cudaDownloadArg(out, t);
}
#endif
const Mat& outm = argTensor(out);
if (isMainGraph) {
if (outm.size.layout == DATA_LAYOUT_BLOCK) {
@@ -1415,8 +1877,8 @@ void Net::Impl::updateUseCounts(const Ptr<Graph>& graph, std::vector<int>& useco
CV_Assert(output.idx < (int)usecounts.size());
usecounts[output.idx]++;
}
const std::vector<Ptr<Layer> >& prog = graph->prog();
for (const Ptr<Layer>& layer: prog) {
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
for (const Ptr<LayerInfo>& layer: prog) {
const std::vector<Arg>& inputs = layer->inputs;
for (const Arg& input: inputs) {
CV_Assert(input.idx < (int)usecounts.size());
@@ -1447,14 +1909,14 @@ int Net::Impl::updateGraphOfs(const Ptr<Graph>& graph, int currofs, bool ismain)
allgraphs.clear();
layerNameToId.clear();
}
const std::vector<Ptr<Layer> >& prog = graph->prog();
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
size_t i, nops = prog.size();
int subgraph_ofs = currofs + (int)nops;
std::string name = graph->name();
graphofs.insert(std::make_pair(name, currofs));
allgraphs.push_back(graph);
for (i = 0; i < nops; i++) {
const Ptr<Layer>& layer = prog[i];
const Ptr<LayerInfo>& layer = prog[i];
layerNameToId.insert(std::make_pair(layer->name, currofs + (int)i));
const std::vector<Ptr<Graph> >* subgraphs = layer->subgraphs();
if (subgraphs) {
@@ -1601,12 +2063,12 @@ bool Net::Impl::tryInferGraphShapes(const Ptr<Graph>& graph,
if (!graph)
return true;
const std::vector<Ptr<Layer> >& prog = graph->prog();
const std::vector<Ptr<LayerInfo> >& prog = graph->prog();
std::vector<MatShape> inpShapes, outShapes, tempShapes;
std::vector<int> inpTypes, outTypes, tempTypes;
for (const Ptr<Layer>& layer: prog) {
for (const Ptr<LayerInfo>& layer: prog) {
if (!layer)
continue;
+21 -2
View File
@@ -299,6 +299,18 @@ void Net::Impl::setPreferableBackend(Net& net, int backendId)
if (mainGraph)
{
if (backendId == DNN_BACKEND_OPENCV
#ifdef HAVE_CUDA
|| backendId == DNN_BACKEND_CUDA
#endif
)
{
if (preferableBackend != backendId) {
preferableBackend = backendId;
finalized = false; // re-select per-op executors on next finalize()
}
return;
}
CV_LOG_WARNING(NULL, "Back-ends are not supported by the new graph engine for now");
preferableBackend = backendId;
return;
@@ -347,10 +359,17 @@ void Net::Impl::setPreferableTarget(int targetId)
if (mainGraph)
{
if (targetId != DNN_TARGET_CPU)
#ifdef HAVE_CUDA
if (targetId == DNN_TARGET_CPU || IS_DNN_CUDA_TARGET(targetId))
{
CV_LOG_WARNING(NULL, "Targets are not supported by the new graph engine for now");
if (preferableTarget != targetId) {
preferableTarget = targetId;
finalized = false; // re-select per-op executors on next finalize()
}
return;
}
#endif
CV_LOG_WARNING(NULL, "Targets are not supported by the new graph engine for now");
return;
}
if (netWasQuantized && targetId != DNN_TARGET_CPU &&
File diff suppressed because it is too large Load Diff
+103 -32
View File
@@ -167,7 +167,7 @@ protected:
std::string onnxBasePath;
Ptr<Graph> curr_graph;
opencv_onnx::GraphProto* curr_graph_proto;
std::vector<Ptr<Layer> > curr_prog;
std::vector<Ptr<LayerInfo> > curr_prog;
std::vector<Arg> node_inputs, node_outputs;
std::string framework_name;
@@ -202,12 +202,14 @@ 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);
void parseConv (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseConvTranspose (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseCumSum (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseCumProd (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseDepthSpaceOps (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseDetectionOutput (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parsePriorBox (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
@@ -285,6 +287,7 @@ protected:
// URL: https://github.com/microsoft/onnxruntime/blob/master/docs/ContribOperators.md
void parseAttention (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseAttentionOnnxAi (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseCausalConvWithState (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseSDPA (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseDequantizeLinear (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void parseQuantizeLinear (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
@@ -719,7 +722,7 @@ Net ONNXImporter2::parseModel()
sstrm << "DNN/ONNX: the model ";
if (!onnxFilename.empty())
sstrm << "'" << onnxFilename << "' ";
sstrm << "cannot be loaded with the new parser. Trying the older parser. ";
sstrm << "cannot be loaded by the DNN engine.";
if (!missing_ops.empty()) {
sstrm << " Unsupported operations:\n";
auto it = missing_ops.begin();
@@ -740,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);
@@ -768,21 +777,6 @@ bool ONNXImporter2::parseValueInfo(const opencv_onnx::ValueInfoProto& valueInfoP
} else {
// ONNX allows dimensions without dim_value and dim_param.
// Treat them as unnamed symbolic dimensions.
// NOTE: LSTM with unnamed dimensions is not ready in the new graph
// engine yet, so force fallback to classic parser.
if (curr_graph_proto)
{
const int n_nodes = curr_graph_proto->node_size();
for (int i = 0; i < n_nodes; ++i)
{
const std::string& op = curr_graph_proto->node(i).op_type();
if (op == "LSTM")
{
raiseError();
return false;
}
}
}
val_j = net.findDim("", true);
}
//CV_Assert(0 <= val_j && val_j <= INT_MAX);
@@ -898,7 +892,7 @@ Ptr<Graph> ONNXImporter2::parseGraph(opencv_onnx::GraphProto* graph_proto, bool
opencv_onnx::GraphProto* saved_graph_proto = curr_graph_proto;
Ptr<Graph> saved_graph = curr_graph;
std::vector<Ptr<Layer> > saved_prog;
std::vector<Ptr<LayerInfo> > saved_prog;
curr_graph_proto = graph_proto;
std::vector<Arg> inputs, outputs;
@@ -1354,7 +1348,9 @@ void ONNXImporter2::parseLSTM(LayerParams& layerParams, const opencv_onnx::NodeP
layerParams.set("produce_sequence_y", need_y);
if (lstm_proto.input_size() == 8)
// The 8th input (P, peephole weights) is optional; an absent ONNX input is
// encoded as an empty name. Only enable peephole when it is actually present.
if (lstm_proto.input_size() == 8 && !lstm_proto.input(7).empty())
layerParams.set("use_peephole", true);
@@ -1462,9 +1458,17 @@ void ONNXImporter2::parsePRelu(LayerParams& layerParams, const opencv_onnx::Node
{
layerParams.type = "PReLU";
CV_Assert(node_inputs.size() == 2);
CV_Assert(net.isConstArg(node_inputs[1]));
layerParams.blobs.push_back(net.argTensor(node_inputs[1]));
addLayer(layerParams, node_proto, 1);
if (net.isConstArg(node_inputs[1]))
{
layerParams.blobs.push_back(net.argTensor(node_inputs[1]));
addLayer(layerParams, node_proto, 1);
}
else
{
// Slope produced by a foldable subgraph (e.g. Reshape of an initializer):
// keep it as a second input for constFold()/constArgs() to resolve.
addLayer(layerParams, node_proto);
}
}
void ONNXImporter2::parseLpNormalization(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
@@ -1500,9 +1504,12 @@ void ONNXImporter2::parseGemm(LayerParams& layerParams, const opencv_onnx::NodeP
if (net.isConstArg(node_inputs[1]) && (n_inputs == 2 || net.isConstArg(node_inputs[2]))) {
Mat B = net.argTensor(node_inputs[1]);
layerParams.blobs.push_back(B);
layerParams.set("constB", true); // weight folded into blobs[0] (enables CUDA InnerProduct)
if (n_inputs > 2) {
Mat bias = net.argTensor(node_inputs[2]);
layerParams.blobs.push_back(bias);
layerParams.set("have_bias", true);
layerParams.set("constC", true);
}
n_inputs = 1;
}
@@ -1717,10 +1724,50 @@ void ONNXImporter2::parseLoop(LayerParams& layerParams,
CV_Assert(!subgraphs[0].empty());
Ptr<Layer>& loopLayer = curr_prog.back();
Ptr<LayerInfo>& loopLayer = curr_prog.back();
*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<Arg> saved_inputs = node_inputs, saved_outputs = node_outputs;
std::vector<Ptr<Graph> > 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<int>("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<int> 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<LayerInfo>& scanLayer = curr_prog.back();
*scanLayer->subgraphs() = subgraphs;
}
void ONNXImporter2::parseIf(LayerParams& layerParams,
const opencv_onnx::NodeProto& node_proto)
{
@@ -1742,7 +1789,7 @@ void ONNXImporter2::parseIf(LayerParams& layerParams,
CV_Assert_N(!thenelse[0].empty(), !thenelse[1].empty());
Ptr<Layer>& ifLayer = curr_prog.back();
Ptr<LayerInfo>& ifLayer = curr_prog.back();
*ifLayer->subgraphs() = thenelse;
}
@@ -2120,6 +2167,22 @@ void ONNXImporter2::parseCumSum(LayerParams& layerParams, const opencv_onnx::Nod
addLayer(layerParams, node_proto, ninputs);
}
void ONNXImporter2::parseCumProd(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
int ninputs = node_proto.input_size();
CV_Assert(ninputs == 2);
layerParams.type = "CumProd";
if (net.isConstArg(node_inputs[1]))
{
Mat axisTensor;
net.argTensor(node_inputs[1]).convertTo(axisTensor, CV_32S);
CV_Assert(axisTensor.total() == 1);
layerParams.set("axis", axisTensor.at<int>(0));
ninputs = 1;
}
addLayer(layerParams, node_proto, ninputs);
}
// "Equal" "Greater" "Less" "Pow" "Add" "Sub" "Mul" "Div" "Sum" "Min" "Max" "GreaterOrEqual" "LessOrEqual" "And" "Or" "Xor"
void ONNXImporter2::parseElementWise(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_)
{
@@ -2632,6 +2695,11 @@ void ONNXImporter2::parseSDPA(LayerParams& params, const opencv_onnx::NodeProto&
addLayer(params, node_proto, 3);
}
void ONNXImporter2::parseCausalConvWithState(LayerParams& params, const opencv_onnx::NodeProto& node_proto) {
params.type = "CausalConvWithState";
addLayer(params, node_proto);
}
void ONNXImporter2::parseRoiAlign(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
{
layerParams.type = "RoiAlign";
@@ -2695,6 +2763,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;
@@ -2724,6 +2793,7 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI()
dispatch["DetectionOutput"] = &ONNXImporter2::parseDetectionOutput;
dispatch["PriorBox"] = &ONNXImporter2::parsePriorBox;
dispatch["CumSum"] = &ONNXImporter2::parseCumSum;
dispatch["CumProd"] = &ONNXImporter2::parseCumProd;
dispatch["SpaceToDepth"] = dispatch["DepthToSpace"] = &ONNXImporter2::parseDepthSpaceOps;
dispatch["ScatterElements"] = dispatch["Scatter"] = dispatch["ScatterND"] = &ONNXImporter2::parseScatter;
dispatch["Tile"] = &ONNXImporter2::parseTile;
@@ -2770,6 +2840,7 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI()
// Opset domain cannot be modified from onnx_graph_simplifier.cpp so this
// operator cannot be parsed if only added in buildDispatchMap_COM_MICROSOFT
dispatch["Attention"] = &ONNXImporter2::parseAttentionOnnxAi;
dispatch["CausalConvWithState"] = &ONNXImporter2::parseCausalConvWithState;
domain_dispatch_map[str_domain_ai_onnx] = dispatch;
}
+63
View File
@@ -8,10 +8,73 @@
#include "op_cuda.hpp"
#include "cuda4dnn/init.hpp"
#include "net_impl.hpp"
#include <opencv2/dnn/layer.details.hpp>
namespace cv { namespace dnn {
CV__DNN_INLINE_NS_BEGIN
class CUDALegacyExec : public Layer
{
public:
CUDALegacyExec(const Ptr<Layer>& impl_, void* ctx_) : impl(impl_), ctx(ctx_) {}
static Ptr<Layer> create(const Ptr<LayerInfo>& data, void* backendCtx)
{
Ptr<Layer> impl = data.dynamicCast<Layer>();
if (!impl || !backendCtx || !impl->supportBackend(DNN_BACKEND_CUDA))
return Ptr<Layer>(); // unsupported -> CPU fallback
Ptr<CUDALegacyExec> e(new CUDALegacyExec(impl, backendCtx));
e->name = impl->name;
e->type = impl->type;
e->inputs = impl->inputs;
e->outputs = impl->outputs;
return e;
}
void finalize(InputArrayOfArrays inputs, OutputArrayOfArrays outputs) CV_OVERRIDE
{
impl->finalize(inputs, outputs);
}
void forwardCUDA(InputArrayOfArrays inputs_,
OutputArrayOfArrays outputs_,
void* workspace) CV_OVERRIDE
{
std::vector<cuda::GpuMatND> inputs, outputs;
inputs_.getGpuMatNDVector(inputs);
outputs_.getGpuMatNDVector(outputs);
cuda4dnn::csl::Workspace& ws = *reinterpret_cast<cuda4dnn::csl::Workspace*>(workspace);
if (!node) {
impl->preferableTarget = preferableTarget; // initCUDA may pick FP16/FP32 by target
cuda4dnn::csl::CSLContext context = *reinterpret_cast<cuda4dnn::csl::CSLContext*>(ctx);
node = impl->initCUDA(&context, inputs_, outputs_);
CV_Assert(node);
cudaNode = node.dynamicCast<CUDABackendNode>();
CV_Assert(cudaNode);
ws.require(cudaNode->get_workspace_memory_in_bytes());
}
cudaNode->forward(inputs, outputs, ws);
}
Ptr<Layer> impl;
void* ctx;
Ptr<BackendNode> node;
Ptr<CUDABackendNode> cudaNode;
};
void registerCudaCommonExecs()
{
CV_DNN_REGISTER_EXEC_CLASS(ReLU, DNN_BACKEND_CUDA, CUDALegacyExec);
CV_DNN_REGISTER_EXEC_CLASS(ReLU6, DNN_BACKEND_CUDA, CUDALegacyExec);
CV_DNN_REGISTER_EXEC_CLASS(NaryEltwise, DNN_BACKEND_CUDA, CUDALegacyExec);
CV_DNN_REGISTER_EXEC_CLASS(Flatten, DNN_BACKEND_CUDA, CUDALegacyExec);
CV_DNN_REGISTER_EXEC_CLASS(BatchNorm2, DNN_BACKEND_CUDA, CUDALegacyExec);
CV_DNN_REGISTER_EXEC_CLASS(MaxPool, DNN_BACKEND_CUDA, CUDALegacyExec);
CV_DNN_REGISTER_EXEC_CLASS(Gemm, DNN_BACKEND_CUDA, CUDALegacyExec);
CV_DNN_REGISTER_EXEC_CLASS(Pooling, DNN_BACKEND_CUDA, CUDALegacyExec); // GlobalAveragePool/GlobalMaxPool
}
void Net::Impl::initCUDABackend(const std::vector<LayerPin>& blobsToKeep_)
{
+74 -3
View File
@@ -18,6 +18,7 @@
#include <opencv2/dnn/shape_utils.hpp>
#include <opencv2/core.hpp>
#include <opencv2/core/cuda.hpp>
#include <cstddef>
#include <memory>
@@ -177,6 +178,22 @@ namespace cv { namespace dnn {
if (temp.data != destMat.data)
temp.copyTo(destMat);
}
/** @brief builds a read-only TensorView<T> over the device memory of a GpuMatND (no copy) */
template <class T>
TensorView<T> viewOf(const cuda::GpuMatND& g) {
using const_ptr = typename TensorView<T>::const_pointer;
return TensorView<T>(const_ptr(reinterpret_cast<const T*>(g.getDevicePtr())),
std::begin(g.size), std::end(g.size));
}
/** @brief builds a writable TensorSpan<T> over the device memory of a GpuMatND (no copy) */
template <class T>
TensorSpan<T> spanOf(const cuda::GpuMatND& g) {
using ptr = typename TensorSpan<T>::pointer;
return TensorSpan<T>(ptr(reinterpret_cast<T*>(g.getDevicePtr())),
std::begin(g.size), std::end(g.size));
}
}} /* namespace cuda4dnn::csl */
/** base class for CUDA operation nodes (for all supported targets) */
@@ -185,10 +202,25 @@ namespace cv { namespace dnn {
CUDABackendNode() : BackendNode(DNN_BACKEND_CUDA) { }
virtual ~CUDABackendNode() { }
/** classic-engine entry point (wrapper-based).
*
* The default adapts the wrappers to GpuMatND headers and dispatches to the GpuMatND
* overload, so ops ported to the new graph engine only implement the GpuMatND forward.
* Ops not yet ported keep overriding this method directly.
*/
virtual void forward(
const std::vector<cv::Ptr<BackendWrapper>>& inputs,
const std::vector<cv::Ptr<BackendWrapper>>& outputs,
cuda4dnn::csl::Workspace& workspace) = 0;
cuda4dnn::csl::Workspace& workspace);
/** new graph-engine entry point (wrapper-free): operates directly on GpuMatND device tensors */
virtual void forward(
const std::vector<cuda::GpuMatND>& inputs,
const std::vector<cuda::GpuMatND>& outputs,
cuda4dnn::csl::Workspace& workspace)
{
CV_Error(Error::StsNotImplemented, "GpuMatND CUDA forward is not implemented for this operation");
}
virtual std::size_t get_workspace_memory_in_bytes() const noexcept { return 0; }
};
@@ -229,7 +261,7 @@ namespace cv { namespace dnn {
template <template <class> class NodeType, class ...Args>
cv::Ptr<BackendNode> make_cuda_node_with_type(int targetId, int hostMatType, Args&& ...args) {
CV_CheckType(hostMatType, hostMatType == CV_32F || hostMatType == CV_8S || hostMatType == CV_8U || hostMatType == CV_32S || hostMatType == CV_64S, "");
CV_CheckType(hostMatType, hostMatType == CV_32F || hostMatType == CV_16F || hostMatType == CV_8S || hostMatType == CV_8U || hostMatType == CV_32S || hostMatType == CV_64S, "");
if (hostMatType == CV_8S)
return Ptr<BackendNode>(new NodeType<int8_t>(std::forward<Args>(args)...));
@@ -239,6 +271,8 @@ namespace cv { namespace dnn {
return Ptr<BackendNode>(new NodeType<int32_t>(std::forward<Args>(args)...));
else if (hostMatType == CV_64S)
return Ptr<BackendNode>(new NodeType<int64_t>(std::forward<Args>(args)...));
else if (hostMatType == CV_16F) // device tensor already stored as half (FP16 target)
return Ptr<BackendNode>(new NodeType<half>(std::forward<Args>(args)...));
else if (hostMatType == CV_32F)
{
if (targetId == DNN_TARGET_CUDA_FP16)
@@ -252,7 +286,7 @@ namespace cv { namespace dnn {
template <template <class, class> class NodeType, class T_INDEX, class ...Args>
cv::Ptr<BackendNode> make_cuda_node_with_indices(int targetId, int hostMatType, Args&& ...args) {
CV_CheckType(hostMatType, hostMatType == CV_32F || hostMatType == CV_8S || hostMatType == CV_8U || hostMatType == CV_32S || hostMatType == CV_64S, "");
CV_CheckType(hostMatType, hostMatType == CV_32F || hostMatType == CV_16F || hostMatType == CV_8S || hostMatType == CV_8U || hostMatType == CV_32S || hostMatType == CV_64S, "");
if (hostMatType == CV_8S)
return Ptr<BackendNode>(new NodeType<int8_t, T_INDEX>(std::forward<Args>(args)...));
@@ -262,6 +296,8 @@ namespace cv { namespace dnn {
return Ptr<BackendNode>(new NodeType<int32_t, T_INDEX>(std::forward<Args>(args)...));
else if (hostMatType == CV_64S)
return Ptr<BackendNode>(new NodeType<int64_t, T_INDEX>(std::forward<Args>(args)...));
else if (hostMatType == CV_16F) // device tensor already stored as half (FP16 target)
return Ptr<BackendNode>(new NodeType<half, T_INDEX>(std::forward<Args>(args)...));
else if (hostMatType == CV_32F)
{
if (targetId == DNN_TARGET_CUDA_FP16)
@@ -298,6 +334,15 @@ namespace cv { namespace dnn {
virtual void setStream(cuda4dnn::csl::Stream stream, cuda4dnn::csl::Stream h2d_stream) noexcept = 0;
virtual void update(const MatShape& shape, std::size_t offset) = 0;
/** @brief returns a GpuMatND header over the device memory (no copy, no synchronization)
*
* The header borrows the device memory and is only valid while this wrapper (and its
* device tensor) is alive. Host<->device synchronization is the caller's responsibility
* (see copyToDevice()/setDeviceDirty()); this accessor must stay side-effect free so it
* can be used at init time before any stream is attached.
*/
virtual cuda::GpuMatND getDeviceMatND() = 0;
};
namespace cuda4dnn { namespace detail {
@@ -646,6 +691,13 @@ namespace cv { namespace dnn {
return tensor_view_type(shared_block->device.get() + offset, std::begin(shape), std::end(shape));
}
cuda::GpuMatND getDeviceMatND() override {
DEVICE_T* ptr = shared_block->device.get().get() + offset;
// Report the host element type (e.g. CV_32F even for an FP16 device buffer): the type is
// only consumed by initCUDA(), while the compute reinterprets the pointer per its own T.
return cuda::GpuMatND(shape, hostMatDepth, static_cast<void*>(ptr));
}
private:
/* The same tensor memory can be reused by different layers whenever possible.
* Hence, it is possible for different backend wrappers to point to the same memory.
@@ -697,6 +749,25 @@ namespace cv { namespace dnn {
template <class T>
using GetCUDABackendWrapperType = typename GetCUDABackendWrapperType_<T>::type;
inline void CUDABackendNode::forward(
const std::vector<cv::Ptr<BackendWrapper>>& inputs,
const std::vector<cv::Ptr<BackendWrapper>>& outputs,
cuda4dnn::csl::Workspace& workspace)
{
std::vector<cuda::GpuMatND> inGpu(inputs.size()), outGpu(outputs.size());
for (size_t i = 0; i < inputs.size(); i++) {
auto w = inputs[i].dynamicCast<CUDABackendWrapper>();
w->copyToDevice(); // host->device if needed (mirrors getView())
inGpu[i] = w->getDeviceMatND();
}
for (size_t i = 0; i < outputs.size(); i++) {
auto w = outputs[i].dynamicCast<CUDABackendWrapper>();
w->setDeviceDirty(); // op writes the device buffer (mirrors getSpan())
outGpu[i] = w->getDeviceMatND();
}
forward(inGpu, outGpu, workspace);
}
#endif
}} /* namespace cv::dnn */
+19 -43
View File
@@ -576,7 +576,7 @@ protected:
std::map<String, int> layer_id;
bool newEngine;
std::vector<Ptr<Layer>> curProg;
std::vector<Ptr<LayerInfo>> curProg;
std::vector<std::vector<std::string>> layersOutputs;
std::vector<Arg> modelInputs;
std::unordered_map<std::string, MatShape> tensorsShape;
@@ -3263,31 +3263,24 @@ void TFLayerHandler::handleFailed(const tensorflow::NodeDef& layer)
} // namespace
// The TensorFlow importer always runs on the OpenCV engine; ENGINE_AUTO resolves to it.
static void warnIfUnsupportedTfEngine(int engine)
{
static const int engine_forced =
(int)utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
if (engine_forced == ENGINE_OPENCV)
engine = engine_forced;
if (engine != ENGINE_AUTO && engine != ENGINE_OPENCV)
CV_LOG_WARNING(NULL, "DNN/TF: only ENGINE_AUTO and ENGINE_OPENCV are supported; "
"using ENGINE_OPENCV.");
}
Net readNetFromTensorflow(const String &model, const String &config, int engine,
const std::vector<String>& extraOutputs)
{
static const int engine_forced = utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
if(engine_forced != ENGINE_AUTO)
engine = engine_forced;
if (engine == ENGINE_AUTO)
{
try
{
return detail::readNetDiagnostic<TFImporter>(model.c_str(), config.c_str(),
true, extraOutputs);
}
catch(const std::exception& e)
{
CV_LOG_WARNING(NULL, "Can't parse model with the new dnn engine, trying to parse with the old dnn engine: " << e.what());
return detail::readNetDiagnostic<TFImporter>(model.c_str(), config.c_str(),
false, extraOutputs);
}
}
else
{
return detail::readNetDiagnostic<TFImporter>(model.c_str(), config.c_str(), engine == ENGINE_NEW || engine == ENGINE_AUTO, extraOutputs);
}
warnIfUnsupportedTfEngine(engine);
return detail::readNetDiagnostic<TFImporter>(model.c_str(), config.c_str(),
/*newEngine*/ true, extraOutputs);
}
Net readNetFromTensorflow(const char* bufferModel, size_t lenModel,
@@ -3295,26 +3288,9 @@ Net readNetFromTensorflow(const char* bufferModel, size_t lenModel,
int engine,
const std::vector<String>& extraOutputs)
{
static const int engine_forced = utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
if(engine_forced != ENGINE_AUTO)
engine = engine_forced;
if (engine == ENGINE_AUTO)
{
try
{
return detail::readNetDiagnostic<TFImporter>(bufferModel, lenModel, bufferConfig, lenConfig, true, extraOutputs);
}
catch(const std::exception& e)
{
CV_LOG_WARNING(NULL, "Can't parse model with the new dnn engine, trying to parse with the old dnn engine: " << e.what());
return detail::readNetDiagnostic<TFImporter>(bufferModel, lenModel, bufferConfig, lenConfig, false, extraOutputs);
}
}
else
{
return detail::readNetDiagnostic<TFImporter>(bufferModel, lenModel, bufferConfig, lenConfig, engine == ENGINE_NEW || engine == ENGINE_AUTO, extraOutputs);
}
warnIfUnsupportedTfEngine(engine);
return detail::readNetDiagnostic<TFImporter>(bufferModel, lenModel, bufferConfig, lenConfig,
/*newEngine*/ true, extraOutputs);
}
Net readNetFromTensorflow(const std::vector<uchar>& bufferModel, const std::vector<uchar>& bufferConfig, int engine,
+17 -9
View File
@@ -34,7 +34,7 @@ private:
const flatbuffers::Vector<flatbuffers::Offset<opencv_tflite::Tensor> >* modelTensors;
std::map<int, Mat> allTensors;
Net& dstNet;
std::vector<Ptr<Layer>> curProg;
std::vector<Ptr<LayerInfo>> curProg;
// This is a vector of pairs (layerId, outputId) where we iterate over
// indices from TFLite notation and get created OpenCV layers.
@@ -1503,10 +1503,20 @@ void TFLiteImporter::getQuantParams(const Operator& op, float& inpScale, int& in
}
}
Net readNetFromTFLite(const String &modelPath, int engine) {
static const int engine_forced = utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
if(engine_forced != ENGINE_AUTO)
// The TFLite importer always runs on the OpenCV engine; ENGINE_AUTO resolves to it.
static void warnIfUnsupportedTFLiteEngine(int engine)
{
static const int engine_forced =
(int)utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
if (engine_forced == ENGINE_OPENCV)
engine = engine_forced;
if (engine != ENGINE_AUTO && engine != ENGINE_OPENCV)
CV_LOG_WARNING(NULL, "DNN/TFLite: only ENGINE_AUTO and ENGINE_OPENCV are supported; "
"using ENGINE_OPENCV.");
}
Net readNetFromTFLite(const String &modelPath, int engine) {
warnIfUnsupportedTFLiteEngine(engine);
Net net;
@@ -1526,7 +1536,7 @@ Net readNetFromTFLite(const String &modelPath, int engine) {
ifs.read(content.data(), sz);
CV_Assert(!ifs.bad());
TFLiteImporter(net, content.data(), content.size(), engine == ENGINE_NEW || engine == ENGINE_AUTO);
TFLiteImporter(net, content.data(), content.size(), /*newEngine*/ true);
return net;
}
@@ -1535,12 +1545,10 @@ Net readNetFromTFLite(const std::vector<uchar>& bufferModel, int engine) {
}
Net readNetFromTFLite(const char *bufferModel, size_t bufSize, int engine) {
static const int engine_forced = utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
if(engine_forced != ENGINE_AUTO)
engine = engine_forced;
warnIfUnsupportedTFLiteEngine(engine);
Net net;
TFLiteImporter(net, bufferModel, bufSize, engine == ENGINE_NEW || engine == ENGINE_AUTO);
TFLiteImporter(net, bufferModel, bufSize, /*newEngine*/ true);
return net;
}
+18 -11
View File
@@ -124,10 +124,12 @@ TEST_P(DNNTestNetwork, DISABLED_YOLOv8n) {
TEST_P(DNNTestNetwork, AlexNet)
{
applyTestTag(CV_TEST_TAG_MEMORY_1GB);
// Skip memory-heavy OpenCL targets on 32-bit (x86) platforms to avoid OutOfMemoryError
if (sizeof(void*) == 4 &&
(target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16))
throw SkipTestException("Skip memory-heavy OpenCL target on 32-bit (x86) platform");
// fc6 needs one contiguous 9216*4096*4 = 144 MiB block, which does not fit
// reliably in the 2 GB user address space of a 32-bit process on Windows.
#ifdef _WIN32
if (sizeof(void*) == 4)
throw SkipTestException("Skip memory-heavy network on 32-bit (x86) Windows platform");
#endif
processNet("dnn/onnx/models/alexnet.onnx", "", Size(227, 227));
expectNoFallbacksFromIE(net);
expectNoFallbacksFromCUDA(net);
@@ -140,6 +142,10 @@ TEST_P(DNNTestNetwork, ResNet_50)
CV_TEST_TAG_DEBUG_VERYLONG
);
// New-engine CUDA: result is off the strict FP32 tolerance vs the CPU reference; skip for now.
if (backend == DNN_BACKEND_CUDA)
applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA, CV_TEST_TAG_DNN_SKIP_CUDA_FP16);
double l1 = default_l1, lInf = default_lInf;
if (target == DNN_TARGET_CUDA_FP16 || target == DNN_TARGET_OPENCL_FP16 || target == DNN_TARGET_CPU_FP16)
{
@@ -269,13 +275,6 @@ TEST_P(DNNTestNetwork, SSD_VGG16)
if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH)
applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NGRAPH);
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
Mat sample = imread(findDataFile("dnn/street.png"));
Mat inp = blobFromImage(sample, 1.0f, Size(300, 300), Scalar(), false);
@@ -1258,6 +1257,10 @@ TEST_P(Concat, Accuracy)
Backend backendId = get<0>(get<2>(GetParam()));
Target targetId = get<1>(get<2>(GetParam()));
// New-engine CUDA: Concat is not yet supported for this shape; skip for now.
if (backendId == DNN_BACKEND_CUDA && inSize == Vec3i(2, 8, 6))
applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA, CV_TEST_TAG_DNN_SKIP_CUDA_FP16);
#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LE(2018050000)
if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019 && targetId == DNN_TARGET_MYRIAD
&& inSize == Vec3i(1, 4, 5) && numChannels == Vec3i(1, 6, 2)
@@ -1340,6 +1343,10 @@ TEST_P(Eltwise, Accuracy)
Backend backendId = get<0>(get<4>(GetParam()));
Target targetId = get<1>(get<4>(GetParam()));
// New-engine CUDA: Eltwise is not yet supported for this shape; skip for now.
if (backendId == DNN_BACKEND_CUDA && inSize == Vec3i(2, 8, 6))
applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA, CV_TEST_TAG_DNN_SKIP_CUDA_FP16);
#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000)
// accuracy
if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && targetId == DNN_TARGET_OPENCL &&
-11
View File
@@ -90,17 +90,6 @@ TEST(Reproducibility_SSD, Accuracy)
CV_TEST_TAG_DEBUG_VERYLONG
);
// The classic engine importer no longer carries the Caffe-SSD specific
// handling (LpNormalization/DetectionOutput); this model is supported on
// the new engine only.
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
Net net = readNetFromONNX(findDataFile("dnn/onnx/models/ssd_vgg16.onnx", false));
ASSERT_FALSE(net.empty());
net.setPreferableBackend(DNN_BACKEND_OPENCV);
+6
View File
@@ -82,6 +82,12 @@ void normAssert(
{
cv::Mat refMat = ref.getMat();
cv::Mat testMat = test.getMat();
// 16-bit float types (half / bfloat16) are compared as float32: the two-arg
// cvtest::norm requires identical types and does not down-convert them.
if (refMat.depth() == CV_16F || refMat.depth() == CV_16BF)
refMat.convertTo(refMat, CV_32F);
if (testMat.depth() == CV_16F || testMat.depth() == CV_16BF)
testMat.convertTo(testMat, CV_32F);
const cv::MatShape refShape = refMat.shape();
const cv::MatShape testShape = testMat.shape();
const bool scalar1dCompatible =
@@ -55,13 +55,6 @@ static std::string _tf(TString filename)
TEST(Test_YOLO, read_yolov4_onnx)
{
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
Net net = readNet(findDataFile("dnn/yolov4.onnx", false));
ASSERT_FALSE(net.empty());
}
@@ -78,14 +71,6 @@ public:
float nmsThreshold = 0.4, bool useWinograd = true,
int zeroPadW = 0, Size inputSize = Size())
{
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
checkBackend();
Mat img1 = imread(_tf("dog416.png"));
+77
View File
@@ -0,0 +1,77 @@
// 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.
#include "test_precomp.hpp"
namespace opencv_test { namespace {
static Mat referenceMatMul(const Mat& A, const Mat& B, bool trans_a, float alpha)
{
const int M = trans_a ? A.cols : A.rows;
const int K = trans_a ? A.rows : A.cols;
const int N = B.cols;
Mat expected(M, N, CV_32F);
for (int m = 0; m < M; m++)
{
float* dst = expected.ptr<float>(m);
for (int n = 0; n < N; n++)
{
float acc = 0.f;
for (int k = 0; k < K; k++)
{
const float a = trans_a ? A.ptr<float>(k)[m] : A.ptr<float>(m)[k];
acc += a * B.ptr<float>(k)[n];
}
dst[n] = alpha * acc;
}
}
return expected;
}
// Exercises the fastGemmThin path (constant-B MatMul). M covers every register
// block width and the multi-block remainder, N covers full strips and partial
// column tails for any VLEN <= 512.
typedef testing::TestWithParam<tuple<int, int, int, int, float>> DNN_FastGemmThin;
TEST_P(DNN_FastGemmThin, MatMulAccuracy)
{
int M = get<0>(GetParam()), N = get<1>(GetParam()), K = get<2>(GetParam());
int trans_a = get<3>(GetParam());
float alpha = get<4>(GetParam());
RNG rng(0x5EED);
Mat B(K, N, CV_32F); rng.fill(B, RNG::UNIFORM, -1.f, 1.f);
Mat A(trans_a ? K : M, trans_a ? M : K, CV_32F);
rng.fill(A, RNG::UNIFORM, -1.f, 1.f);
LayerParams lp;
lp.type = "MatMul";
lp.name = "thin_matmul";
lp.set("transA", trans_a != 0);
lp.set("transB", false);
lp.set("alpha", alpha);
lp.blobs.push_back(B);
Net net;
net.addLayerToPrev(lp.name, lp.type, lp);
net.setInputsNames(std::vector<String>{ "A" });
net.setInput(A, "A");
Mat actual = net.forward();
Mat expected = referenceMatMul(A, B, trans_a != 0, alpha);
EXPECT_LE(cv::norm(expected, actual, NORM_INF), 2e-5f * std::max(K, 1))
<< "M=" << M << ", N=" << N << ", K=" << K
<< ", trans_a=" << trans_a << ", alpha=" << alpha;
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, DNN_FastGemmThin, Combine(
Values(1, 2, 3, 4, 5),
Values(16, 19, 64, 67, 80, 83),
Values(1, 3, 4, 7, 16, 64),
Values(0, 1),
Values(1.f, -0.5f)
));
}} // namespace
+1 -3
View File
@@ -153,9 +153,7 @@ TEST_F(Test_Graph_Simplifier, BiasedMatMulSubgraph) {
/* Test for 1 subgraphs
- BiasedMatMulSubgraph
*/
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
const std::string expected = engine_forced == cv::dnn::ENGINE_CLASSIC ? "MatMul" : "Gemm";
const std::string expected = "Gemm";
test("biased_matmul", expected);
}
+6 -50
View File
@@ -2249,21 +2249,10 @@ TEST(Layer_LSTM, repeatedInference)
TEST(Layer_If, resize)
{
// Skip this test when the classic DNN engine is explicitly requested. The
// "if" layer is supported only by the new engine.
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
// Mark the test as skipped and exit early.
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
const std::string imgname = findDataFile("cv/shared/lena.png", true);
const std::string modelname = findDataFile("dnn/onnx/models/if_layer.onnx", true);
dnn::Net net = dnn::readNetFromONNX(modelname, ENGINE_NEW);
dnn::Net net = dnn::readNetFromONNX(modelname, ENGINE_OPENCV);
Mat src = imread(imgname), blob;
dnn::blobFromImage(src, blob, 1.0, cv::Size(), cv::Scalar(), false, false);
@@ -2286,16 +2275,8 @@ TEST(Layer_If, resize)
TEST(Layer_If, subgraph_name_scoping)
{
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
const std::string modelname = findDataFile("dnn/onnx/models/subgraph_name_scoping.onnx", true);
dnn::Net net = dnn::readNetFromONNX(modelname, ENGINE_NEW);
dnn::Net net = dnn::readNetFromONNX(modelname, ENGINE_OPENCV);
int xshape[1] = {2};
Mat x(1, xshape, CV_32F);
@@ -2331,16 +2312,8 @@ TEST(Layer_If, subgraph_name_scoping)
TEST(Layer_Size, onnx_1d)
{
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
const std::string modelname = findDataFile("dnn/onnx/models/test_size_1d_model.onnx", true);
cv::dnn::Net net = cv::dnn::readNetFromONNX(modelname, ENGINE_NEW);
cv::dnn::Net net = cv::dnn::readNetFromONNX(modelname, ENGINE_OPENCV);
int sz1d[1] = {7};
cv::Mat x(1, sz1d, CV_32F);
@@ -2358,16 +2331,8 @@ TEST(Layer_Size, onnx_1d)
TEST(Layer_Size, onnx_0d_scalar)
{
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
const std::string modelname = findDataFile("dnn/onnx/models/test_size_0d_model.onnx", true);
cv::dnn::Net net = cv::dnn::readNetFromONNX(modelname, ENGINE_NEW);
cv::dnn::Net net = cv::dnn::readNetFromONNX(modelname, ENGINE_OPENCV);
cv::Mat x(1, 1, CV_32F);
x.at<float>(0, 0) = 3.14f;
@@ -2430,20 +2395,11 @@ class TESTKVCache : public testing::TestWithParam<std::string>
public:
void testKVCache(const std::string& layout)
{
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
// Mark the test as skipped and exit early.
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
std::string model_path = "dnn/onnx/models/test_attention_kv_cache_" + layout + ".onnx";
Net netWithKVCache = readNetFromONNX(findDataFile(model_path, true), cv::dnn::ENGINE_NEW);
Net netWithKVCache = readNetFromONNX(findDataFile(model_path, true), cv::dnn::ENGINE_OPENCV);
netWithKVCache.enableKVCache();
Net netWithoutKVCache = readNetFromONNX(findDataFile(model_path, true), cv::dnn::ENGINE_NEW);
Net netWithoutKVCache = readNetFromONNX(findDataFile(model_path, true), cv::dnn::ENGINE_OPENCV);
int T = 523, Nq = 8, Nkv = 4, D = 256;
// Keep the prefill larger than one cache page, then exercise generation
+9 -45
View File
@@ -690,17 +690,19 @@ static void topK(const Mat& probs, std::vector<std::pair<int, float> >& result,
}
}
typedef testing::TestWithParam<Target> Reproducibility_ResNet50_ONNX;
typedef testing::TestWithParam<tuple<Backend, Target> > Reproducibility_ResNet50_ONNX;
TEST_P(Reproducibility_ResNet50_ONNX, Accuracy)
{
Target targetId = GetParam();
Backend backendId = get<0>(GetParam());
Target targetId = get<1>(GetParam());
applyTestTag(targetId == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_512MB : CV_TEST_TAG_MEMORY_1GB);
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16
|| backendId == DNN_BACKEND_CUDA);
std::string modelname = _tf("onnx/models/resnet50v1.onnx", false);
Net net = readNetFromONNX(modelname);
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableBackend(backendId);
net.setPreferableTarget(targetId);
if (targetId == DNN_TARGET_CPU_FP16)
@@ -740,7 +742,7 @@ TEST_P(Reproducibility_ResNet50_ONNX, Accuracy)
}
}
INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_ResNet50_ONNX,
testing::ValuesIn(getAvailableTargets(DNN_BACKEND_OPENCV)));
dnnBackendsAndTargets(false, false, true, false, true, false, false, false));
typedef testing::TestWithParam<Target> Reproducibility_ResNet50_QDQ_ONNX;
TEST_P(Reproducibility_ResNet50_QDQ_ONNX, Accuracy)
@@ -798,13 +800,6 @@ TEST_P(Reproducibility_ViT_ONNX, Accuracy)
Target targetId = GetParam();
applyTestTag(targetId == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_1GB : CV_TEST_TAG_MEMORY_2GB);
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
auto engine_forced = static_cast<EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO));
if (engine_forced == ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
std::string modelname = _tf("vit_base_patch16_224_Opset16.onnx", false);
Net net = readNetFromONNX(modelname);
@@ -832,7 +827,7 @@ TEST_P(Reproducibility_ViT_ONNX, Accuracy)
topK(out, res, K);
ASSERT_EQ(int(res.size()), K);
// Reference top-5 captured from the ONNX Runtime engine (OPENCV_FORCE_DNN_ENGINE=4).
// Reference top-5 captured from the ONNX Runtime engine (OPENCV_FORCE_DNN_ENGINE=2, ENGINE_ORT).
std::vector<std::pair<int, float> > ref = {
{285, 7.683f}, {282, 7.182f}, {281, 6.894f}, {287, 3.623f}, {283, 3.287f}
};
@@ -859,13 +854,6 @@ TEST_P(Reproducibility_BERT_ONNX, Accuracy)
Target targetId = GetParam();
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
auto engine_forced = static_cast<EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO));
if (engine_forced == ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
std::string modelname = _tf("onnx/models/bert.onnx", false);
Net net = readNetFromONNX(modelname);
@@ -931,14 +919,6 @@ typedef testing::TestWithParam<Target> Reproducibility_MobileNetSSD_ONNX;
TEST_P(Reproducibility_MobileNetSSD_ONNX, Accuracy)
{
Target targetId = GetParam();
auto engine_forced = static_cast<EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO));
if (engine_forced == ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
applyTestTag(targetId == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_512MB : CV_TEST_TAG_MEMORY_1GB);
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
@@ -1265,14 +1245,6 @@ INSTANTIATE_TEST_CASE_P(/**/, Reproducibility_YOLOXS_ONNX,
typedef testing::TestWithParam<Target> Reproducibility_BlazeFace_ONNX;
TEST_P(Reproducibility_BlazeFace_ONNX, Accuracy)
{
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
Target targetId = GetParam();
applyTestTag(targetId == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_512MB : CV_TEST_TAG_MEMORY_1GB);
ASSERT_TRUE(ocl::useOpenCL() || targetId == DNN_TARGET_CPU || targetId == DNN_TARGET_CPU_FP16);
@@ -1381,16 +1353,8 @@ TEST_P(Reproducibility_SwinIR_ONNX, Accuracy)
Target targetId = GetParam();
applyTestTag(CV_TEST_TAG_MEMORY_512MB, CV_TEST_TAG_LONG);
auto engine_forced = static_cast<EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO));
if (engine_forced == ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
std::string modelname = _tf("onnx/models/swinir_x4_gan.onnx", false);
Net net = readNetFromONNX(modelname, ENGINE_NEW);
Net net = readNetFromONNX(modelname, ENGINE_OPENCV);
ASSERT_FALSE(net.empty());
net.setPreferableBackend(DNN_BACKEND_OPENCV);
-7
View File
@@ -109,13 +109,6 @@ TEST(SoftNMS, Accuracy)
// NMS with dynamic output shapes is only supported by the new engine.
TEST(NMS, ZeroDetections_Reshape)
{
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
std::string onnxmodel = findDataFile("dnn/onnx/models/nms_reshape_empty.onnx");
cv::dnn::Net net = cv::dnn::readNetFromONNX(onnxmodel);
+165 -13
View File
@@ -554,7 +554,6 @@ static const TestCase testConformanceConfig[] = {
{"test_pow_bcast_array", 2, 1},
{"test_pow_bcast_scalar", 2, 1},
{"test_pow_example", 2, 1},
{"test_pow_types_float", 2, 1},
{"test_pow_types_float32_int32", 2, 1},
{"test_pow_types_float32_int64", 2, 1},
{"test_pow_types_float32_uint32", 2, 1},
@@ -1673,6 +1672,160 @@ static const TestCase testConformanceConfig[] = {
{"test_top_k_uint64", 0, 0},
{"test_unique_length_1", 0, 0},
{"test_wrap_pad", 0, 0},
// ===== ONNX 1.22 additions =====
{"test_attention_4d_softcap_neginf_mask", 4, 1},
{"test_attention_4d_softcap_neginf_mask_expanded", 4, 1},
{"test_attention_4d_softcap_neginf_mask_poison", 4, 1},
{"test_attention_4d_softcap_neginf_mask_poison_expanded", 4, 1},
{"test_bitcast_2d_float32_to_int32", 1, 1},
{"test_bitcast_bool_to_uint8", 1, 1},
{"test_bitcast_float32_to_int32", 1, 1},
{"test_bitcast_float64_to_int64", 1, 1},
{"test_bitcast_int32_to_float32", 1, 1},
{"test_bitcast_int64_to_float64", 1, 1},
{"test_bitcast_int8_to_uint8", 1, 1},
{"test_bitcast_scalar_float32_to_int32", 1, 1},
{"test_bitcast_uint16_to_int16", 1, 1},
{"test_bitcast_uint32_to_int32", 1, 1},
{"test_cast_FLOAT16_to_INT2", 1, 1},
{"test_cast_FLOAT16_to_UINT2", 1, 1},
{"test_cast_FLOAT_to_INT2", 1, 1},
{"test_cast_FLOAT_to_UINT2", 1, 1},
{"test_cast_INT2_to_FLOAT", 1, 1},
{"test_cast_INT2_to_FLOAT16", 1, 1},
{"test_cast_INT2_to_INT8", 1, 1},
{"test_cast_UINT2_to_FLOAT", 1, 1},
{"test_cast_UINT2_to_FLOAT16", 1, 1},
{"test_cast_UINT2_to_UINT8", 1, 1},
{"test_castlike_FLOAT16_to_INT2", 2, 1},
{"test_castlike_FLOAT16_to_INT2_expanded", 2, 1},
{"test_castlike_FLOAT16_to_UINT2", 2, 1},
{"test_castlike_FLOAT16_to_UINT2_expanded", 2, 1},
{"test_castlike_FLOAT_to_INT2", 2, 1},
{"test_castlike_FLOAT_to_INT2_expanded", 2, 1},
{"test_castlike_FLOAT_to_UINT2", 2, 1},
{"test_castlike_FLOAT_to_UINT2_expanded", 2, 1},
{"test_castlike_INT2_to_FLOAT", 2, 1},
{"test_castlike_INT2_to_FLOAT16", 2, 1},
{"test_castlike_INT2_to_FLOAT16_expanded", 2, 1},
{"test_castlike_INT2_to_FLOAT_expanded", 2, 1},
{"test_castlike_INT2_to_INT8", 2, 1},
{"test_castlike_INT2_to_INT8_expanded", 2, 1},
{"test_castlike_UINT2_to_FLOAT", 2, 1},
{"test_castlike_UINT2_to_FLOAT16", 2, 1},
{"test_castlike_UINT2_to_FLOAT16_expanded", 2, 1},
{"test_castlike_UINT2_to_FLOAT_expanded", 2, 1},
{"test_castlike_UINT2_to_UINT8", 2, 1},
{"test_castlike_UINT2_to_UINT8_expanded", 2, 1},
{"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ_expanded", 2, 1},
{"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FN_expanded", 2, 1},
{"test_causal_conv_with_state_b1_c1_degenerate", 2, 2},
{"test_causal_conv_with_state_b1_c1_degenerate_expanded", 2, 2},
{"test_causal_conv_with_state_basic", 2, 2},
{"test_causal_conv_with_state_basic_expanded", 2, 2},
{"test_causal_conv_with_state_decode_step", 4, 2},
{"test_causal_conv_with_state_decode_step_expanded", 4, 2},
{"test_causal_conv_with_state_fp16", 2, 2},
{"test_causal_conv_with_state_fp16_expanded", 2, 2},
{"test_causal_conv_with_state_kernel_size_one", 2, 2},
{"test_causal_conv_with_state_kernel_size_one_expanded", 2, 2},
{"test_causal_conv_with_state_short_input_no_past_state", 2, 2},
{"test_causal_conv_with_state_short_input_no_past_state_expanded", 2, 2},
{"test_causal_conv_with_state_silu", 2, 2},
{"test_causal_conv_with_state_silu_expanded", 2, 2},
{"test_causal_conv_with_state_silu_fp16", 2, 2},
{"test_causal_conv_with_state_silu_fp16_expanded", 2, 2},
{"test_causal_conv_with_state_silu_with_past_state", 3, 2},
{"test_causal_conv_with_state_silu_with_past_state_expanded", 3, 2},
{"test_causal_conv_with_state_swish_alias", 2, 2},
{"test_causal_conv_with_state_swish_alias_expanded", 2, 2},
{"test_causal_conv_with_state_with_bias", 3, 2},
{"test_causal_conv_with_state_with_bias_and_past_state", 4, 2},
{"test_causal_conv_with_state_with_bias_and_past_state_expanded", 4, 2},
{"test_causal_conv_with_state_with_bias_expanded", 3, 2},
{"test_causal_conv_with_state_with_past_state", 3, 2},
{"test_causal_conv_with_state_with_past_state_expanded", 3, 2},
{"test_cumprod_1d", 2, 1},
{"test_cumprod_1d_exclusive", 2, 1},
{"test_cumprod_1d_int32_exclusive", 2, 1},
{"test_cumprod_1d_reverse", 2, 1},
{"test_cumprod_1d_reverse_exclusive", 2, 1},
{"test_cumprod_2d_axis_0", 2, 1},
{"test_cumprod_2d_axis_1", 2, 1},
{"test_cumprod_2d_int32", 2, 1},
{"test_cumprod_2d_negative_axis", 2, 1},
{"test_dequantizelinear_int2", 3, 1},
{"test_dequantizelinear_uint2", 3, 1},
{"test_dft_irfft", 2, 1},
{"test_dft_irfft_opset19", 1, 1},
{"test_dft_rfft", 2, 1},
{"test_dft_rfft_opset19", 1, 1},
{"test_div_int32_trunc", 2, 1},
{"test_flexattention", 3, 1},
{"test_flexattention_causal_mask", 3, 1},
{"test_flexattention_causal_mask_expanded_ver26", 3, 1},
{"test_flexattention_diff_head_sizes", 3, 1},
{"test_flexattention_diff_head_sizes_expanded_ver26", 3, 1},
{"test_flexattention_double", 3, 1},
{"test_flexattention_double_expanded_ver26", 3, 1},
{"test_flexattention_expanded_ver26", 3, 1},
{"test_flexattention_fp16", 3, 1},
{"test_flexattention_fp16_expanded_ver26", 3, 1},
{"test_flexattention_gqa", 3, 1},
{"test_flexattention_gqa_expanded_ver26", 3, 1},
{"test_flexattention_prob_mod", 3, 1},
{"test_flexattention_prob_mod_expanded_ver26", 3, 1},
{"test_flexattention_relative_positional", 3, 1},
{"test_flexattention_relative_positional_expanded_ver26", 3, 1},
{"test_flexattention_scaled", 3, 1},
{"test_flexattention_scaled_expanded_ver26", 3, 1},
{"test_flexattention_score_mod", 3, 1},
{"test_flexattention_score_mod_expanded_ver26", 3, 1},
{"test_flexattention_soft_cap", 3, 1},
{"test_flexattention_soft_cap_expanded_ver26", 3, 1},
{"test_linear_attention_decode_step", 6, 2},
{"test_linear_attention_decode_step_expanded", 6, 2},
{"test_linear_attention_delta", 4, 2},
{"test_linear_attention_delta_expanded", 4, 2},
{"test_linear_attention_explicit_scale", 5, 2},
{"test_linear_attention_explicit_scale_expanded", 5, 2},
{"test_linear_attention_fp16", 5, 2},
{"test_linear_attention_fp16_expanded", 5, 2},
{"test_linear_attention_gated", 4, 2},
{"test_linear_attention_gated_delta", 5, 2},
{"test_linear_attention_gated_delta_beta_scalar", 5, 2},
{"test_linear_attention_gated_delta_beta_scalar_expanded", 5, 2},
{"test_linear_attention_gated_delta_expanded", 5, 2},
{"test_linear_attention_gated_delta_gqa", 5, 2},
{"test_linear_attention_gated_delta_gqa_expanded", 5, 2},
{"test_linear_attention_gated_delta_mqa", 5, 2},
{"test_linear_attention_gated_delta_mqa_expanded", 5, 2},
{"test_linear_attention_gated_expanded", 4, 2},
{"test_linear_attention_gated_per_head_decay", 4, 2},
{"test_linear_attention_gated_per_head_decay_expanded", 4, 2},
{"test_linear_attention_linear", 3, 2},
{"test_linear_attention_linear_expanded", 3, 2},
{"test_linear_attention_linear_t1_no_past", 3, 2},
{"test_linear_attention_linear_t1_no_past_expanded", 3, 2},
{"test_linear_attention_no_past_explicit_zeros", 6, 2},
{"test_linear_attention_no_past_explicit_zeros_expanded", 6, 2},
{"test_linear_attention_prefill_with_past", 6, 2},
{"test_linear_attention_prefill_with_past_expanded", 6, 2},
{"test_matmul_1d_1d", 2, 1},
{"test_matmul_1d_3d", 2, 1},
{"test_matmul_4d_1d", 2, 1},
{"test_matmul_bcast", 2, 1},
{"test_nonmaxsuppression_iou_threshold_boundary", 5, 1},
{"test_quantizelinear_int2", 3, 1},
{"test_quantizelinear_uint2", 3, 1},
{"test_range_bfloat16_type_positive_delta", 3, 1},
{"test_range_bfloat16_type_positive_delta_expanded", 3, 1},
{"test_range_float16_type_positive_delta", 3, 1},
{"test_range_float16_type_positive_delta_expanded", 3, 1},
{"test_scan9_multi_state", 3, 3},
{"test_scan9_scalar", 2, 2},
{"test_scatter_elements_with_reduction_mul", 3, 1},
};
@@ -1784,18 +1937,6 @@ public:
#include "test_onnx_conformance_layer_filter_opencv_ocl_fp32_denylist.inl.hpp"
};
EngineType engine_forced =
(EngineType)utils::getConfigurationParameterSizeT(
"OPENCV_FORCE_DNN_ENGINE", ENGINE_AUTO);
if (engine_forced == ENGINE_CLASSIC) {
classic_deny_list = {
#include "test_onnx_conformance_layer_filter_opencv_classic_denylist.inl.hpp"
};
} else {
classic_deny_list = {};
}
#ifdef HAVE_HALIDE
halide_deny_list = {
#include "test_onnx_conformance_layer_filter__halide_denylist.inl.hpp"
@@ -1912,6 +2053,11 @@ TEST_P(Test_ONNX_conformance, Layer_Test)
default_l1 = std::max(default_l1, 2e-4);
default_lInf = std::max(default_lInf, 1e-3);
}
// fp16 CausalConvWithState keeps fp16 output precision (~8e-4 Inf) on fp32 targets.
if (name == "test_causal_conv_with_state_fp16" || name == "test_causal_conv_with_state_silu_fp16") {
default_l1 = std::max(default_l1, 2e-4);
default_lInf = std::max(default_lInf, 2e-3);
}
}
#ifdef HAVE_HALIDE
else if (backend == DNN_BACKEND_HALIDE)
@@ -1983,6 +2129,12 @@ TEST_P(Test_ONNX_conformance, Layer_Test)
default_l1 = std::max(default_l1, 2e-4);
default_lInf = std::max(default_lInf, 1e-3);
}
// fp16 CausalConvWithState keeps fp16 output precision (~8e-4 Inf) on fp32 targets
// (the layer falls back to the CPU path).
if (name == "test_causal_conv_with_state_fp16" || name == "test_causal_conv_with_state_silu_fp16") {
default_l1 = std::max(default_l1, 2e-4);
default_lInf = std::max(default_lInf, 2e-3);
}
}
#endif
else
@@ -146,4 +146,36 @@
"test_attention_4d_scaled",
"test_attention_4d_softcap",
"test_attention_4d_attn_mask_bool",
"test_attention_4d_attn_mask_bool_4d",
"test_attention_4d_attn_mask_bool_4d",
// New-engine CUDA: arithmetic ops lack integer (int16/uint16/uint32/uint64) support and
// standalone BatchNorm with non-const params; denylisted for now (pass on CPU, wrong on CUDA).
"test_add_int16",
"test_add_uint16",
"test_add_uint32",
"test_add_uint64",
"test_sub_int16",
"test_sub_uint16",
"test_sub_uint32",
"test_sub_uint64",
"test_mul_int16",
"test_mul_uint16",
"test_mul_uint32",
"test_mul_uint64",
"test_div_int16",
"test_div_uint16",
"test_div_uint32",
"test_div_uint64",
"test_max_int16",
"test_max_uint16",
"test_max_uint32",
"test_max_uint64",
"test_min_int16",
"test_min_uint16",
"test_min_uint32",
"test_min_uint64",
"test_mod_mixed_sign_int16",
"test_mod_uint16",
"test_mod_uint32",
"test_mod_uint64",
"test_batchnorm_epsilon",
"test_batchnorm_example",
@@ -82,4 +82,36 @@
"test_attention_4d_scaled",
"test_attention_4d_softcap",
"test_attention_4d_attn_mask_bool",
"test_attention_4d_attn_mask_bool_4d",
"test_attention_4d_attn_mask_bool_4d",
// New-engine CUDA: arithmetic ops lack integer (int16/uint16/uint32/uint64) support and
// standalone BatchNorm with non-const params; denylisted for now (pass on CPU, wrong on CUDA).
"test_add_int16",
"test_add_uint16",
"test_add_uint32",
"test_add_uint64",
"test_sub_int16",
"test_sub_uint16",
"test_sub_uint32",
"test_sub_uint64",
"test_mul_int16",
"test_mul_uint16",
"test_mul_uint32",
"test_mul_uint64",
"test_div_int16",
"test_div_uint16",
"test_div_uint32",
"test_div_uint64",
"test_max_int16",
"test_max_uint16",
"test_max_uint32",
"test_max_uint64",
"test_min_int16",
"test_min_uint16",
"test_min_uint32",
"test_min_uint64",
"test_mod_mixed_sign_int16",
"test_mod_uint16",
"test_mod_uint32",
"test_mod_uint64",
"test_batchnorm_epsilon",
"test_batchnorm_example",
@@ -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)
@@ -3326,6 +3330,96 @@ CASE(test_attention_4d_with_qk_matmul_softmax)
SKIP;
CASE(test_attention_4d_with_qk_matmul_softmax_expanded)
SKIP;
// ONNX 1.22 additions: unsupported on OpenVINO
CASE(test_dft_rfft)
SKIP;
CASE(test_nonmaxsuppression_iou_threshold_boundary)
SKIP;
CASE(test_attention_4d_softcap_neginf_mask_expanded)
SKIP;
CASE(test_attention_4d_softcap_neginf_mask_poison_expanded)
SKIP;
CASE(test_causal_conv_with_state_b1_c1_degenerate_expanded)
SKIP;
CASE(test_causal_conv_with_state_basic_expanded)
SKIP;
CASE(test_causal_conv_with_state_decode_step_expanded)
SKIP;
CASE(test_causal_conv_with_state_kernel_size_one_expanded)
SKIP;
CASE(test_causal_conv_with_state_short_input_no_past_state_expanded)
SKIP;
CASE(test_causal_conv_with_state_silu_expanded)
SKIP;
CASE(test_causal_conv_with_state_silu_with_past_state_expanded)
SKIP;
CASE(test_causal_conv_with_state_swish_alias_expanded)
SKIP;
CASE(test_causal_conv_with_state_with_bias_and_past_state_expanded)
SKIP;
CASE(test_causal_conv_with_state_with_bias_expanded)
SKIP;
CASE(test_causal_conv_with_state_with_past_state_expanded)
SKIP;
CASE(test_causal_conv_with_state_b1_c1_degenerate)
SKIP;
CASE(test_causal_conv_with_state_basic)
SKIP;
CASE(test_causal_conv_with_state_decode_step)
SKIP;
CASE(test_causal_conv_with_state_fp16)
SKIP;
CASE(test_causal_conv_with_state_kernel_size_one)
SKIP;
CASE(test_causal_conv_with_state_short_input_no_past_state)
SKIP;
CASE(test_causal_conv_with_state_silu)
SKIP;
CASE(test_causal_conv_with_state_silu_fp16)
SKIP;
CASE(test_causal_conv_with_state_silu_with_past_state)
SKIP;
CASE(test_causal_conv_with_state_swish_alias)
SKIP;
CASE(test_causal_conv_with_state_with_bias)
SKIP;
CASE(test_causal_conv_with_state_with_bias_and_past_state)
SKIP;
CASE(test_causal_conv_with_state_with_past_state)
SKIP;
CASE(test_cumprod_1d)
SKIP;
CASE(test_cumprod_1d_exclusive)
SKIP;
CASE(test_cumprod_1d_int32_exclusive)
SKIP;
CASE(test_cumprod_1d_reverse)
SKIP;
CASE(test_cumprod_1d_reverse_exclusive)
SKIP;
CASE(test_cumprod_2d_axis_0)
SKIP;
CASE(test_cumprod_2d_axis_1)
SKIP;
CASE(test_cumprod_2d_int32)
SKIP;
CASE(test_cumprod_2d_negative_axis)
SKIP;
CASE(test_flexattention_scaled_expanded_ver26)
SKIP;
CASE(test_range_bfloat16_type_positive_delta)
SKIP;
CASE(test_range_float16_type_positive_delta)
SKIP;
CASE(test_dft_rfft_opset19)
SKIP;
CASE(test_div_int32_trunc)
SKIP;
CASE(test_matmul_bcast)
SKIP;
CASE(test_scatter_elements_with_reduction_mul)
SKIP;
END_SWITCH()
#undef EOF_LABEL
#undef BEGIN_SWITCH
@@ -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",
@@ -916,3 +919,43 @@
"test_attention_4d_with_qk_matmul_expanded",
"test_attention_4d_with_qk_matmul_softmax",
"test_attention_4d_with_qk_matmul_softmax_expanded",
"test_dft_rfft",
"test_nonmaxsuppression_iou_threshold_boundary",
"test_attention_4d_softcap_neginf_mask_expanded",
"test_attention_4d_softcap_neginf_mask_poison_expanded",
"test_causal_conv_with_state_b1_c1_degenerate_expanded",
"test_causal_conv_with_state_basic_expanded",
"test_causal_conv_with_state_decode_step_expanded",
"test_causal_conv_with_state_kernel_size_one_expanded",
"test_causal_conv_with_state_short_input_no_past_state_expanded",
"test_causal_conv_with_state_silu_expanded",
"test_causal_conv_with_state_silu_with_past_state_expanded",
"test_causal_conv_with_state_swish_alias_expanded",
"test_causal_conv_with_state_with_bias_and_past_state_expanded",
"test_causal_conv_with_state_with_bias_expanded",
"test_causal_conv_with_state_with_past_state_expanded",
"test_causal_conv_with_state_b1_c1_degenerate",
"test_causal_conv_with_state_basic",
"test_causal_conv_with_state_decode_step",
"test_causal_conv_with_state_fp16",
"test_causal_conv_with_state_kernel_size_one",
"test_causal_conv_with_state_short_input_no_past_state",
"test_causal_conv_with_state_silu",
"test_causal_conv_with_state_silu_fp16",
"test_causal_conv_with_state_silu_with_past_state",
"test_causal_conv_with_state_swish_alias",
"test_causal_conv_with_state_with_bias",
"test_causal_conv_with_state_with_bias_and_past_state",
"test_causal_conv_with_state_with_past_state",
"test_cumprod_1d",
"test_cumprod_1d_exclusive",
"test_cumprod_1d_int32_exclusive",
"test_cumprod_1d_reverse",
"test_cumprod_1d_reverse_exclusive",
"test_cumprod_2d_axis_0",
"test_cumprod_2d_axis_1",
"test_cumprod_2d_int32",
"test_cumprod_2d_negative_axis",
"test_flexattention_scaled_expanded_ver26",
"test_range_bfloat16_type_positive_delta",
"test_range_float16_type_positive_delta",
@@ -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",
@@ -331,3 +331,122 @@
"test_training_dropout_mask", // ---- same as above ---
"test_training_dropout_zero_ratio_mask", // ---- same as above ---
"test_unique_length_1", //incorrect output
// ===== ONNX 1.22 additions: ops/dtypes not yet supported by the importer =====
// BitCast op not supported by the ONNX importer
"test_bitcast_2d_float32_to_int32",
"test_bitcast_bool_to_uint8",
"test_bitcast_float32_to_int32",
"test_bitcast_float64_to_int64",
"test_bitcast_int32_to_float32",
"test_bitcast_int64_to_float64",
"test_bitcast_int8_to_uint8",
"test_bitcast_scalar_float32_to_int32",
"test_bitcast_uint16_to_int16",
"test_bitcast_uint32_to_int32",
// INT2/UINT2 (2-bit) dtype not supported
"test_cast_FLOAT16_to_INT2",
"test_cast_FLOAT16_to_UINT2",
"test_cast_FLOAT_to_INT2",
"test_cast_FLOAT_to_UINT2",
"test_cast_INT2_to_FLOAT",
"test_cast_INT2_to_FLOAT16",
"test_cast_INT2_to_INT8",
"test_cast_UINT2_to_FLOAT",
"test_cast_UINT2_to_FLOAT16",
"test_cast_UINT2_to_UINT8",
"test_castlike_FLOAT16_to_INT2",
"test_castlike_FLOAT16_to_INT2_expanded",
"test_castlike_FLOAT16_to_UINT2",
"test_castlike_FLOAT16_to_UINT2_expanded",
"test_castlike_FLOAT_to_INT2",
"test_castlike_FLOAT_to_INT2_expanded",
"test_castlike_FLOAT_to_UINT2",
"test_castlike_FLOAT_to_UINT2_expanded",
"test_castlike_INT2_to_FLOAT",
"test_castlike_INT2_to_FLOAT16",
"test_castlike_INT2_to_FLOAT16_expanded",
"test_castlike_INT2_to_FLOAT_expanded",
"test_castlike_INT2_to_INT8",
"test_castlike_INT2_to_INT8_expanded",
"test_castlike_UINT2_to_FLOAT",
"test_castlike_UINT2_to_FLOAT16",
"test_castlike_UINT2_to_FLOAT16_expanded",
"test_castlike_UINT2_to_FLOAT_expanded",
"test_castlike_UINT2_to_UINT8",
"test_castlike_UINT2_to_UINT8_expanded",
"test_dequantizelinear_int2",
"test_dequantizelinear_uint2",
"test_quantizelinear_int2",
"test_quantizelinear_uint2",
// FlexAttention op not supported
"test_flexattention",
"test_flexattention_causal_mask",
"test_flexattention_diff_head_sizes",
"test_flexattention_double",
"test_flexattention_fp16",
"test_flexattention_gqa",
"test_flexattention_prob_mod",
"test_flexattention_relative_positional",
"test_flexattention_scaled",
"test_flexattention_score_mod",
"test_flexattention_soft_cap",
// LinearAttention op not supported
"test_linear_attention_decode_step",
"test_linear_attention_decode_step_expanded",
"test_linear_attention_delta",
"test_linear_attention_delta_expanded",
"test_linear_attention_explicit_scale",
"test_linear_attention_explicit_scale_expanded",
"test_linear_attention_fp16",
"test_linear_attention_fp16_expanded",
"test_linear_attention_gated",
"test_linear_attention_gated_delta",
"test_linear_attention_gated_delta_beta_scalar",
"test_linear_attention_gated_delta_beta_scalar_expanded",
"test_linear_attention_gated_delta_expanded",
"test_linear_attention_gated_delta_gqa",
"test_linear_attention_gated_delta_gqa_expanded",
"test_linear_attention_gated_delta_mqa",
"test_linear_attention_gated_delta_mqa_expanded",
"test_linear_attention_gated_expanded",
"test_linear_attention_gated_per_head_decay",
"test_linear_attention_gated_per_head_decay_expanded",
"test_linear_attention_linear",
"test_linear_attention_linear_expanded",
"test_linear_attention_linear_t1_no_past",
"test_linear_attention_linear_t1_no_past_expanded",
"test_linear_attention_no_past_explicit_zeros",
"test_linear_attention_no_past_explicit_zeros_expanded",
"test_linear_attention_prefill_with_past",
"test_linear_attention_prefill_with_past_expanded",
// misc unsupported (expanded subgraphs / new ops)
"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ_expanded",
"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FN_expanded",
"test_range_bfloat16_type_positive_delta_expanded",
"test_range_float16_type_positive_delta_expanded",
// ===== ONNX 1.22 additions: forward/accuracy not yet supported =====
// MatMul with 1-D operand not supported (requires >=2D)
"test_matmul_1d_1d",
"test_matmul_1d_3d",
"test_matmul_4d_1d",
// DFT inverse RFFT not supported
"test_dft_irfft",
"test_dft_irfft_opset19",
// Attention softcap accuracy
"test_attention_4d_softcap_neginf_mask",
"test_attention_4d_softcap_neginf_mask_poison",
// CausalConvWithState fp16 (expanded) accuracy
"test_causal_conv_with_state_fp16_expanded",
"test_causal_conv_with_state_silu_fp16_expanded",
// FlexAttention (expanded) accuracy
"test_flexattention_causal_mask_expanded_ver26",
"test_flexattention_diff_head_sizes_expanded_ver26",
"test_flexattention_double_expanded_ver26",
"test_flexattention_expanded_ver26",
"test_flexattention_fp16_expanded_ver26",
"test_flexattention_gqa_expanded_ver26",
"test_flexattention_prob_mod_expanded_ver26",
"test_flexattention_relative_positional_expanded_ver26",
"test_flexattention_score_mod_expanded_ver26",
"test_flexattention_soft_cap_expanded_ver26",
+12 -16
View File
@@ -217,14 +217,6 @@ public:
// output against an in-test attention reference computed from the same inputs.
void testSDPAModel(const String& basename, double l1, double lInf)
{
// SDPA is only handled by the new-engine ONNX importer.
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC) {
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
Mat Q = blobFromNPY(_tf("data/input_" + basename + "_0.npy"));
Mat KT = blobFromNPY(_tf("data/input_" + basename + "_1.npy"));
Mat V = blobFromNPY(_tf("data/input_" + basename + "_2.npy"));
@@ -1151,6 +1143,9 @@ TEST_P(Test_ONNX_layers, MatMul_init_2)
}
TEST_P(Test_ONNX_layers, MatMul_init_bcast)
{
// New-engine CUDA MatMul/GEMM does not yet cover this broadcast variant; skip for now.
if (backend == DNN_BACKEND_CUDA)
applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA, CV_TEST_TAG_DNN_SKIP_CUDA_FP16);
testONNXModels("matmul_init_bcast");
}
@@ -1170,6 +1165,9 @@ TEST_P(Test_ONNX_layers, MatMulAdd)
#endif
if (backend == DNN_BACKEND_OPENCV && target == DNN_TARGET_OPENCL_FP16)
applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCL_FP16);
// New-engine CUDA MatMul/GEMM does not yet cover this fused-add variant; skip for now.
if (backend == DNN_BACKEND_CUDA)
applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA, CV_TEST_TAG_DNN_SKIP_CUDA_FP16);
testONNXModels("matmul_add");
}
@@ -2323,14 +2321,6 @@ TEST_P(Test_ONNX_layers, Gemm_External_Data)
TEST_P(Test_ONNX_layers, Quantized_MatMul_Variable_Weights)
{
auto engine_forced = static_cast<cv::dnn::EngineType>(
cv::utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", cv::dnn::ENGINE_AUTO));
if (engine_forced == cv::dnn::ENGINE_CLASSIC)
{
applyTestTag(CV_TEST_TAG_DNN_SKIP_PARSER);
return;
}
testONNXModels("quantized_matmul_variable_inputs", npy, 1.3, 1.3);
}
@@ -2749,6 +2739,9 @@ TEST_P(Test_ONNX_nets, LResNet100E_IR)
#endif
CV_TEST_TAG_DEBUG_VERYLONG
);
// New-engine CUDA lacks support for some layers in this net; skip for now.
if (backend == DNN_BACKEND_CUDA)
applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA, CV_TEST_TAG_DNN_SKIP_CUDA_FP16);
if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019)
{
if (target == DNN_TARGET_OPENCL_FP16) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER);
@@ -3617,6 +3610,9 @@ TEST_P(Test_ONNX_layers, LayerNormNoFusion) {
}
TEST_P(Test_ONNX_layers, MatMulAddFusion) {
// New-engine CUDA MatMul/GEMM does not yet cover this fused variant; skip for now.
if (backend == DNN_BACKEND_CUDA)
applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA, CV_TEST_TAG_DNN_SKIP_CUDA_FP16);
double l1 = (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_OPENCL) ? 0.0018 : default_l1;
double lInf = (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_OPENCL) ? 0.011 : default_lInf;
testONNXModels("biased_matmul", npy, l1, lInf);
+1 -1
View File
@@ -1813,7 +1813,7 @@ TEST_P(Test_TensorFlow_nets, Mask_RCNN)
outNames[0] = "detection_out_final";
outNames[1] = "detection_masks";
Net net = readNetFromTensorflow(model, proto, ENGINE_AUTO, outNames);
Net net = readNetFromTensorflow(model, proto, ENGINE_OPENCV, outNames);
Mat refDetections = blobFromNPY(path("mask_rcnn_inception_v2_coco_2018_01_28.detection_out.npy"));
Mat refMasks = blobFromNPY(path("mask_rcnn_inception_v2_coco_2018_01_28.detection_masks.npy"));
Mat blob = blobFromImage(img, 1.0f, Size(800, 800), Scalar(), true, false);
+18 -1
View File
@@ -859,7 +859,7 @@ public:
CV_WRAP Params();
CV_PROP_RW Size inputSize; //!< Input image size for the network, default 640x640
CV_PROP_RW bool normalizeDescriptors; //!< Whether to L2-normalize descriptors, default true
CV_PROP_RW int engine; //!< DNN engine type (dnn::EngineType), default ENGINE_NEW
CV_PROP_RW int engine; //!< DNN engine type (dnn::EngineType), default ENGINE_AUTO
CV_PROP_RW int backend; //!< DNN backend, default DNN_BACKEND_DEFAULT
CV_PROP_RW int target; //!< DNN target, default DNN_TARGET_CPU
};
@@ -998,6 +998,19 @@ public:
*/
CV_WRAP virtual bool isMaskSupported() const = 0;
/** @brief Provides keypoint and image-size context for matchers that need it (e.g. LightGlueMatcher).
Must be called before match()/knnMatch()/radiusMatch() for matchers that require this context.
Matchers that don't need it (e.g. BFMatcher, FlannBasedMatcher) ignore the call.
@param queryKpts Query image keypoints.
@param trainKpts Train image keypoints.
@param queryImageSize Size of the query image (width, height).
@param trainImageSize Size of the train image (width, height).
*/
CV_WRAP virtual void setImagePairInfo(const std::vector<KeyPoint>& queryKpts, const std::vector<KeyPoint>& trainKpts,
Size queryImageSize = Size(), Size trainImageSize = Size());
/** @brief Trains a descriptor matcher
Trains a descriptor matcher (for example, the flann index). In all methods to match, the method
@@ -1349,6 +1362,10 @@ public:
/** @brief Clears stored pair context information.
*/
CV_WRAP virtual void clearPairInfo() = 0;
/** @brief Convenience overload of setPairInfo() taking keypoints directly. */
CV_WRAP void setImagePairInfo(const std::vector<KeyPoint>& queryKpts, const std::vector<KeyPoint>& trainKpts,
Size queryImageSize = Size(), Size trainImageSize = Size()) CV_OVERRIDE;
};
//! @} features_match
+1 -1
View File
@@ -20,7 +20,7 @@ ALIKED::Params::Params()
inputSize = Size(640, 640);
normalizeDescriptors = true;
#ifdef HAVE_OPENCV_DNN
engine = dnn::ENGINE_NEW;
engine = dnn::ENGINE_AUTO;
backend = dnn::DNN_BACKEND_DEFAULT;
target = dnn::DNN_TARGET_CPU;
#else
+4
View File
@@ -580,6 +580,10 @@ bool DescriptorMatcher::empty() const
void DescriptorMatcher::train()
{}
void DescriptorMatcher::setImagePairInfo( const std::vector<KeyPoint>&, const std::vector<KeyPoint>&,
Size, Size )
{}
void DescriptorMatcher::match( InputArray queryDescriptors, InputArray trainDescriptors,
std::vector<DMatch>& matches, InputArray mask ) const
{

Some files were not shown because too many files have changed in this diff Show More