diff --git a/modules/dnn/src/layers/cast2_layer.cpp b/modules/dnn/src/layers/cast2_layer.cpp index b9370849f1..17f995cdd4 100644 --- a/modules/dnn/src/layers/cast2_layer.cpp +++ b/modules/dnn/src/layers/cast2_layer.cpp @@ -8,6 +8,9 @@ #include "layers_common.hpp" #include "../net_impl.hpp" +#include "opencv-onnx.pb.h" +#include "../onnx/onnx_dtype_convert.hpp" + namespace cv { namespace dnn { // ONNX Cast operator @@ -19,13 +22,18 @@ namespace cv { namespace dnn { namespace { - // ONNX Cast float->int truncates toward zero; Mat::convertTo rounds. Truncate to match the spec. template inline void truncateToIntImpl(const Mat& src, Mat& dst) { const int n = (int)src.total() * src.channels(); DT* d = dst.ptr
(); - if (src.depth() == CV_32F) + if (src.depth() == CV_16F) + { + const hfloat* s = src.ptr(); + for (int i = 0; i < n; ++i) + d[i] = saturate_cast
(std::trunc((float)s[i])); + } + else if (src.depth() == CV_32F) { const float* s = src.ptr(); for (int i = 0; i < n; ++i) @@ -53,87 +61,6 @@ namespace } } - inline void castQuantized(const Mat& src, Mat& dst, int targetDepth) - { - if (targetDepth == CV_16F) - { - CV_Assert(dst.depth() == CV_32F); - if (src.depth() == CV_32F) - { - MatConstIterator_ sIt = src.begin(), sEnd = src.end(); - MatIterator_ dIt = dst.begin(); - for (; sIt != sEnd; ++sIt, ++dIt) - { - *dIt = (float)hfloat(*sIt); - } - } - else if (src.depth() == CV_64F) - { - MatConstIterator_ sIt = src.begin(), sEnd = src.end(); - MatIterator_ dIt = dst.begin(); - for (; sIt != sEnd; ++sIt, ++dIt) - { - float v = (float)*sIt; - *dIt = (float)hfloat(v); - } - } - else - { - Mat src32; src.convertTo(src32, CV_32F); - MatConstIterator_ sIt = src32.begin(), sEnd = src32.end(); - MatIterator_ dIt = dst.begin(); - for (; sIt != sEnd; ++sIt, ++dIt) - { - *dIt = (float)hfloat(*sIt); - } - } - return; - } - - if (targetDepth == CV_16BF) - { - const int ddepth = dst.depth(); - if (!(ddepth == CV_16BF || ddepth == CV_16U)) - { - CV_Error(Error::StsNotImplemented, "Unsupported destination depth for BF16 cast"); - } - - Mat dst_bits(dst.size(), CV_MAKETYPE(CV_16U, dst.channels()), dst.data, dst.step); - - const Mat* src32p; - Mat src32; - if (src.depth() == CV_32F) - src32p = &src; - else - { - src.convertTo(src32, CV_32F); - src32p = &src32; - } - - const int rows = src32p->rows; - const int cols_x_cn = src32p->cols * src32p->channels(); - for (int r = 0; r < rows; ++r) - { - const float* in = src32p->ptr(r); - ushort* out = dst_bits.ptr(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]; - 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; - } - src.convertTo(dst, dst.depth()); - } } class Cast2LayerImpl CV_FINAL : public Cast2Layer @@ -144,10 +71,13 @@ public: setParamsFrom(params); hasToParam = false; toCvDepth_ = -1; + toOnnxType_ = -1; + saturate_ = params.get("saturate", 1) != 0; if (params.has("to")) { hasToParam = true; - toCvDepth_ = mapToCvDepth(params.get("to")); + toOnnxType_ = params.get("to"); + toCvDepth_ = mapToCvDepth(toOnnxType_); } else if (params.has("outputType")) { @@ -167,6 +97,9 @@ public: virtual bool supportBackend(int backendId) CV_OVERRIDE { + // Exotic dtypes (FP8/FP4/INT4/UINT4) are handled on the CPU path only. + if (onnx_dtype::isExotic(toOnnxType_)) + return backendId == DNN_BACKEND_OPENCV; return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; } @@ -181,6 +114,18 @@ public: return false; } + // Half targets are stored as FP32 unless native FP16 is enabled; forward() still rounds. + int resolveStorageDepth(int targetDepth, bool exotic) const + { + if (!exotic && (targetDepth == CV_16F || targetDepth == CV_16BF)) + { + Net::Impl* ni = getNetImpl(const_cast(this)); + if (ni && !ni->enableFP16) + return CV_32F; + } + return targetDepth; + } + virtual void getTypes(const std::vector& inputs, const int requiredOutputs, const int requiredInternals, @@ -216,9 +161,8 @@ public: const int in0Type = inputs[0]; const int in0CN = in0Type >= 0 ? CV_MAT_CN(in0Type) : 1; - int planDepth = targetDepth; - if (planDepth == CV_16F) planDepth = CV_32F; - const int outType = CV_MAKETYPE(planDepth, in0CN); + const bool exotic = hasToParam && onnx_dtype::isExotic(toOnnxType_); + const int outType = CV_MAKETYPE(resolveStorageDepth(targetDepth, exotic), in0CN); outputs.assign(1, outType); } @@ -227,6 +171,9 @@ public: { std::vector inputs, outputs; + if (hasToParam && onnx_dtype::isExotic(toOnnxType_)) + return false; // exotic conversions run on the CPU path + inputs_.getUMatVector(inputs); outputs_.getUMatVector(outputs); CV_CheckEQ(inputs.size(), (size_t)1, ""); @@ -305,46 +252,42 @@ public: } CV_CheckGE(runtimeTargetDepth, 0, "Cast: failed to resolve target data type at runtime"); - int plannedDDepth = (runtimeTargetDepth == CV_16F) ? CV_32F : runtimeTargetDepth; - if (dst0.depth() != plannedDDepth) - dst0.create(dst0.size(), CV_MAKETYPE(plannedDDepth, src0.channels())); + const bool exotic = hasToParam && onnx_dtype::isExotic(toOnnxType_); + const int storeDepth = resolveStorageDepth(runtimeTargetDepth, exotic); + if (dst0.depth() != storeDepth) + dst0.create(dst0.size(), CV_MAKETYPE(storeDepth, src0.channels())); Mat src = src0; Mat dst = dst0; + if (exotic) + { + castExotic(src, dst, toOnnxType_, saturate_); + return; + } + + // Cast to half yields half-representable values even when FP32 carries them. + if (storeDepth != runtimeTargetDepth && + (runtimeTargetDepth == CV_16F || runtimeTargetDepth == CV_16BF)) + { + Mat half; + src.convertTo(half, runtimeTargetDepth); + half.convertTo(dst, storeDepth); + return; + } + const int sdepth = src.depth(); const int ddepth = dst.depth(); - if (sdepth == runtimeTargetDepth && !(runtimeTargetDepth == CV_16F && ddepth == CV_32F)) + if (sdepth == ddepth && sdepth == runtimeTargetDepth) { src0.copyTo(dst0); return; } - if (runtimeTargetDepth == CV_16BF && (ddepth == CV_16BF || ddepth == CV_16U)) + if ((sdepth == CV_16F || sdepth == CV_32F || sdepth == CV_64F) && CV_IS_INT_TYPE(ddepth)) { - castQuantized(src, dst, CV_16BF); - } - else if (sdepth == CV_16BF) - { - src.convertTo(dst, ddepth); - } - else if (runtimeTargetDepth == CV_16F && ddepth == CV_32F) - { - castQuantized(src, dst, CV_16F); - } - else if (runtimeTargetDepth == CV_64F && ddepth != CV_64F) - { - if (ddepth == CV_16U || ddepth == CV_16BF) - { - castQuantized(src, dst, CV_16BF); - } - else - src.convertTo(dst, ddepth); - } - else if ((sdepth == CV_32F || sdepth == CV_64F) && CV_IS_INT_TYPE(ddepth)) - { - truncateFloatToInt(src, dst); + truncateFloatToInt(src, dst); // ONNX float->int truncates toward zero } else { @@ -374,9 +317,67 @@ public: } #endif // HAVE_DNN_NGRAPH + void castExotic(const Mat& src, Mat& dst, int onnxType, bool saturate) + { + const int sdepth = src.depth(); + const float* sf = (sdepth == CV_32F) ? src.ptr() : nullptr; + const hfloat* sh = (sdepth == CV_16F) ? src.ptr() : nullptr; + Mat src32; + if (!sf && !sh) { src.convertTo(src32, CV_32F); sf = src32.ptr(); } + const size_t total = src.total() * src.channels(); + #define CV_DNN_SRC_F(i) (sf ? sf[i] : (float)sh[i]) + + if (onnx_dtype::isFp8(onnxType)) + { + const onnx_dtype::Fp8Fmt fmt = onnx_dtype::fp8FmtFor(onnxType); + const int ddepth = dst.depth(); + if (ddepth == CV_8F_E4M3FN || ddepth == CV_8F_E4M3FNUZ) + { + // Store the ONNX-encoded byte: core's E4M3 encode rounds differently. + uchar* d = dst.ptr(); + for (size_t i = 0; i < total; i++) + d[i] = onnx_dtype::f32ToFp8(CV_DNN_SRC_F(i), fmt, saturate); + } + else + { + // E5M2/E5M2FNUZ have no native depth: round onto the FP8 grid, keep CV_16F. + hfloat* d = dst.ptr(); + for (size_t i = 0; i < total; i++) + d[i] = hfloat(onnx_dtype::fp8ToF32(onnx_dtype::f32ToFp8(CV_DNN_SRC_F(i), fmt, saturate), fmt)); + } + } + else if (onnxType == onnx_dtype::ONNX_FLOAT8E8M0) + { + float* d = dst.ptr(); // E8M0 range exceeds FP16, stays CV_32F + for (size_t i = 0; i < total; i++) + d[i] = onnx_dtype::e8m0ToF32(onnx_dtype::f32ToE8M0(CV_DNN_SRC_F(i))); + } + else if (onnxType == opencv_onnx::TensorProto_DataType_FLOAT4E2M1) + { + hfloat* d = dst.ptr(); + for (size_t i = 0; i < total; i++) + d[i] = hfloat(onnx_dtype::fp4ToF32(onnx_dtype::f32ToFp4(CV_DNN_SRC_F(i)))); + } + else if (onnx_dtype::isInt4(onnxType)) + { + schar* d = dst.ptr(); + for (size_t i = 0; i < total; i++) + d[i] = onnx_dtype::f32ToInt4(CV_DNN_SRC_F(i)); + } + else // UINT4 + { + uchar* d = dst.ptr(); + for (size_t i = 0; i < total; i++) + d[i] = onnx_dtype::f32ToUint4(CV_DNN_SRC_F(i)); + } + #undef CV_DNN_SRC_F + } + private: bool hasToParam = false; int toCvDepth_ = -1; + int toOnnxType_ = -1; + bool saturate_ = true; // ONNX TensorProto::DataType values (see opencv-onnx.proto); the 'to' // attribute stores the raw ONNX value. Fixed by the ONNX specification, @@ -398,6 +399,11 @@ private: static int mapToCvDepth(int v) { + if (v == onnx_dtype::ONNX_FLOAT8E8M0) return CV_32F; // range exceeds FP16 + if (onnx_dtype::isFp8Native(v)) return onnx_dtype::fp8NativeDepth(v); // E4M3FN/E4M3FNUZ + if (onnx_dtype::isExoticFloat(v)) return CV_16F; // E5M2/FP4 + if (onnx_dtype::isInt4(v)) return CV_8S; + if (onnx_dtype::isUint4(v)) return CV_8U; switch (v) { case ONNX_DT_FLOAT: return CV_32F; diff --git a/modules/dnn/src/layers/gemm_layer.cpp b/modules/dnn/src/layers/gemm_layer.cpp index 8634aaed5a..c0273abf46 100644 --- a/modules/dnn/src/layers/gemm_layer.cpp +++ b/modules/dnn/src/layers/gemm_layer.cpp @@ -69,6 +69,14 @@ public: have_bias = params.get("have_bias", false); real_ndims_C = params.get("real_ndims_C", -1); + + for (Mat& blob : blobs) { + if (blob.type() == CV_16F || blob.type() == CV_16BF) { + Mat widened; + blob.convertTo(widened, CV_32F); + blob = widened; + } + } } virtual bool supportBackend(int backendId) CV_OVERRIDE { diff --git a/modules/dnn/src/layers/matmul_layer.cpp b/modules/dnn/src/layers/matmul_layer.cpp index 5b4732446e..f060c86c67 100644 --- a/modules/dnn/src/layers/matmul_layer.cpp +++ b/modules/dnn/src/layers/matmul_layer.cpp @@ -42,6 +42,14 @@ class MatMulLayerImpl CV_FINAL : public MatMulLayer { beta = params.get("beta", 1.f); real_ndims_C = params.get("real_ndims_C", -1); + + for (Mat& blob : blobs) { + if (blob.type() == CV_16F || blob.type() == CV_16BF) { + Mat widened; + blob.convertTo(widened, CV_32F); + blob = widened; + } + } } virtual bool supportBackend(int backendId) CV_OVERRIDE { diff --git a/modules/dnn/src/net_impl.hpp b/modules/dnn/src/net_impl.hpp index 2dff534b37..37b1f3ef79 100644 --- a/modules/dnn/src/net_impl.hpp +++ b/modules/dnn/src/net_impl.hpp @@ -139,6 +139,7 @@ struct Net::Impl : public detail::NetImplBase KVCacheManager kvCacheManager; Ptr mainGraph; + std::vector mainGraphOutTypes; int globGraphIdx; int accuracy; @@ -551,6 +552,8 @@ struct Net::Impl : public detail::NetImplBase void fuseScaleSoftmax(); // replace constant sub-expressions with their results + // widen FP16/BF16 constants to execution precision while the engine lacks half kernels + void widenHalfConstants(); void fuseQDQ(); void constFold(); // make some operations (activation, batch norm, convolution) unary if diff --git a/modules/dnn/src/net_impl2.cpp b/modules/dnn/src/net_impl2.cpp index d8285ec69c..928bd8cba7 100644 --- a/modules/dnn/src/net_impl2.cpp +++ b/modules/dnn/src/net_impl2.cpp @@ -552,6 +552,29 @@ Ptr Net::Impl::newGraph(const std::string& name_, const std::vector& return graph; } +// No half kernels yet, so half constants are widened just as setGraphInput() widens inputs. +void Net::Impl::widenHalfConstants() +{ + if (enableFP16) + return; + size_t nargs = args.size(); + __tensors__.resize(nargs); + for (size_t i = 1; i < nargs; i++) { + ArgData& adata = args[i]; + if (adata.kind != DNN_ARG_CONST || + (adata.type != CV_16F && adata.type != CV_16BF)) + continue; + Mat& t = __tensors__[i]; + if (!t.empty()) { + Mat widened; + widened.fit(t.shape(), accuracy); + t.convertTo(widened, accuracy); + t = widened; + } + adata.type = accuracy; + } +} + void Net::Impl::prepareForInference() { #ifdef HAVE_ONNXRUNTIME @@ -563,6 +586,7 @@ void Net::Impl::prepareForInference() #endif if (!prepared) { + widenHalfConstants(); fuseQDQ(); constFold(); fuseBN(); @@ -1266,11 +1290,10 @@ void Net::Impl::setGraphInput(Ptr& graph, size_t idx, const Mat& m) if ((adata_type == CV_16F || adata_type == CV_16BF) && !enableFP16) adata_type = CV_32F; - if (adata_type != mtype && - !((adata_type == CV_64F || adata_type == CV_32F || adata_type == CV_16F || adata_type == CV_16BF) && - (mtype == CV_64F || mtype == CV_32F || mtype == CV_16F || mtype == CV_16BF)) && - !((adata_type == CV_8U || adata_type == CV_8S || adata_type == CV_16U || adata_type == CV_16S || adata_type == CV_32S || adata_type == CV_32U || adata_type == CV_64S || adata_type == CV_64U) && - (mtype == CV_8U || mtype == CV_8S || mtype == CV_16U || mtype == CV_16S || mtype == CV_32S || mtype == CV_32U || mtype == CV_64S || mtype == CV_64U)) && + // setInput converts to the declared type, so any numeric source type is acceptable. + const bool aNumeric = CV_IS_INT_TYPE(adata_type) || CV_IS_FLOAT_TYPE(adata_type); + const bool mNumeric = CV_IS_INT_TYPE(mtype) || CV_IS_FLOAT_TYPE(mtype); + if (adata_type != mtype && !(aNumeric && mNumeric) && !(adata.type == CV_16BF && mtype == CV_16U) && !(adata.type == CV_16F && mtype == CV_16U) && !m.empty()) { @@ -1868,6 +1891,18 @@ void Net::Impl::forwardGraph(Ptr& graph, InputArrayOfArrays inputs_, outputsVec[i].fit(outm.shape(), outm.type()); outm.copyTo(outputsVec[i]); } + // Narrow to the declared output dtype when an op computed in a wider type. + // Half is excepted: the graph was widened, so narrowing would only lose precision. + int declaredOutType = i < mainGraphOutTypes.size() ? mainGraphOutTypes[i] : -1; + if (!enableFP16 && (declaredOutType == CV_16F || declaredOutType == CV_16BF)) + declaredOutType = -1; + if (declaredOutType >= 0 && !outputsVec[i].empty() && + outputsVec[i].depth() != CV_MAT_DEPTH(declaredOutType)) + { + Mat tmp; + outputsVec[i].convertTo(tmp, CV_MAT_DEPTH(declaredOutType)); + outputsVec[i] = tmp; + } } else { outputsVec[i] = outm; } diff --git a/modules/dnn/src/onnx/onnx_dtype_convert.hpp b/modules/dnn/src/onnx/onnx_dtype_convert.hpp new file mode 100644 index 0000000000..d501deeecc --- /dev/null +++ b/modules/dnn/src/onnx/onnx_dtype_convert.hpp @@ -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. + +// TODO: drop when these dtypes get native cv::Mat depths; math then moves to core. + +#ifndef OPENCV_DNN_ONNX_DTYPE_CONVERT_HPP +#define OPENCV_DNN_ONNX_DTYPE_CONVERT_HPP + +#include +#include +#include +#include + +namespace cv { namespace dnn { namespace onnx_dtype { + +// ONNX TensorProto.DataType value for FLOAT8E8M0 (absent from the bundled opencv-onnx.proto). +enum { ONNX_FLOAT8E8M0 = 24 }; + +// Description of a sign+exponent+mantissa low-precision float (the FP8 family). +struct Fp8Fmt { int ebits, mbits, bias; bool has_inf, fnuz; }; + +// Returns the format for an FP8 ONNX dtype, or {0,...} for non-FP8 types. +inline Fp8Fmt fp8FmtFor(int onnxType) +{ + switch (onnxType) + { + case 17: return {4, 3, 7, false, false}; // FLOAT8E4M3FN + case 18: return {4, 3, 8, false, true}; // FLOAT8E4M3FNUZ + case 19: return {5, 2, 15, true, false}; // FLOAT8E5M2 + case 20: return {5, 2, 16, false, true}; // FLOAT8E5M2FNUZ + } + return {0, 0, 0, false, false}; +} + +inline bool isFp8(int onnxType) { return onnxType >= 17 && onnxType <= 20; } +// E4M3FN (17) and E4M3FNUZ (18) have a native cv::Mat depth; E5M2/E5M2FNUZ do not. +inline bool isFp8Native(int t) { return t == 17 || t == 18; } +inline int fp8NativeDepth(int t) { return t == 17 ? CV_8F_E4M3FN : CV_8F_E4M3FNUZ; } +// Storage: E4M3 native FP8; E5M2/FP4 CV_16F; E8M0 CV_32F; INT4 CV_8S; UINT4 CV_8U. +inline bool isExoticFloat(int t) { return isFp8(t) || t == 23 /*FP4*/ || t == ONNX_FLOAT8E8M0; } +inline bool isInt4(int t) { return t == 22; } +inline bool isUint4(int t) { return t == 21; } +inline bool isExotic(int t) { return isExoticFloat(t) || isInt4(t) || isUint4(t); } + +inline uint32_t f2u(float f) { Cv32suf s; s.f = f; return s.u; } + +// Round 'full' dropping 'shift' low bits, round to nearest, ties to even. +inline uint32_t roundRNE(uint32_t full, int shift) +{ + if (shift <= 0) return full << (-shift); + uint32_t q = full >> shift; + uint32_t rem = full & ((1u << shift) - 1); + uint32_t half = 1u << (shift - 1); + if (rem > half || (rem == half && (q & 1))) q++; + return q; +} + +inline uint8_t f32ToFp8(float x, const Fp8Fmt& f, bool saturate) +{ + uint32_t u = f2u(x), sign = (u >> 31) & 1, e = (u >> 23) & 0xFF, m = u & 0x7FFFFF; + const int W = f.ebits + f.mbits; + const uint32_t sbit = sign << W, maxe = (1u << f.ebits) - 1; + const uint8_t NaNc = f.fnuz ? 0x80 : (uint8_t)(sbit | (maxe << f.mbits) | ((1u << f.mbits) - 1)); + uint8_t maxfin; + if (f.has_inf) maxfin = (uint8_t)(sbit | ((maxe - 1) << f.mbits) | ((1u << f.mbits) - 1)); + else if (f.fnuz) maxfin = (uint8_t)(sbit | (maxe << f.mbits) | ((1u << f.mbits) - 1)); + else maxfin = (uint8_t)(sbit | (maxe << f.mbits) | ((1u << f.mbits) - 2)); + const uint8_t Infc = (uint8_t)(sbit | (maxe << f.mbits)); + + if (e == 0xFF && m != 0) return NaNc; + if (e == 0xFF && m == 0) + { + if (f.has_inf && !f.fnuz) return saturate ? maxfin : Infc; + return saturate ? maxfin : NaNc; + } + if (e == 0 && m == 0) return f.fnuz ? 0 : (uint8_t)sbit; + + int newexp = (int)e - 127 + f.bias; + const uint32_t full = (1u << 23) | m; + if (newexp <= 0) + { + const int shift = (23 - f.mbits) + (1 - newexp); + uint32_t mant = (shift >= 32) ? 0u : roundRNE(full, shift); + if (mant == 0) return f.fnuz ? 0 : (uint8_t)sbit; + return (uint8_t)(sbit | mant); + } + uint32_t rounded = roundRNE(full, 23 - f.mbits); + if (rounded & (1u << (f.mbits + 1))) { rounded >>= 1; newexp++; } + const uint32_t mant = rounded & ((1u << f.mbits) - 1); + bool ov; + if (f.has_inf) ov = (uint32_t)newexp >= maxe; + else if (f.fnuz) ov = (uint32_t)newexp > maxe; + else ov = (uint32_t)newexp > maxe || ((uint32_t)newexp == maxe && mant == (1u << f.mbits) - 1); + if (ov) + { + if (f.has_inf && !f.fnuz) return saturate ? maxfin : Infc; + return saturate ? maxfin : NaNc; + } + return (uint8_t)(sbit | ((uint32_t)newexp << f.mbits) | mant); +} + +// ONNX preserves the NaN sign bit (FNUZ 0x80 decodes to -NaN); bit-exact tests need it. +inline float signedQNaN(uint32_t sign) { Cv32suf s; s.u = sign ? 0xFFC00000u : 0x7FC00000u; return s.f; } + +inline float fp8ToF32(uint8_t code, const Fp8Fmt& f) +{ + const int W = f.ebits + f.mbits; + const uint32_t sign = (code >> W) & 1; + const uint32_t exp = ((uint32_t)code >> f.mbits) & ((1u << f.ebits) - 1); + const uint32_t man = code & ((1u << f.mbits) - 1); + const uint32_t maxe = (1u << f.ebits) - 1; + const float s = sign ? -1.f : 1.f; + if (f.fnuz) + { + if ((uint32_t)code == (1u << W)) return signedQNaN(sign); + } + else if (exp == maxe) + { + if (f.has_inf) return man == 0 ? s * std::numeric_limits::infinity() : signedQNaN(sign); + if (man == (1u << f.mbits) - 1) return signedQNaN(sign); + } + if (exp == 0) return s * (float)man * std::ldexp(1.0f, 1 - f.bias - f.mbits); + return s * (1.0f + (float)man / (float)(1u << f.mbits)) * std::ldexp(1.0f, (int)exp - f.bias); +} + +// E8M0: unsigned, 8-bit exponent only (no sign, no mantissa), bias 127; 0xFF == NaN. +// Conversion rounds UP (ceil to next power of two), per the ONNX reference data. +inline uint8_t f32ToE8M0(float x) +{ + if (std::isnan(x)) return 0xFF; + const uint32_t u = f2u(x), sign = (u >> 31) & 1, e = (u >> 23) & 0xFF, m = u & 0x7FFFFF; + if (x == 0.f) return 0; + if (sign) return 0xFF; // negative -> NaN (unsigned format) + if (e == 0xFF) return 0xFF; // inf -> NaN + int code = (int)e + (m > 0 ? 1 : 0); + return (uint8_t)(code > 0xFE ? 0xFE : code); +} + +inline float e8m0ToF32(uint8_t c) +{ + if (c == 0xFF) return std::numeric_limits::quiet_NaN(); + return std::ldexp(1.0f, (int)c - 127); +} + +// FLOAT4E2M1: 1-2-1, bias 1. Magnitudes {0,.5,1,1.5,2,3,4,6}; saturating; NaN -> 0x8. +inline const float* fp4Values() +{ + static const float v[8] = {0.f, 0.5f, 1.f, 1.5f, 2.f, 3.f, 4.f, 6.f}; + return v; +} + +inline uint8_t f32ToFp4(float x) +{ + if (std::isnan(x)) return 0x8; + const uint32_t sign = (f2u(x) >> 31) & 1; + const float ax = std::fabs(x); + const float* V = fp4Values(); + int best; + if (std::isinf(ax) || ax >= 6.0f) best = 7; // saturate to max magnitude + else + { + best = 0; float bestd = std::numeric_limits::max(); + for (int c = 0; c < 8; c++) + { + const float d = std::fabs(ax - V[c]); + if (d < bestd) { bestd = d; best = c; } + else if (d == bestd && (best & 1)) best = c; // tie -> even code + } + } + return (uint8_t)((sign << 3) | best); +} + +inline float fp4ToF32(uint8_t code) { return (code & 0x8 ? -1.f : 1.f) * fp4Values()[code & 7]; } + +// INT4/UINT4: truncate toward zero, keep low 4 bits (wraps; ONNX does not clamp). +inline int8_t f32ToInt4(float x) { int n = (int)std::trunc(x) & 0xF; return (int8_t)(n >= 8 ? n - 16 : n); } +inline uint8_t f32ToUint4(float x) { return (uint8_t)((int)std::trunc(x) & 0xF); } +inline int8_t int4SignExtend(uint8_t nib) { return (int8_t)((nib & 0xF) >= 8 ? (int)(nib & 0xF) - 16 : (nib & 0xF)); } + +// Extract the i-th 4-bit element from a tensor packed two-per-byte (low nibble first). +inline uint8_t unpackNibble(const uchar* raw, size_t i) { return (i & 1) ? (raw[i >> 1] >> 4) & 0xF : raw[i >> 1] & 0xF; } + +}}} // namespace cv::dnn::onnx_dtype + +#endif // OPENCV_DNN_ONNX_DTYPE_CONVERT_HPP diff --git a/modules/dnn/src/onnx/onnx_graph_simplifier.cpp b/modules/dnn/src/onnx/onnx_graph_simplifier.cpp index 1fea52e78f..6d335136a9 100644 --- a/modules/dnn/src/onnx/onnx_graph_simplifier.cpp +++ b/modules/dnn/src/onnx/onnx_graph_simplifier.cpp @@ -10,6 +10,7 @@ #ifdef HAVE_PROTOBUF #include "../graph_simplifier.hpp" #include "onnx_graph_simplifier.hpp" +#include "onnx_dtype_convert.hpp" #include #include "opencv2/core/utils/filesystem.private.hpp" @@ -1958,7 +1959,12 @@ int dataType2cv(int dt) dt == opencv_onnx::TensorProto_DataType_BFLOAT16 ? CV_16BF : dt == opencv_onnx::TensorProto_DataType_COMPLEX64 ? CV_32FC2 : dt == opencv_onnx::TensorProto_DataType_COMPLEX128 ? CV_64FC2 : - dt == opencv_onnx::TensorProto_DataType_BOOL ? CV_Bool : -1; + dt == opencv_onnx::TensorProto_DataType_BOOL ? CV_Bool : + dt == opencv_onnx::TensorProto_DataType_UINT4 ? CV_8U : + dt == opencv_onnx::TensorProto_DataType_INT4 ? CV_8S : + dt == onnx_dtype::ONNX_FLOAT8E8M0 ? CV_32F : + onnx_dtype::isFp8Native(dt) ? onnx_dtype::fp8NativeDepth(dt) : + onnx_dtype::isExoticFloat(dt) ? CV_16F : -1; } Mat getMatFromTensor(const opencv_onnx::TensorProto& tensor_proto, bool uint8ToInt8, const std::string base_path) @@ -2029,33 +2035,23 @@ Mat getMatFromTensor(const opencv_onnx::TensorProto& tensor_proto, bool uint8ToI } else if (datatype == opencv_onnx::TensorProto_DataType_FLOAT16) { - // FIXME, for now, we only load FP16 Tensor as FP32 Mat, full support for FP16 is required in the future. - CV_LOG_ONCE_INFO(NULL, "DNN: load FP16 model as FP32 model, and it takes twice the FP16 RAM requirement."); - - // ONNX saves float 16 data in two format: int32 and raw_data. + // Load FP16 natively as CV_16F; ONNX stores it in int32_data or raw_data. // Link: https://github.com/onnx/onnx/issues/4460#issuecomment-1224373746 if (!tensor_proto.int32_data().empty()) { size_t sz = tensor_proto.int32_data().size(); checkPayloadSize(sz); std::vector halfvec(sz); + blob.create((int)sizes.size(), sizes.data(), CV_16FC1); const int32_t* intdata = (const int32_t*)tensor_proto.int32_data().data(); + uint16_t* dst = (uint16_t*)blob.data; for (size_t i = 0; i < sz; i++) - { - union - { - int16_t h; - int32_t i; - } u; - u.i = intdata[i]; - halfvec[i] = u.h; - } - Mat(sizes, CV_16FC1, halfvec.data()).convertTo(blob, CV_32FC1); + dst[i] = (uint16_t)(intdata[i] & 0xFFFF); } else { checkPayloadSize(raw_data_size / sizeof(int16_t)); - Mat(sizes, CV_16FC1, rawdata).convertTo(blob, CV_32FC1); + Mat(sizes, CV_16FC1, rawdata).copyTo(blob); } } else if (datatype == opencv_onnx::TensorProto_DataType_BFLOAT16) @@ -2236,6 +2232,58 @@ Mat getMatFromTensor(const opencv_onnx::TensorProto& tensor_proto, bool uint8ToI else Mat(sizes, CV_64UC1, rawdata).copyTo(blob); } + else if (datatype == opencv_onnx::TensorProto_DataType_FLOAT8E4M3FN || + datatype == opencv_onnx::TensorProto_DataType_FLOAT8E4M3FNUZ) + { + // E4M3FN/E4M3FNUZ have a native depth: keep the raw FP8 bytes. + checkPayloadSize(raw_data_size); + blob.create((int)sizes.size(), sizes.data(), + CV_MAKETYPE(onnx_dtype::fp8NativeDepth(datatype), 1)); + memcpy(blob.data, rawdata, (size_t)blob.total() * blob.elemSize()); + } + else if (datatype == opencv_onnx::TensorProto_DataType_FLOAT8E5M2 || + datatype == opencv_onnx::TensorProto_DataType_FLOAT8E5M2FNUZ) + { + // E5M2/E5M2FNUZ have no native depth: decode losslessly into CV_16F. + const onnx_dtype::Fp8Fmt fmt = onnx_dtype::fp8FmtFor(datatype); + blob.create((int)sizes.size(), sizes.data(), CV_16FC1); + const uchar* src = (const uchar*)rawdata; + hfloat* dst = blob.ptr(); + for (size_t i = 0, total = blob.total(); i < total; i++) + dst[i] = hfloat(onnx_dtype::fp8ToF32(src[i], fmt)); + } + else if (datatype == onnx_dtype::ONNX_FLOAT8E8M0) + { + blob.create((int)sizes.size(), sizes.data(), CV_32FC1); + const uchar* src = (const uchar*)rawdata; + float* dst = blob.ptr(); + for (size_t i = 0, total = blob.total(); i < total; i++) + dst[i] = onnx_dtype::e8m0ToF32(src[i]); + } + else if (datatype == opencv_onnx::TensorProto_DataType_FLOAT4E2M1) + { + blob.create((int)sizes.size(), sizes.data(), CV_16FC1); + const uchar* src = (const uchar*)rawdata; + hfloat* dst = blob.ptr(); + for (size_t i = 0, total = blob.total(); i < total; i++) + dst[i] = hfloat(onnx_dtype::fp4ToF32(onnx_dtype::unpackNibble(src, i))); + } + else if (datatype == opencv_onnx::TensorProto_DataType_INT4) + { + blob.create((int)sizes.size(), sizes.data(), CV_8SC1); + const uchar* src = (const uchar*)rawdata; + schar* dst = blob.ptr(); + for (size_t i = 0, total = blob.total(); i < total; i++) + dst[i] = onnx_dtype::int4SignExtend(onnx_dtype::unpackNibble(src, i)); + } + else if (datatype == opencv_onnx::TensorProto_DataType_UINT4) + { + blob.create((int)sizes.size(), sizes.data(), CV_8UC1); + const uchar* src = (const uchar*)rawdata; + uchar* dst = blob.ptr(); + for (size_t i = 0, total = blob.total(); i < total; i++) + dst[i] = onnx_dtype::unpackNibble(src, i); + } else { // @TODO: refactor the error handling diff --git a/modules/dnn/src/onnx/onnx_importer2.cpp b/modules/dnn/src/onnx/onnx_importer2.cpp index 5d1af7e2e1..8b267583f7 100644 --- a/modules/dnn/src/onnx/onnx_importer2.cpp +++ b/modules/dnn/src/onnx/onnx_importer2.cpp @@ -61,9 +61,6 @@ static T getScalarFromMat(Mat m) } - - - static std::string dataType2str(int dt) { const char* str = @@ -111,6 +108,7 @@ protected: Ptr parseGraph(opencv_onnx::GraphProto* graph_proto, bool mainGraph); void parseNode(const opencv_onnx::NodeProto& node_proto); bool parseValueInfo(const opencv_onnx::ValueInfoProto& valueInfoProto, ArgData& data); + int findGraphTensorOnnxType(const std::string& name) const; Mat parseTensor(const opencv_onnx::TensorProto& tensorProto); void rememberMissingOp(const std::string& opname); @@ -718,6 +716,14 @@ Net ONNXImporter2::parseModel() parseOperatorSet(); Ptr mainGraph = parseGraph(graph_proto, true); netimpl->mainGraph = mainGraph; + // Capture declared output dtypes before prepareForInference() replaces them with computed ones. + if (mainGraph) + { + const std::vector& outs = mainGraph->outputs(); + netimpl->mainGraphOutTypes.resize(outs.size()); + for (size_t i = 0; i < outs.size(); i++) + netimpl->mainGraphOutTypes[i] = netimpl->args.at(outs[i].idx).type; + } netimpl->modelFormat = DNN_MODEL_ONNX; netimpl->originalLayout = DATA_LAYOUT_NCHW; // netimpl->onnx_opset = onnx_opset; @@ -1689,10 +1695,37 @@ void ONNXImporter2::parseCast2(LayerParams& layerParams, const opencv_onnx::Node addLayer(layerParams, node_proto); } +// Returns a graph tensor's ONNX data_type by name, or -1 if unknown. +int ONNXImporter2::findGraphTensorOnnxType(const std::string& name) const +{ + if (!curr_graph_proto) + return -1; + const opencv_onnx::GraphProto& g = *curr_graph_proto; + for (int i = 0; i < g.input_size(); i++) + if (g.input(i).name() == name && g.input(i).has_type() && g.input(i).type().has_tensor_type()) + return g.input(i).type().tensor_type().elem_type(); + for (int i = 0; i < g.value_info_size(); i++) + if (g.value_info(i).name() == name && g.value_info(i).has_type() && g.value_info(i).type().has_tensor_type()) + return g.value_info(i).type().tensor_type().elem_type(); + for (int i = 0; i < g.output_size(); i++) + if (g.output(i).name() == name && g.output(i).has_type() && g.output(i).type().has_tensor_type()) + return g.output(i).type().tensor_type().elem_type(); + for (int i = 0; i < g.initializer_size(); i++) + if (g.initializer(i).name() == name) + return g.initializer(i).data_type(); + return -1; +} + void ONNXImporter2::parseCastLike(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { CV_CheckEQ(node_proto.input_size(), 2, "CastLike requires two inputs"); layerParams.type = "Cast2"; + if (!layerParams.has("to")) + { + int elemType = findGraphTensorOnnxType(node_proto.input(1)); + if (elemType > 0) + layerParams.set("to", elemType); + } addLayer(layerParams, node_proto); } diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp index b9b7a45e01..3604dffd76 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp @@ -416,6 +416,174 @@ CASE(test_castlike_STRING_to_FLOAT_expanded) #if SKIP_SET_1 SKIP; #endif +CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FN) + SKIP; +CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ) + SKIP; +CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ_expanded) + SKIP; +CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FN_expanded) + SKIP; +CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2) + SKIP; +CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ) + SKIP; +CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ_expanded) + SKIP; +CASE(test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2_expanded) + SKIP; +CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FN) + SKIP; +CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ) + SKIP; +CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2) + SKIP; +CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ) + SKIP; +CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ_expanded) + SKIP; +CASE(test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2_expanded) + SKIP; +CASE(test_castlike_FLOAT_to_UINT4) + SKIP; +CASE(test_castlike_FLOAT_to_UINT4_expanded) + SKIP; +CASE(test_castlike_FLOAT_to_FLOAT4E2M1) + SKIP; +CASE(test_castlike_FLOAT_to_FLOAT4E2M1_expanded) + SKIP; +CASE(test_castlike_FLOAT_to_FLOAT8E4M3FN) + SKIP; +CASE(test_castlike_FLOAT_to_FLOAT8E4M3FNUZ) + SKIP; +CASE(test_castlike_FLOAT_to_FLOAT8E4M3FNUZ_expanded) + SKIP; +CASE(test_castlike_FLOAT_to_FLOAT8E4M3FN_expanded) + SKIP; +CASE(test_castlike_FLOAT_to_FLOAT8E5M2) + SKIP; +CASE(test_castlike_FLOAT_to_FLOAT8E5M2FNUZ) + SKIP; +CASE(test_castlike_FLOAT_to_FLOAT8E5M2FNUZ_expanded) + SKIP; +CASE(test_castlike_FLOAT_to_FLOAT8E5M2_expanded) + SKIP; +CASE(test_castlike_FLOAT_to_INT4) + SKIP; +CASE(test_castlike_FLOAT_to_INT4_expanded) + SKIP; +CASE(test_cast_FLOAT16_to_FLOAT4E2M1) + SKIP; +CASE(test_cast_FLOAT16_to_FLOAT8E4M3FN) + SKIP; +CASE(test_cast_FLOAT16_to_FLOAT8E4M3FNUZ) + SKIP; +CASE(test_cast_FLOAT16_to_FLOAT8E5M2) + SKIP; +CASE(test_cast_FLOAT16_to_FLOAT8E5M2FNUZ) + SKIP; +CASE(test_cast_FLOAT16_to_INT4) + SKIP; +CASE(test_cast_FLOAT16_to_UINT4) + SKIP; +CASE(test_cast_FLOAT4E2M1_to_FLOAT) + SKIP; +CASE(test_cast_FLOAT4E2M1_to_FLOAT16) + SKIP; +CASE(test_cast_FLOAT8E4M3FNUZ_to_FLOAT) + SKIP; +CASE(test_cast_FLOAT8E4M3FNUZ_to_FLOAT16) + SKIP; +CASE(test_cast_FLOAT8E4M3FN_to_FLOAT) + SKIP; +CASE(test_cast_FLOAT8E4M3FN_to_FLOAT16) + SKIP; +CASE(test_cast_FLOAT8E5M2FNUZ_to_FLOAT) + SKIP; +CASE(test_cast_FLOAT8E5M2FNUZ_to_FLOAT16) + SKIP; +CASE(test_cast_FLOAT8E5M2_to_FLOAT) + SKIP; +CASE(test_cast_FLOAT8E5M2_to_FLOAT16) + SKIP; +CASE(test_cast_FLOAT_to_FLOAT4E2M1) + SKIP; +CASE(test_cast_FLOAT_to_FLOAT8E4M3FN) + SKIP; +CASE(test_cast_FLOAT_to_FLOAT8E4M3FNUZ) + SKIP; +CASE(test_cast_FLOAT_to_FLOAT8E5M2) + SKIP; +CASE(test_cast_FLOAT_to_FLOAT8E5M2FNUZ) + SKIP; +CASE(test_cast_FLOAT_to_INT4) + SKIP; +CASE(test_cast_FLOAT_to_UINT4) + SKIP; +CASE(test_cast_INT4_to_FLOAT) + SKIP; +CASE(test_cast_INT4_to_FLOAT16) + SKIP; +CASE(test_cast_INT4_to_INT8) + SKIP; +CASE(test_cast_UINT4_to_FLOAT) + SKIP; +CASE(test_cast_UINT4_to_FLOAT16) + SKIP; +CASE(test_cast_UINT4_to_UINT8) + SKIP; +CASE(test_cast_e8m0_FLOAT16_to_FLOAT8E8M0) + SKIP; +CASE(test_cast_e8m0_FLOAT8E8M0_to_FLOAT) + SKIP; +CASE(test_cast_e8m0_FLOAT8E8M0_to_FLOAT16) + SKIP; +CASE(test_cast_e8m0_FLOAT_to_FLOAT8E8M0) + SKIP; +CASE(test_cast_no_saturate_FLOAT16_to_FLOAT8E4M3FN) + SKIP; +CASE(test_cast_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ) + SKIP; +CASE(test_cast_no_saturate_FLOAT16_to_FLOAT8E5M2) + SKIP; +CASE(test_cast_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ) + SKIP; +CASE(test_cast_no_saturate_FLOAT_to_FLOAT8E4M3FN) + SKIP; +CASE(test_cast_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ) + SKIP; +CASE(test_cast_no_saturate_FLOAT_to_FLOAT8E5M2) + SKIP; +CASE(test_cast_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ) + SKIP; +CASE(test_castlike_FLOAT16_to_FLOAT4E2M1) + SKIP; +CASE(test_castlike_FLOAT16_to_FLOAT4E2M1_expanded) + SKIP; +CASE(test_castlike_FLOAT16_to_FLOAT8E4M3FN) + SKIP; +CASE(test_castlike_FLOAT16_to_FLOAT8E4M3FNUZ) + SKIP; +CASE(test_castlike_FLOAT16_to_FLOAT8E4M3FNUZ_expanded) + SKIP; +CASE(test_castlike_FLOAT16_to_FLOAT8E4M3FN_expanded) + SKIP; +CASE(test_castlike_FLOAT16_to_FLOAT8E5M2) + SKIP; +CASE(test_castlike_FLOAT16_to_FLOAT8E5M2FNUZ) + SKIP; +CASE(test_castlike_FLOAT16_to_FLOAT8E5M2FNUZ_expanded) + SKIP; +CASE(test_castlike_FLOAT16_to_FLOAT8E5M2_expanded) + SKIP; +CASE(test_castlike_FLOAT16_to_INT4) + SKIP; +CASE(test_castlike_FLOAT16_to_INT4_expanded) + SKIP; +CASE(test_castlike_FLOAT16_to_UINT4) + SKIP; +CASE(test_castlike_FLOAT16_to_UINT4_expanded) + SKIP; CASE(test_ceil) // no filter CASE(test_ceil_example) diff --git a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp index 54a7832101..7542a6d669 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp @@ -39,63 +39,7 @@ "test_bernoulli_expanded", // ---- same as above --- "test_bernoulli_seed", // ---- same as above --- "test_bernoulli_seed_expanded", // ---- same as above --- -"test_cast_FLOAT16_to_FLOAT4E2M1", -"test_cast_FLOAT16_to_FLOAT8E4M3FN", -"test_cast_FLOAT16_to_FLOAT8E4M3FNUZ", -"test_cast_FLOAT16_to_FLOAT8E5M2", -"test_cast_FLOAT16_to_FLOAT8E5M2FNUZ", -"test_cast_FLOAT16_to_INT4", -"test_cast_FLOAT16_to_UINT4", -"test_cast_FLOAT4E2M1_to_FLOAT", -"test_cast_FLOAT4E2M1_to_FLOAT16", -"test_cast_FLOAT8E4M3FNUZ_to_FLOAT", -"test_cast_FLOAT8E4M3FNUZ_to_FLOAT16", -"test_cast_FLOAT8E4M3FN_to_FLOAT", -"test_cast_FLOAT8E4M3FN_to_FLOAT16", -"test_cast_FLOAT8E5M2FNUZ_to_FLOAT", -"test_cast_FLOAT8E5M2FNUZ_to_FLOAT16", -"test_cast_FLOAT8E5M2_to_FLOAT", -"test_cast_FLOAT8E5M2_to_FLOAT16", -"test_cast_FLOAT_to_FLOAT4E2M1", -"test_cast_FLOAT_to_FLOAT8E4M3FN", -"test_cast_FLOAT_to_FLOAT8E4M3FNUZ", -"test_cast_FLOAT_to_FLOAT8E5M2", -"test_cast_FLOAT_to_FLOAT8E5M2FNUZ", -"test_cast_FLOAT_to_INT4", -"test_cast_FLOAT_to_UINT4", -"test_cast_INT4_to_FLOAT", -"test_cast_INT4_to_FLOAT16", -"test_cast_INT4_to_INT8", -"test_cast_UINT4_to_FLOAT", -"test_cast_UINT4_to_FLOAT16", -"test_cast_UINT4_to_UINT8", -"test_cast_e8m0_FLOAT16_to_FLOAT8E8M0", -"test_cast_e8m0_FLOAT8E8M0_to_FLOAT", -"test_cast_e8m0_FLOAT8E8M0_to_FLOAT16", -"test_cast_e8m0_FLOAT_to_FLOAT8E8M0", -"test_cast_no_saturate_FLOAT16_to_FLOAT8E4M3FN", -"test_cast_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ", -"test_cast_no_saturate_FLOAT16_to_FLOAT8E5M2", -"test_cast_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ", -"test_cast_no_saturate_FLOAT_to_FLOAT8E4M3FN", -"test_cast_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ", -"test_cast_no_saturate_FLOAT_to_FLOAT8E5M2", -"test_cast_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ", -"test_castlike_FLOAT16_to_FLOAT4E2M1", -"test_castlike_FLOAT16_to_FLOAT4E2M1_expanded", -"test_castlike_FLOAT16_to_FLOAT8E4M3FN", -"test_castlike_FLOAT16_to_FLOAT8E4M3FNUZ", -"test_castlike_FLOAT16_to_FLOAT8E4M3FNUZ_expanded", -"test_castlike_FLOAT16_to_FLOAT8E4M3FN_expanded", -"test_castlike_FLOAT16_to_FLOAT8E5M2", -"test_castlike_FLOAT16_to_FLOAT8E5M2FNUZ", -"test_castlike_FLOAT16_to_FLOAT8E5M2FNUZ_expanded", -"test_castlike_FLOAT16_to_FLOAT8E5M2_expanded", -"test_castlike_FLOAT16_to_INT4", -"test_castlike_FLOAT16_to_INT4_expanded", -"test_castlike_FLOAT16_to_UINT4", -"test_castlike_FLOAT16_to_UINT4_expanded", -"test_castlike_FLOAT4E2M1_to_FLOAT", +"test_castlike_FLOAT4E2M1_to_FLOAT", // opencv_extra stores empty input data for these from-exotic CastLike cases "test_castlike_FLOAT4E2M1_to_FLOAT16", "test_castlike_FLOAT4E2M1_to_FLOAT16_expanded", "test_castlike_FLOAT4E2M1_to_FLOAT_expanded", @@ -115,21 +59,7 @@ "test_castlike_FLOAT8E5M2_to_FLOAT16", "test_castlike_FLOAT8E5M2_to_FLOAT16_expanded", "test_castlike_FLOAT8E5M2_to_FLOAT_expanded", -"test_castlike_FLOAT_to_FLOAT4E2M1", -"test_castlike_FLOAT_to_FLOAT4E2M1_expanded", -"test_castlike_FLOAT_to_FLOAT8E4M3FN", -"test_castlike_FLOAT_to_FLOAT8E4M3FNUZ", -"test_castlike_FLOAT_to_FLOAT8E4M3FNUZ_expanded", -"test_castlike_FLOAT_to_FLOAT8E4M3FN_expanded", -"test_castlike_FLOAT_to_FLOAT8E5M2", -"test_castlike_FLOAT_to_FLOAT8E5M2FNUZ", -"test_castlike_FLOAT_to_FLOAT8E5M2FNUZ_expanded", -"test_castlike_FLOAT_to_FLOAT8E5M2_expanded", -"test_castlike_FLOAT_to_INT4", -"test_castlike_FLOAT_to_INT4_expanded", "test_castlike_FLOAT_to_STRING", -"test_castlike_FLOAT_to_UINT4", -"test_castlike_FLOAT_to_UINT4_expanded", "test_castlike_INT4_to_FLOAT", "test_castlike_INT4_to_FLOAT16", "test_castlike_INT4_to_FLOAT16_expanded", @@ -143,20 +73,6 @@ "test_castlike_UINT4_to_FLOAT_expanded", "test_castlike_UINT4_to_UINT8", "test_castlike_UINT4_to_UINT8_expanded", -"test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FN", -"test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ", -"test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FNUZ_expanded", -"test_castlike_no_saturate_FLOAT16_to_FLOAT8E4M3FN_expanded", -"test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2", -"test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ", -"test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2FNUZ_expanded", -"test_castlike_no_saturate_FLOAT16_to_FLOAT8E5M2_expanded", -"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FN", -"test_castlike_no_saturate_FLOAT_to_FLOAT8E4M3FNUZ", -"test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2", -"test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ", -"test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2FNUZ_expanded", -"test_castlike_no_saturate_FLOAT_to_FLOAT8E5M2_expanded", "test_clip_min_greater_than_max", "test_col2im", "test_col2im_5d", diff --git a/modules/python/src2/cv2_convert.cpp b/modules/python/src2/cv2_convert.cpp index 720f3b9f58..8cf87c5767 100644 --- a/modules/python/src2/cv2_convert.cpp +++ b/modules/python/src2/cv2_convert.cpp @@ -315,8 +315,8 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info) template<> PyObject* pyopencv_from(const cv::Mat& m) { - // NumPy has no bfloat16 dtype: widen CV_16BF to float32 (lossless). - if( m.depth() == CV_16BF ) + // NumPy has no bfloat16 or float8 dtype: widen these to float32 (lossless). + if( m.depth() == CV_16BF || m.depth() == CV_8F_E4M3FN || m.depth() == CV_8F_E4M3FNUZ ) { cv::Mat m32f; ERRWRAP2(m.convertTo(m32f, CV_32F)); diff --git a/modules/ts/src/ts_func.cpp b/modules/ts/src/ts_func.cpp index 0f0856260e..907e59a4ff 100644 --- a/modules/ts/src/ts_func.cpp +++ b/modules/ts/src/ts_func.cpp @@ -1460,7 +1460,7 @@ norm_flt_(const _Tp* src1, const _Tp* src2, size_t total, int cn, int normType, double norm(InputArray _src, int normType, InputArray _mask) { Mat src = _src.getMat(), mask = _mask.getMat(); - if( src.depth() == CV_16F ) + if( src.depth() == CV_16F || src.depth() == CV_8F_E4M3FN || src.depth() == CV_8F_E4M3FNUZ ) { Mat src32f; src.convertTo(src32f, CV_32F); @@ -1660,6 +1660,12 @@ double norm(InputArray _src1, InputArray _src2, int normType, InputArray _mask) case CV_16BF: result = norm_flt_((const cv::bfloat*)sptr1, (const cv::bfloat*)sptr2, total, cn, normType, result, mptr); break; + case CV_8F_E4M3FN: + result = norm_flt_((const cv::fp8_t*)sptr1, (const cv::fp8_t*)sptr2, total, cn, normType, result, mptr); + break; + case CV_8F_E4M3FNUZ: + result = norm_flt_((const cv::fp8a_t*)sptr1, (const cv::fp8a_t*)sptr2, total, cn, normType, result, mptr); + break; default: CV_Error(Error::StsUnsupportedFormat, ""); };