mirror of
https://github.com/opencv/opencv.git
synced 2026-07-31 00:03:03 +04:00
Compare commits
81 Commits
8d69623051
..
5.x
| Author | SHA1 | Date | |
|---|---|---|---|
| 9548c7ea4e | |||
| 0f83d516bf | |||
| ccfa3a733b | |||
| ba5b40b1a2 | |||
| 3ef693c48a | |||
| e0e52c14b1 | |||
| 34c478016d | |||
| 58c28e1e82 | |||
| 059a93339c | |||
| 12fb9a6c24 | |||
| 7f63fede4c | |||
| e7dc3a9a9b | |||
| 0843a0bb17 | |||
| 2089d7fdb1 | |||
| dd3f3f6d9d | |||
| c48e3f7f3e | |||
| bc5c1b4da8 | |||
| cab931296f | |||
| 72ffdfc170 | |||
| 204725ea18 | |||
| a5e4c04903 | |||
| 8e4e45eac3 | |||
| b94079a214 | |||
| 62732ab966 | |||
| 0a7288695f | |||
| f3a8f654c3 | |||
| 51f7547bf1 | |||
| 57bcf14e78 | |||
| c2e0e4edb9 | |||
| 940810b589 | |||
| 0bc2636564 | |||
| 2c67582020 | |||
| ad8056f9b0 | |||
| 46331314a5 | |||
| 49b8f7c03f | |||
| 36d8bc4a44 | |||
| e1f1495faa | |||
| 7a25b1986d | |||
| 53d308c2a5 | |||
| 4ccee4a65f | |||
| 6e70e5d2a9 | |||
| 4ba2752f36 | |||
| 9fcdbaa8b5 | |||
| 18b39f1f34 | |||
| 73c580723c | |||
| fe482bd575 | |||
| 859d550029 | |||
| b83e561526 | |||
| 17d63dddf6 | |||
| b671e1e56f | |||
| 0d88e1933a | |||
| e247376ee2 | |||
| b41cda2b93 | |||
| c3d4337abb | |||
| aacac3d349 | |||
| 864f9594ad | |||
| a1084efef2 | |||
| 4bace2fc28 | |||
| 9c487708f9 | |||
| 8308fbf9ba | |||
| 325e30db1d | |||
| 757c40c81a | |||
| 1af832250b | |||
| c219bb783d | |||
| eccd1c43c4 | |||
| c42778a0e8 | |||
| 6b04d456b3 | |||
| 7cc9947809 | |||
| dcc3daf76b | |||
| 319a03a2c9 | |||
| fd4491fcb1 | |||
| ae392712b1 | |||
| ccbc31a0f1 | |||
| 4ef179b4bf | |||
| f552e8ac82 | |||
| 747a71fdfe | |||
| bf5224cfa8 | |||
| a02eb6ad0f | |||
| 65c93e673d | |||
| 08077a00a1 | |||
| 72b3fea7dd |
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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 ¶ms);
|
||||
};
|
||||
|
||||
class CV_EXPORTS CausalConvWithStateLayer : public Layer {
|
||||
public:
|
||||
static Ptr<CausalConvWithStateLayer> create(const LayerParams ¶ms);
|
||||
};
|
||||
|
||||
class CV_EXPORTS CumProdLayer : public Layer {
|
||||
public:
|
||||
static Ptr<CumProdLayer> create(const LayerParams ¶ms);
|
||||
};
|
||||
|
||||
class CV_EXPORTS GroupNormLayer : public Layer {
|
||||
public:
|
||||
static Ptr<GroupNormLayer> create(const LayerParams ¶ms);
|
||||
|
||||
@@ -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 ¶ms); //!< Initializes only #name, #type and #blobs fields.
|
||||
void setParamsFrom(const LayerParams ¶ms); //!< 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 ¶ms) \
|
||||
{ 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 ¶ms)
|
||||
return Ptr<Layer>(LayerClass::create(params));
|
||||
}
|
||||
|
||||
template<typename OpClass>
|
||||
Ptr<LayerInfo> _opDynamicRegisterer(const LayerParams ¶ms)
|
||||
{
|
||||
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
|
||||
{
|
||||
|
||||
@@ -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].
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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++) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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>();
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_)
|
||||
{
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user