From 34074075b2884facc614eeb16293531a458b640f Mon Sep 17 00:00:00 2001 From: Abhishek Gola Date: Wed, 15 Jul 2026 18:15:01 +0530 Subject: [PATCH] Merge pull request #29369 from abhishek-gola:fp8_support FP8 support in core module #29369 Core part of https://github.com/opencv/opencv/issues/29313 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/core/include/opencv2/core/cvdef.h | 82 ++++++++- .../core/include/opencv2/core/hal/interface.h | 19 +- .../core/include/opencv2/core/persistence.hpp | 6 + .../core/include/opencv2/core/saturate.hpp | 22 +++ modules/core/include/opencv2/core/traits.hpp | 34 ++++ modules/core/src/check.cpp | 3 +- modules/core/src/convert.dispatch.cpp | 5 + modules/core/src/convert.simd.hpp | 110 +++++++++++- modules/core/src/convert_scale.simd.hpp | 124 ++++++++++++- modules/core/src/copy.cpp | 6 + modules/core/src/cuda/gpu_mat.cu | 5 +- modules/core/src/persistence.cpp | 38 +++- .../core/src/persistence_base64_encoding.cpp | 7 + modules/core/src/rand.cpp | 22 ++- modules/core/test/test_arithm.cpp | 17 +- modules/core/test/test_cuda.cpp | 8 +- modules/core/test/test_fp8.cpp | 168 ++++++++++++++++++ modules/core/test/test_io.cpp | 51 ++++++ modules/ts/src/ts_func.cpp | 61 +++++++ 19 files changed, 766 insertions(+), 22 deletions(-) create mode 100644 modules/core/test/test_fp8.cpp diff --git a/modules/core/include/opencv2/core/cvdef.h b/modules/core/include/opencv2/core/cvdef.h index f2c9dabe77..d94d96a925 100644 --- a/modules/core/include/opencv2/core/cvdef.h +++ b/modules/core/include/opencv2/core/cvdef.h @@ -521,7 +521,10 @@ Cv64suf; CV_16U - 2 bytes ... */ -#define CV_ELEM_SIZE1(type) ((int)((0x4881228442211ULL >> (CV_MAT_DEPTH(type) * 4)) & 15)) +#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)) #define CV_ELEM_SIZE(type) (CV_MAT_CN(type)*CV_ELEM_SIZE1(type)) @@ -979,6 +982,83 @@ protected: ushort w; }; +namespace fp8_detail { + +// round-half-up (ties away from zero) — deliberately NOT round-to-nearest-even / OCP-ONNX spec +inline unsigned roundHalfUp(unsigned full, int shift) +{ + if (shift <= 0) return full << (-shift); + unsigned q = full >> shift, rem = full & ((1u << shift) - 1), half = 1u << (shift - 1); + if (rem >= half) q++; + return q; +} +inline float pow2(int n) { Cv32suf s; s.u = (unsigned)((n + 127) << 23); return s.f; } + +// E4M3 encode; no inf, bias/fnuz select E4M3FN vs E4M3FNUZ +inline uchar encodeE4M3(float x, int bias, bool fnuz) +{ + Cv32suf in; in.f = x; + unsigned u = in.u, sign = (u >> 31) & 1, e = (u >> 23) & 0xFF, m = u & 0x7FFFFF; + const unsigned sbit = sign << 7; + const unsigned nanc = fnuz ? 0x80u : (sbit | 0x7Fu); + if (e == 0xFF) return (uchar)nanc; + if (e == 0 && m == 0) return (uchar)(fnuz ? 0u : sbit); + int newexp = (int)e - 127 + bias; + const unsigned full = (1u << 23) | m; + if (newexp <= 0) // subnormal + { + const int shift = 20 + (1 - newexp); + unsigned mant = (shift >= 32) ? 0u : roundHalfUp(full, shift); + return (uchar)(mant == 0 ? (fnuz ? 0u : sbit) : (sbit | mant)); + } + unsigned rounded = roundHalfUp(full, 20); + if (rounded & 16u) { rounded >>= 1; newexp++; } // carry into exponent + const unsigned mant = rounded & 7u; + const bool ov = fnuz ? ((unsigned)newexp > 15) + : ((unsigned)newexp > 15 || ((unsigned)newexp == 15 && mant == 7)); + if (ov) return (uchar)nanc; + return (uchar)(sbit | ((unsigned)newexp << 3) | mant); +} + +inline float decodeE4M3(uchar b, int bias, bool fnuz) +{ + const unsigned sign = ((unsigned)b >> 7) & 1, exp = ((unsigned)b >> 3) & 15, man = (unsigned)b & 7; + const float s = sign ? -1.f : 1.f; + Cv32suf qn; qn.u = sign ? 0xFFC00000u : 0x7FC00000u; + if (fnuz) { if ((unsigned)b == 0x80u) return qn.f; } + else if (exp == 15) { if (man == 7) return qn.f; } + if (exp == 0) return s * (float)man * pow2(1 - bias - 3); + return s * (1.0f + (float)man / 8.0f) * pow2((int)exp - bias); +} + +} // namespace fp8_detail + +struct fp8_t // E4M3FN: bias 7, no inf, max 448 +{ + fp8_t() : b(0) {} + explicit fp8_t(float x) : b(fp8_detail::encodeE4M3(x, 7, false)) {} + operator float() const { return table()[b]; } + static const float* decodeLUT() { return table(); } +protected: + uchar b; +private: + static const float* table() + { static const struct T { float v[256]; T() { for (int i = 0; i < 256; i++) v[i] = fp8_detail::decodeE4M3((uchar)i, 7, false); } } t; return t.v; } +}; + +struct fp8a_t // E4M3FNUZ: bias 8, no inf, single NaN, no -0, max 240 +{ + fp8a_t() : b(0) {} + explicit fp8a_t(float x) : b(fp8_detail::encodeE4M3(x, 8, true)) {} + operator float() const { return table()[b]; } + static const float* decodeLUT() { return table(); } +protected: + uchar b; +private: + static const float* table() + { static const struct T { float v[256]; T() { for (int i = 0; i < 256; i++) v[i] = fp8_detail::decodeE4M3((uchar)i, 8, true); } } t; return t.v; } +}; + } #endif diff --git a/modules/core/include/opencv2/core/hal/interface.h b/modules/core/include/opencv2/core/hal/interface.h index c6d42b0369..10c72c132b 100644 --- a/modules/core/include/opencv2/core/hal/interface.h +++ b/modules/core/include/opencv2/core/hal/interface.h @@ -64,12 +64,16 @@ typedef int16_t cv_hal_bf16; #define CV_64U 10 #define CV_64S 11 #define CV_32U 12 -#define CV_DEPTH_CURR_MAX 13 +#define CV_8F_E4M3FN 13 // OCP/ONNX FLOAT8E4M3FN (1-4-3, bias 7, no inf, max 448) +#define CV_8F_E4M3FNUZ 14 // FLOAT8E4M3FNUZ (1-4-3, bias 8, no inf, single NaN, no -0) +#define CV_8F CV_8F_E4M3FN // alias for the main 8-bit floating-point type +#define CV_DEPTH_CURR_MAX 15 #define CV_MAT_DEPTH_MASK (CV_DEPTH_MAX - 1) #define CV_MAT_DEPTH(flags) ((flags) & CV_MAT_DEPTH_MASK) #define CV_IS_INT_TYPE(flags) (((1 << CV_MAT_DEPTH(flags)) & 0x1e1f) != 0) -#define CV_IS_FLOAT_TYPE(flags) (((1 << CV_MAT_DEPTH(flags)) & 0x1e0) != 0) +// float family: 32F,64F,16F,16BF (bits 5-8) + the four FP8 depths (bits 13-16) +#define CV_IS_FLOAT_TYPE(flags) (((1 << CV_MAT_DEPTH(flags)) & 0x1e1e0) != 0) #define CV_MAKETYPE(depth,cn) (CV_MAT_DEPTH(depth) + (((cn)-1) << CV_CN_SHIFT)) #define CV_MAKE_TYPE CV_MAKETYPE @@ -152,6 +156,17 @@ typedef int16_t cv_hal_bf16; #define CV_16BFC4 CV_MAKETYPE(CV_16BF,4) #define CV_16BFC(n) CV_MAKETYPE(CV_16BF,(n)) +#define CV_8F_E4M3FNC1 CV_MAKETYPE(CV_8F_E4M3FN,1) +#define CV_8F_E4M3FNC(n) CV_MAKETYPE(CV_8F_E4M3FN,(n)) +#define CV_8F_E4M3FNUZC1 CV_MAKETYPE(CV_8F_E4M3FNUZ,1) +#define CV_8F_E4M3FNUZC(n) CV_MAKETYPE(CV_8F_E4M3FNUZ,(n)) + +#define CV_8FC1 CV_MAKETYPE(CV_8F,1) +#define CV_8FC2 CV_MAKETYPE(CV_8F,2) +#define CV_8FC3 CV_MAKETYPE(CV_8F,3) +#define CV_8FC4 CV_MAKETYPE(CV_8F,4) +#define CV_8FC(n) CV_MAKETYPE(CV_8F,(n)) + //! @name Comparison operation //! @sa cv::CmpTypes //! @{ diff --git a/modules/core/include/opencv2/core/persistence.hpp b/modules/core/include/opencv2/core/persistence.hpp index 675f471de8..65bd85ab80 100644 --- a/modules/core/include/opencv2/core/persistence.hpp +++ b/modules/core/include/opencv2/core/persistence.hpp @@ -672,6 +672,8 @@ CV_EXPORTS void write( FileStorage& fs, const String& name, double value ); CV_EXPORTS void write( FileStorage& fs, const String& name, const String& value ); CV_EXPORTS void write( FileStorage& fs, const String& name, const Mat& value ); CV_EXPORTS void write( FileStorage& fs, const String& name, const SparseMat& value ); +static inline void write( FileStorage& fs, const String& name, const fp8_t& value ) { write( fs, name, (float)value ); } +static inline void write( FileStorage& fs, const String& name, const fp8a_t& value ) { write( fs, name, (float)value ); } #ifdef CV__LEGACY_PERSISTENCE CV_EXPORTS void write( FileStorage& fs, const String& name, const std::vector& value); CV_EXPORTS void write( FileStorage& fs, const String& name, const std::vector& value); @@ -690,6 +692,10 @@ CV_EXPORTS void read(const FileNode& node, double& value, double default_value); CV_EXPORTS void read(const FileNode& node, std::string& value, const std::string& default_value); CV_EXPORTS void read(const FileNode& node, Mat& mat, const Mat& default_mat = Mat() ); CV_EXPORTS void read(const FileNode& node, SparseMat& mat, const SparseMat& default_mat = SparseMat() ); +static inline void read(const FileNode& node, fp8_t& value, const fp8_t& default_value = fp8_t()) +{ float f; read(node, f, (float)default_value); value = fp8_t(f); } +static inline void read(const FileNode& node, fp8a_t& value, const fp8a_t& default_value = fp8a_t()) +{ float f; read(node, f, (float)default_value); value = fp8a_t(f); } #ifdef CV__LEGACY_PERSISTENCE CV_EXPORTS void read(const FileNode& node, std::vector& keypoints); CV_EXPORTS void read(const FileNode& node, std::vector& matches); diff --git a/modules/core/include/opencv2/core/saturate.hpp b/modules/core/include/opencv2/core/saturate.hpp index f08092e84e..a20a9d91a3 100644 --- a/modules/core/include/opencv2/core/saturate.hpp +++ b/modules/core/include/opencv2/core/saturate.hpp @@ -207,6 +207,28 @@ template<> inline bool saturate_cast(int64_t v){ return v != 0; } template<> inline bool saturate_cast(hfloat v){ return (float)v != 0; } template<> inline bool saturate_cast(bfloat v){ return (float)v != 0; } +// saturate_cast for the FP8 family — routes through float, mirroring hfloat/bfloat above. +#define CV_FP8_SATURATE_CAST(T) \ +template static inline _Tp saturate_cast(T v) { return saturate_cast<_Tp>((float)v); } \ +template<> inline T saturate_cast(uchar v) { return T((float)v); } \ +template<> inline T saturate_cast(schar v) { return T((float)v); } \ +template<> inline T saturate_cast(ushort v) { return T((float)v); } \ +template<> inline T saturate_cast(short v) { return T((float)v); } \ +template<> inline T saturate_cast(unsigned v) { return T((float)v); } \ +template<> inline T saturate_cast(int v) { return T((float)v); } \ +template<> inline T saturate_cast(uint64 v) { return T((float)v); } \ +template<> inline T saturate_cast(int64 v) { return T((float)v); } \ +template<> inline T saturate_cast(float v) { return T(v); } \ +template<> inline T saturate_cast(double v) { return T((float)v); } \ +template<> inline T saturate_cast(hfloat v) { return T((float)v); } \ +template<> inline T saturate_cast(bfloat v) { return T((float)v); } \ +template<> inline T saturate_cast(T v) { return v; } \ +template<> inline bool saturate_cast(T v) { return (float)v != 0; } + +CV_FP8_SATURATE_CAST(fp8_t) +CV_FP8_SATURATE_CAST(fp8a_t) +#undef CV_FP8_SATURATE_CAST + //! @} } // cv diff --git a/modules/core/include/opencv2/core/traits.hpp b/modules/core/include/opencv2/core/traits.hpp index a75047b202..53f581fbdf 100644 --- a/modules/core/include/opencv2/core/traits.hpp +++ b/modules/core/include/opencv2/core/traits.hpp @@ -336,6 +336,28 @@ public: }; }; +template<> class DataType +{ +public: + typedef fp8_t value_type; + typedef float work_type; + typedef value_type channel_type; + typedef value_type vec_type; + enum { generic_type = 0, depth = CV_8F_E4M3FN, channels = 1, + fmt = (int)'e', type = CV_MAKETYPE(depth, channels) }; +}; + +template<> class DataType +{ +public: + typedef fp8a_t value_type; + typedef float work_type; + typedef value_type channel_type; + typedef value_type vec_type; + enum { generic_type = 0, depth = CV_8F_E4M3FNUZ, channels = 1, + fmt = (int)'E', type = CV_MAKETYPE(depth, channels) }; +}; + /** @brief A helper class for cv::DataType The class is specialized for each fundamental numerical data type supported by OpenCV. It provides @@ -434,6 +456,18 @@ template<> class TypeDepth typedef bfloat value_type; }; +template<> class TypeDepth +{ + enum { depth = CV_8F_E4M3FN }; + typedef fp8_t value_type; +}; + +template<> class TypeDepth +{ + enum { depth = CV_8F_E4M3FNUZ }; + typedef fp8a_t value_type; +}; + template<> class TypeDepth { enum { depth = CV_Bool }; diff --git a/modules/core/src/check.cpp b/modules/core/src/check.cpp index a6588db3b4..1d3919bdc2 100644 --- a/modules/core/src/check.cpp +++ b/modules/core/src/check.cpp @@ -46,7 +46,8 @@ static const char* getTestOpMath(unsigned testOp) const char* depthToString_(int depth) { static const char* depthNames[] = { "CV_8U", "CV_8S", "CV_16U", "CV_16S", "CV_32S", "CV_32F", "CV_64F", "CV_16F", - "CV_16BF", "CV_Bool", "CV_64U", "CV_64S", "CV_32U" }; + "CV_16BF", "CV_Bool", "CV_64U", "CV_64S", "CV_32U", + "CV_8F", "CV_8FNUZ" }; return (depth < CV_DEPTH_CURR_MAX && depth >= 0) ? depthNames[depth] : NULL; } diff --git a/modules/core/src/convert.dispatch.cpp b/modules/core/src/convert.dispatch.cpp index 66671804cc..23855b944a 100644 --- a/modules/core/src/convert.dispatch.cpp +++ b/modules/core/src/convert.dispatch.cpp @@ -63,6 +63,11 @@ static bool ocl_convertTo(InputArray src_, OutputArray dst_, int ddepth, bool no int sdepth = CV_MAT_DEPTH(stype); int cn = CV_MAT_CN(stype); + // FP8 types are not supported by the OpenCL kernels yet; fall back to CPU. + if ((sdepth >= CV_8F_E4M3FN && sdepth <= CV_8F_E4M3FNUZ) || + (ddepth >= CV_8F_E4M3FN && ddepth <= CV_8F_E4M3FNUZ)) + return false; + int dtype = CV_MAKETYPE(ddepth, cn); int wdepth = (sdepth == CV_64F) ? CV_64F : CV_32F; diff --git a/modules/core/src/convert.simd.hpp b/modules/core/src/convert.simd.hpp index dadc78a194..ce787dd584 100644 --- a/modules/core/src/convert.simd.hpp +++ b/modules/core/src/convert.simd.hpp @@ -508,6 +508,55 @@ static void cvt32s(const uchar* src, size_t sstep, const uchar*, size_t, uchar* static void cvt64s(const uchar* src, size_t sstep, const uchar*, size_t, uchar* dst, size_t dstep, Size size, void*) { CV_INSTRUMENT_REGION(); cvtCopy((const uchar*)src, sstep, (uchar*)dst, dstep, size, 8); } +//////////////////// FP8 (1-byte float) conversions — scalar via saturate_cast //////////////////// +// Suffixes: 8fe4m3 (E4M3FN), 8fe4m3u (E4M3FNUZ). +#define DEF_CVT_FP8(S, T) \ + DEF_CVT_SCALAR_FUNC(S##8u, T, uchar) DEF_CVT_SCALAR_FUNC(8u##S, uchar, T) \ + DEF_CVT_SCALAR_FUNC(S##8s, T, schar) DEF_CVT_SCALAR_FUNC(8s##S, schar, T) \ + DEF_CVT_SCALAR_FUNC(S##16u, T, ushort) DEF_CVT_SCALAR_FUNC(16u##S, ushort, T) \ + DEF_CVT_SCALAR_FUNC(S##16s, T, short) DEF_CVT_SCALAR_FUNC(16s##S, short, T) \ + DEF_CVT_SCALAR_FUNC(S##32u, T, unsigned) DEF_CVT_SCALAR_FUNC(32u##S, unsigned, T) \ + DEF_CVT_SCALAR_FUNC(S##32s, T, int) DEF_CVT_SCALAR_FUNC(32s##S, int, T) \ + DEF_CVT_SCALAR_FUNC(32f##S, float, T) \ + DEF_CVT_SCALAR_FUNC(S##64f, T, double) DEF_CVT_SCALAR_FUNC(64f##S, double, T) \ + DEF_CVT_SCALAR_FUNC(S##16f, T, hfloat) DEF_CVT_SCALAR_FUNC(16f##S, hfloat, T) \ + DEF_CVT_SCALAR_FUNC(S##16bf, T, bfloat) DEF_CVT_SCALAR_FUNC(16bf##S, bfloat, T) \ + DEF_CVT_SCALAR_FUNC(S##64u, T, uint64_t) DEF_CVT_SCALAR_FUNC(64u##S, uint64_t, T) \ + DEF_CVT_SCALAR_FUNC(S##64s, T, int64_t) DEF_CVT_SCALAR_FUNC(64s##S, int64_t, T) \ + DEF_CVT_SCALAR_FUNC(S##8b, T, bool) + +DEF_CVT_FP8(8fe4m3, fp8_t) +DEF_CVT_FP8(8fe4m3u, fp8a_t) + +// FP8 -> FP8, cross-format only (identity uses the 1-byte copy cvt8u, like 16f->16f uses cvt16u) +DEF_CVT_SCALAR_FUNC(8fe4m38fe4m3u, fp8_t, fp8a_t) +DEF_CVT_SCALAR_FUNC(8fe4m3u8fe4m3, fp8a_t, fp8_t) + +// FP8 -> float32: 256-entry decode table gathered via universal intrinsics (same table as scalar) +template +static void cvtFp8ToF32(const uchar* src, size_t sstep, const uchar*, size_t, + uchar* dst_, size_t dstep, Size size, void*) +{ + CV_INSTRUMENT_REGION(); + float* dst = (float*)dst_; + const float* tab = FP8::decodeLUT(); + dstep /= sizeof(dst[0]); + for (int i = 0; i < size.height; i++, src += sstep, dst += dstep) + { + int j = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + const int VECSZ = VTraits::vlanes(); + for (; j <= size.width - VECSZ; j += VECSZ) + v_store(dst + j, v_lut(tab, v_reinterpret_as_s32(vx_load_expand_q(src + j)))); +#endif + for (; j < size.width; j++) dst[j] = tab[src[j]]; + } +} +static void cvt8fe4m332f(const uchar* s, size_t ss, const uchar* p, size_t ps, uchar* d, size_t ds, Size sz, void* x) +{ cvtFp8ToF32(s, ss, p, ps, d, ds, sz, x); } +static void cvt8fe4m3u32f(const uchar* s, size_t ss, const uchar* p, size_t ps, uchar* d, size_t ds, Size sz, void* x) +{ cvtFp8ToF32(s, ss, p, ps, d, ds, sz, x); } + BinaryFunc getConvertFunc(int sdepth_, int ddepth_) { int sdepth = CV_MAT_DEPTH(sdepth_); @@ -527,6 +576,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b8u : sdepth == CV_64U ? cvt64u8u : sdepth == CV_64S ? cvt64s8u : + sdepth == CV_8F_E4M3FN ? cvt8fe4m38u : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u8u : 0) : ddepth == CV_8S ? ( sdepth == CV_8U ? cvt8u8s : @@ -542,6 +593,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b8u : sdepth == CV_64U ? cvt64u8s : sdepth == CV_64S ? cvt64s8s : + sdepth == CV_8F_E4M3FN ? cvt8fe4m38s : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u8s : 0) : ddepth == CV_16U ? ( sdepth == CV_8U ? cvt8u16s : // same as cvt8u16u @@ -557,6 +610,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b16s : sdepth == CV_64U ? cvt64u16u : sdepth == CV_64S ? cvt64s16u : + sdepth == CV_8F_E4M3FN ? cvt8fe4m316u : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u16u : 0) : ddepth == CV_16S ? ( sdepth == CV_8U ? cvt8u16s : @@ -572,6 +627,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b16s : sdepth == CV_64U ? cvt64u16s : sdepth == CV_64S ? cvt64s16s : + sdepth == CV_8F_E4M3FN ? cvt8fe4m316s : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u16s : 0) : ddepth == CV_32U ? ( sdepth == CV_8U ? cvt8u32s : // same as cvt8u32u @@ -587,7 +644,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b32s : sdepth == CV_64U ? cvt64u32u : sdepth == CV_64S ? cvt64s32u : - + sdepth == CV_8F_E4M3FN ? cvt8fe4m332u : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u32u : 0) : ddepth == CV_32S ? ( sdepth == CV_8U ? cvt8u32s : @@ -603,6 +661,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b32s : sdepth == CV_64U ? cvt64u32s : sdepth == CV_64S ? cvt64s32s : + sdepth == CV_8F_E4M3FN ? cvt8fe4m332s : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u32s : 0) : ddepth == CV_32F ? ( sdepth == CV_8U ? cvt8u32f : @@ -618,6 +678,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b32f : sdepth == CV_64U ? cvt64u32f : sdepth == CV_64S ? cvt64s32f : + sdepth == CV_8F_E4M3FN ? cvt8fe4m332f : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u32f : 0) : ddepth == CV_64F ? ( sdepth == CV_8U ? cvt8u64f : @@ -633,6 +695,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b64f : sdepth == CV_64U ? cvt64u64f : sdepth == CV_64S ? cvt64s64f : + sdepth == CV_8F_E4M3FN ? cvt8fe4m364f : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u64f : 0) : ddepth == CV_16F ? ( sdepth == CV_8U ? cvt8u16f : @@ -648,6 +712,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b16f : sdepth == CV_64U ? cvt64u16f : sdepth == CV_64S ? cvt64s16f : + sdepth == CV_8F_E4M3FN ? cvt8fe4m316f : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u16f : 0) : ddepth == CV_16BF ? ( sdepth == CV_8U ? cvt8u16bf : @@ -663,6 +729,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b16bf : sdepth == CV_64U ? cvt64u16bf : sdepth == CV_64S ? cvt64s16bf : + sdepth == CV_8F_E4M3FN ? cvt8fe4m316bf : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u16bf : 0) : ddepth == CV_Bool ? ( sdepth == CV_8U ? cvt8u8b : @@ -678,6 +746,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8u : sdepth == CV_64U ? cvt64s8b : sdepth == CV_64S ? cvt64s8b : + sdepth == CV_8F_E4M3FN ? cvt8fe4m38b : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u8b : 0) : ddepth == CV_64U ? ( sdepth == CV_8U ? cvt8u64s : // same as cvt8u64u @@ -693,6 +763,8 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b64s : sdepth == CV_64U ? cvt64s : sdepth == CV_64S ? cvt64s64u : + sdepth == CV_8F_E4M3FN ? cvt8fe4m364u : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u64u : 0) : ddepth == CV_64S ? ( sdepth == CV_8U ? cvt8u64s : @@ -708,6 +780,42 @@ BinaryFunc getConvertFunc(int sdepth_, int ddepth_) sdepth == CV_Bool ? cvt8b64s : sdepth == CV_64U ? cvt64s : sdepth == CV_64S ? cvt64s : + sdepth == CV_8F_E4M3FN ? cvt8fe4m364s : + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u64s : + 0) : + ddepth == CV_8F_E4M3FN ? ( + sdepth == CV_8U ? cvt8u8fe4m3 : + sdepth == CV_8S ? cvt8s8fe4m3 : + sdepth == CV_16U ? cvt16u8fe4m3 : + sdepth == CV_16S ? cvt16s8fe4m3 : + sdepth == CV_32U ? cvt32u8fe4m3 : + sdepth == CV_32S ? cvt32s8fe4m3 : + sdepth == CV_32F ? cvt32f8fe4m3 : + sdepth == CV_64F ? cvt64f8fe4m3 : + sdepth == CV_16F ? cvt16f8fe4m3 : + sdepth == CV_16BF ? cvt16bf8fe4m3 : + sdepth == CV_Bool ? cvt8u8fe4m3 : // bool stored as 0/1 byte -> reuse uchar path + sdepth == CV_64U ? cvt64u8fe4m3 : + sdepth == CV_64S ? cvt64s8fe4m3 : + sdepth == CV_8F_E4M3FN ? cvt8u : // identity: 1-byte copy + sdepth == CV_8F_E4M3FNUZ ? cvt8fe4m3u8fe4m3 : + 0) : + ddepth == CV_8F_E4M3FNUZ ? ( + sdepth == CV_8U ? cvt8u8fe4m3u : + sdepth == CV_8S ? cvt8s8fe4m3u : + sdepth == CV_16U ? cvt16u8fe4m3u : + sdepth == CV_16S ? cvt16s8fe4m3u : + sdepth == CV_32U ? cvt32u8fe4m3u : + sdepth == CV_32S ? cvt32s8fe4m3u : + sdepth == CV_32F ? cvt32f8fe4m3u : + sdepth == CV_64F ? cvt64f8fe4m3u : + sdepth == CV_16F ? cvt16f8fe4m3u : + sdepth == CV_16BF ? cvt16bf8fe4m3u : + sdepth == CV_Bool ? cvt8u8fe4m3u : // bool stored as 0/1 byte -> reuse uchar path + sdepth == CV_64U ? cvt64u8fe4m3u : + sdepth == CV_64S ? cvt64s8fe4m3u : + sdepth == CV_8F_E4M3FN ? cvt8fe4m38fe4m3u : + sdepth == CV_8F_E4M3FNUZ ? cvt8u : // identity: 1-byte copy 0) : 0; CV_Assert(func != 0); diff --git a/modules/core/src/convert_scale.simd.hpp b/modules/core/src/convert_scale.simd.hpp index 962f0c1a49..9db95a50b5 100644 --- a/modules/core/src/convert_scale.simd.hpp +++ b/modules/core/src/convert_scale.simd.hpp @@ -22,9 +22,17 @@ template inline void cvtabs_32f( const _Ts* src, size_t sstep, _Td* dst, size_t dstep, Size size, float a, float b ) { -#if (CV_SIMD || CV_SIMD_SCALABLE) +// Excluding GNU in CV_SIMD_SCALABLE because of "opencv/issues/26936" +#if (CV_SIMD || (CV_SIMD_SCALABLE && !(defined(__GNUC__) && !defined(__clang__))) ) v_float32 va = vx_setall_f32(a), vb = vx_setall_f32(b); const int VECSZ = VTraits::vlanes()*2; + // GCC miscompiles this scalable block only on VLEN=128 RVV (opencv/issues/26936). + // v_float32 is LMUL=2, so vlanes()==8 means VLEN==128: use scalar there, keep SIMD elsewhere. +#if (CV_SIMD_SCALABLE && defined(__GNUC__) && !defined(__clang__)) + const bool useSIMD = VTraits::vlanes() != 8; +#else + const bool useSIMD = true; +#endif #endif sstep /= sizeof(src[0]); dstep /= sizeof(dst[0]); @@ -33,6 +41,7 @@ cvtabs_32f( const _Ts* src, size_t sstep, _Td* dst, size_t dstep, { int j = 0; #if (CV_SIMD || CV_SIMD_SCALABLE) + if( useSIMD ) for( ; j < size.width; j += VECSZ ) { if( j > size.width - VECSZ ) @@ -423,9 +432,27 @@ DEF_CVT_SCALEBOOL2_FUNC(8b64f, double, double) DEF_CVT_SCALEBOOL2_FUNC(8b16f, hfloat, float) DEF_CVT_SCALEBOOL2_FUNC(8b16bf, bfloat, float) +// FP8 src -> CV_8U abs|scale|shift (scalar) +#define DEF_CVT_SCALE_ABS_SCALAR_FUNC(suffix, stype) \ +static void cvtScaleAbs##suffix( const uchar* src_, size_t sstep, const uchar*, size_t, \ + uchar* dst, size_t dstep, Size size, void* scale_) \ +{ \ + const stype* src = (const stype*)src_; \ + const double* scale = (const double*)scale_; \ + float a = (float)scale[0], b = (float)scale[1]; \ + sstep /= sizeof(src[0]); \ + for( int i = 0; i < size.height; i++, src += sstep, dst += dstep ) \ + for (int j = 0; j < size.width; j++) \ + dst[j] = saturate_cast(std::abs((float)src[j]*a + b)); \ +} +DEF_CVT_SCALE_ABS_SCALAR_FUNC(8fe4m3u8u, fp8a_t) +DEF_CVT_SCALE_ABS_SCALAR_FUNC(8fe4m38u, fp8_t) + BinaryFunc getCvtScaleAbsFunc(int depth) { BinaryFunc func = + depth == CV_8F_E4M3FN ? (BinaryFunc)cvtScaleAbs8fe4m38u : + depth == CV_8F_E4M3FNUZ ? (BinaryFunc)cvtScaleAbs8fe4m3u8u : depth == CV_8U ? (BinaryFunc)cvtScaleAbs8u : depth == CV_8S ? (BinaryFunc)cvtScaleAbs8s8u : depth == CV_Bool ? (BinaryFunc)cvtScaleAbs8b8u : @@ -443,6 +470,40 @@ BinaryFunc getCvtScaleAbsFunc(int depth) return func; } +//////////////////// FP8 scaled conversions — scalar via saturate_cast(src*a+b) //////////////////// +#define DEF_CVT_SCALE_SCALAR_FUNC(suffix, stype, dtype, wtype) \ +static void cvtScale##suffix( const uchar* src_, size_t sstep, const uchar*, size_t, \ + uchar* dst_, size_t dstep, Size size, void* scale_) \ +{ \ + const stype* src = (const stype*)src_; dtype* dst = (dtype*)dst_; \ + const double* scale = (const double*)scale_; \ + wtype a = (wtype)scale[0], b = (wtype)scale[1]; \ + sstep /= sizeof(src[0]); dstep /= sizeof(dst[0]); \ + for( int i = 0; i < size.height; i++, src += sstep, dst += dstep ) \ + for (int j = 0; j < size.width; j++) \ + dst[j] = saturate_cast((wtype)src[j]*a + b); \ +} + +// double intermediate so coarse FP8 grids round identically to the (double-precision) reference +#define DEF_CVT_SCALE_FP8(S, T) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##8u, T, uchar, double) DEF_CVT_SCALE_SCALAR_FUNC(8u##S, uchar, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##8s, T, schar, double) DEF_CVT_SCALE_SCALAR_FUNC(8s##S, schar, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##16u, T, ushort, double) DEF_CVT_SCALE_SCALAR_FUNC(16u##S, ushort, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##16s, T, short, double) DEF_CVT_SCALE_SCALAR_FUNC(16s##S, short, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##32u, T, unsigned, double) DEF_CVT_SCALE_SCALAR_FUNC(32u##S, unsigned, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##32s, T, int, double) DEF_CVT_SCALE_SCALAR_FUNC(32s##S, int, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##32f, T, float, double) DEF_CVT_SCALE_SCALAR_FUNC(32f##S, float, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##64f, T, double, double) DEF_CVT_SCALE_SCALAR_FUNC(64f##S, double, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##16f, T, hfloat, double) DEF_CVT_SCALE_SCALAR_FUNC(16f##S, hfloat, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##16bf, T, bfloat, double) DEF_CVT_SCALE_SCALAR_FUNC(16bf##S, bfloat, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##64u, T, uint64_t, double) DEF_CVT_SCALE_SCALAR_FUNC(64u##S, uint64_t, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##64s, T, int64_t, double) DEF_CVT_SCALE_SCALAR_FUNC(64s##S, int64_t, T, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##8b, T, bool, double) \ + DEF_CVT_SCALE_SCALAR_FUNC(S##8fe4m3, T, fp8_t, double) DEF_CVT_SCALE_SCALAR_FUNC(S##8fe4m3u, T, fp8a_t, double) + +DEF_CVT_SCALE_FP8(8fe4m3, fp8_t) +DEF_CVT_SCALE_FP8(8fe4m3u, fp8a_t) + BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) { int sdepth = CV_MAT_DEPTH(sdepth_); @@ -462,6 +523,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf8u : sdepth == CV_64U ? cvtScale64u8u : sdepth == CV_64S ? cvtScale64s8u : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m38u : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u8u : 0) : ddepth == CV_8S ? ( sdepth == CV_8U ? cvtScale8u8s : @@ -477,6 +540,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf8s : sdepth == CV_64U ? cvtScale64u8s : sdepth == CV_64S ? cvtScale64s8s : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m38s : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u8s : 0) : ddepth == CV_16U ? ( sdepth == CV_8U ? cvtScale8u16u : @@ -492,6 +557,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf16u : sdepth == CV_64U ? cvtScale64u16u : sdepth == CV_64S ? cvtScale64s16u : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m316u : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u16u : 0) : ddepth == CV_16S ? ( sdepth == CV_8U ? cvtScale8u16s : @@ -507,6 +574,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf16s : sdepth == CV_64U ? cvtScale64u16s : sdepth == CV_64S ? cvtScale64s16s : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m316s : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u16s : 0) : ddepth == CV_32U ? ( sdepth == CV_8U ? cvtScale8u32u : @@ -522,7 +591,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf32u : sdepth == CV_64U ? cvtScale64u32u : sdepth == CV_64S ? cvtScale64s32u : - + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m332u : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u32u : 0) : ddepth == CV_32S ? ( sdepth == CV_8U ? cvtScale8u32s : @@ -538,6 +608,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf32s : sdepth == CV_64U ? cvtScale64u32s : sdepth == CV_64S ? cvtScale64s32s : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m332s : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u32s : 0) : ddepth == CV_32F ? ( sdepth == CV_8U ? cvtScale8u32f : @@ -553,6 +625,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf32f : sdepth == CV_64U ? cvtScale64u32f : sdepth == CV_64S ? cvtScale64s32f : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m332f : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u32f : 0) : ddepth == CV_64F ? ( sdepth == CV_8U ? cvtScale8u64f : @@ -568,6 +642,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf64f : sdepth == CV_64U ? cvtScale64u64f : sdepth == CV_64S ? cvtScale64s64f : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m364f : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u64f : 0) : ddepth == CV_16F ? ( sdepth == CV_8U ? cvtScale8u16f : @@ -583,6 +659,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf16f : sdepth == CV_64U ? cvtScale64u16f : sdepth == CV_64S ? cvtScale64s16f : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m316f : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u16f : 0) : ddepth == CV_16BF ? ( sdepth == CV_8U ? cvtScale8u16bf : @@ -598,6 +676,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf : sdepth == CV_64U ? cvtScale64u16bf : sdepth == CV_64S ? cvtScale64s16bf : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m316bf : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u16bf : 0) : ddepth == CV_Bool ? ( sdepth == CV_8U ? cvtScale8u8b : @@ -613,6 +693,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf8b : sdepth == CV_64U ? cvtScale64u8b : sdepth == CV_64S ? cvtScale64s8b : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m38b : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u8b : 0) : ddepth == CV_64U ? ( sdepth == CV_8U ? cvtScale8u64u : @@ -628,6 +710,8 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf64u : sdepth == CV_64U ? cvtScale64u : sdepth == CV_64S ? cvtScale64s64u : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m364u : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u64u : 0) : ddepth == CV_64S ? ( sdepth == CV_8U ? cvtScale8u64s : @@ -643,6 +727,42 @@ BinaryFunc getConvertScaleFunc(int sdepth_, int ddepth_) sdepth == CV_16BF ? cvtScale16bf64s : sdepth == CV_64U ? cvtScale64u64s : sdepth == CV_64S ? cvtScale64s : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m364s : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u64s : + 0) : + ddepth == CV_8F_E4M3FN ? ( + sdepth == CV_8U ? cvtScale8u8fe4m3 : + sdepth == CV_8S ? cvtScale8s8fe4m3 : + sdepth == CV_Bool ? cvtScale8u8fe4m3 : // bool stored as 0/1 byte -> reuse uchar path + sdepth == CV_16U ? cvtScale16u8fe4m3 : + sdepth == CV_16S ? cvtScale16s8fe4m3 : + sdepth == CV_32U ? cvtScale32u8fe4m3 : + sdepth == CV_32S ? cvtScale32s8fe4m3 : + sdepth == CV_32F ? cvtScale32f8fe4m3 : + sdepth == CV_64F ? cvtScale64f8fe4m3 : + sdepth == CV_16F ? cvtScale16f8fe4m3 : + sdepth == CV_16BF ? cvtScale16bf8fe4m3 : + sdepth == CV_64U ? cvtScale64u8fe4m3 : + sdepth == CV_64S ? cvtScale64s8fe4m3 : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m38fe4m3 : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u8fe4m3 : + 0) : + ddepth == CV_8F_E4M3FNUZ ? ( + sdepth == CV_8U ? cvtScale8u8fe4m3u : + sdepth == CV_8S ? cvtScale8s8fe4m3u : + sdepth == CV_Bool ? cvtScale8u8fe4m3u : // bool stored as 0/1 byte -> reuse uchar path + sdepth == CV_16U ? cvtScale16u8fe4m3u : + sdepth == CV_16S ? cvtScale16s8fe4m3u : + sdepth == CV_32U ? cvtScale32u8fe4m3u : + sdepth == CV_32S ? cvtScale32s8fe4m3u : + sdepth == CV_32F ? cvtScale32f8fe4m3u : + sdepth == CV_64F ? cvtScale64f8fe4m3u : + sdepth == CV_16F ? cvtScale16f8fe4m3u : + sdepth == CV_16BF ? cvtScale16bf8fe4m3u : + sdepth == CV_64U ? cvtScale64u8fe4m3u : + sdepth == CV_64S ? cvtScale64s8fe4m3u : + sdepth == CV_8F_E4M3FN ? cvtScale8fe4m38fe4m3u : + sdepth == CV_8F_E4M3FNUZ ? cvtScale8fe4m3u8fe4m3u : 0) : 0; CV_Assert(func != 0); diff --git a/modules/core/src/copy.cpp b/modules/core/src/copy.cpp index 61f7d57457..a0a98faa71 100644 --- a/modules/core/src/copy.cpp +++ b/modules/core/src/copy.cpp @@ -92,6 +92,12 @@ void scalarToRawData(const Scalar& s, void* _buf, int type, int unroll_to) case CV_16BF: scalarToRawData_(s, (bfloat*)_buf, cn, unroll_to); break; + case CV_8F_E4M3FN: + scalarToRawData_(s, (fp8_t*)_buf, cn, unroll_to); + break; + case CV_8F_E4M3FNUZ: + scalarToRawData_(s, (fp8a_t*)_buf, cn, unroll_to); + break; case CV_32U: scalarToRawData_(s, (unsigned*)_buf, cn, unroll_to); break; diff --git a/modules/core/src/cuda/gpu_mat.cu b/modules/core/src/cuda/gpu_mat.cu index f6878f3bee..e774de61be 100644 --- a/modules/core/src/cuda/gpu_mat.cu +++ b/modules/core/src/cuda/gpu_mat.cu @@ -575,7 +575,10 @@ void cv::cuda::GpuMat::convertTo(OutputArray _dst, int rtype, Stream& stream) co {convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, convertToNoScale, 0, 0, 0, convertToNoScale, convertToNoScale, 0}, }; - funcs[sdepth][ddepth](src.reshape(1), dst.reshape(1), stream); + const func_t func = funcs[sdepth][ddepth]; + CV_Assert(func); + + func(src.reshape(1), dst.reshape(1), stream); } void cv::cuda::GpuMat::convertTo(OutputArray _dst, int rtype, double alpha, double beta, Stream& stream) const diff --git a/modules/core/src/persistence.cpp b/modules/core/src/persistence.cpp index 8256ebd7b5..826168954b 100644 --- a/modules/core/src/persistence.cpp +++ b/modules/core/src/persistence.cpp @@ -178,12 +178,12 @@ char* floatToString( char* buf, size_t bufSize, float value, bool halfprecision, return buf; } -static const char symbols[] = "ucwsifdhHbUIn"; +static const char symbols[] = "ucwsifdhHbUIneE"; static char typeSymbol(int depth) { CV_StaticAssert(CV_64F == 6, ""); - CV_CheckDepth(depth, depth >= 0 && depth <= CV_32U, ""); + CV_CheckDepth(depth, depth >= 0 && depth <= CV_8F_E4M3FNUZ, ""); return symbols[depth]; } @@ -310,6 +310,8 @@ int calcStructSize( const char* dt, int initial_size ) case 'd': { elem_max_size = std::max( elem_max_size, sizeof(double) ); break; } case 'h': { elem_max_size = std::max( elem_max_size, sizeof(hfloat)); break; } case 'H': { elem_max_size = std::max( elem_max_size, sizeof(bfloat)); break; } + case 'e': { elem_max_size = std::max( elem_max_size, sizeof(fp8_t) ); break; } + case 'E': { elem_max_size = std::max( elem_max_size, sizeof(fp8a_t)); break; } case 'I': { elem_max_size = std::max( elem_max_size, sizeof(int64_t)); break; } case 'U': { elem_max_size = std::max( elem_max_size, sizeof(uint64_t)); break; } default: @@ -1229,6 +1231,14 @@ void FileStorage::Impl::writeRawData(const std::string &dt, const void *_data, s ptr = fs::floatToString(buf, sizeof(buf), (float) *(bfloat *) data, true, explicitZero); data += sizeof(bfloat); break; + case CV_8F_E4M3FN: + ptr = fs::floatToString(buf, sizeof(buf), (float) *(fp8_t *) data, true, explicitZero); + data += sizeof(fp8_t); + break; + case CV_8F_E4M3FNUZ: + ptr = fs::floatToString(buf, sizeof(buf), (float) *(fp8a_t *) data, true, explicitZero); + data += sizeof(fp8a_t); + break; default: CV_Error(cv::Error::StsUnsupportedFormat, "Unsupported type"); return; @@ -1930,6 +1940,14 @@ char *FileStorage::Impl::parseBase64(char *ptr, int indent, FileNode &collection fval = float(hfloatFromBits(base64decoder.getUInt16())); node_type = FileNode::REAL; break; + case CV_8F_E4M3FN: + fval = fp8_t::decodeLUT()[base64decoder.getUInt8()]; + node_type = FileNode::REAL; + break; + case CV_8F_E4M3FNUZ: + fval = fp8a_t::decodeLUT()[base64decoder.getUInt8()]; + node_type = FileNode::REAL; + break; default: CV_Error(Error::StsUnsupportedFormat, "Unsupported type"); } @@ -2780,6 +2798,14 @@ FileNodeIterator& FileNodeIterator::readRaw( const String& fmt, void* _data0, si *(bfloat*)data = bfloat((float)ival); data += sizeof(bfloat); break; + case CV_8F_E4M3FN: + *(fp8_t*)data = fp8_t((float)ival); + data += sizeof(fp8_t); + break; + case CV_8F_E4M3FNUZ: + *(fp8a_t*)data = fp8a_t((float)ival); + data += sizeof(fp8a_t); + break; default: CV_Error( Error::StsUnsupportedFormat, "Unsupported type" ); } @@ -2838,6 +2864,14 @@ FileNodeIterator& FileNodeIterator::readRaw( const String& fmt, void* _data0, si *(bfloat*)data = bfloat((float)fval); data += sizeof(bfloat); break; + case CV_8F_E4M3FN: + *(fp8_t*)data = fp8_t((float)fval); + data += sizeof(fp8_t); + break; + case CV_8F_E4M3FNUZ: + *(fp8a_t*)data = fp8a_t((float)fval); + data += sizeof(fp8a_t); + break; default: CV_Error( Error::StsUnsupportedFormat, "Unsupported type" ); } diff --git a/modules/core/src/persistence_base64_encoding.cpp b/modules/core/src/persistence_base64_encoding.cpp index c2d81b94f6..ac56304db1 100644 --- a/modules/core/src/persistence_base64_encoding.cpp +++ b/modules/core/src/persistence_base64_encoding.cpp @@ -212,6 +212,8 @@ int base64::icvCalcStructSize(const char *dt, int initial_size) { case 'd': { elem_max_size = std::max( elem_max_size, sizeof(double) ); break; } case 'I': { elem_max_size = std::max( elem_max_size, sizeof(int64_t)); break; } case 'U': { elem_max_size = std::max( elem_max_size, sizeof(uint64_t)); break; } + case 'e': { elem_max_size = std::max( elem_max_size, sizeof(uchar) ); break; } + case 'E': { elem_max_size = std::max( elem_max_size, sizeof(uchar) ); break; } default: break; } } @@ -361,6 +363,11 @@ size_t base64::RawDataToBinaryConvertor::make_to_binary_funcs(const std::string size = sizeof(uint64_t); pack.func = to_binary; break; + case 'e': + case 'E': + size = sizeof(uchar); + pack.func = to_binary; + break; case 'r': default: CV_Error(cv::Error::StsError, "type is not supported"); diff --git a/modules/core/src/rand.cpp b/modules/core/src/rand.cpp index 604d3f6ac0..69e4a0ba69 100644 --- a/modules/core/src/rand.cpp +++ b/modules/core/src/rand.cpp @@ -232,12 +232,20 @@ DEF_RANDI_FUNC(32s, int) DEF_RANDI_FUNC(64u, uint64_t) DEF_RANDI_FUNC(64s, int64_t) +// Narrow an f32 buffer into one of the 1-byte FP8 destinations. +static inline void cvt32fToFP8(const float* src, void* dst, int len, int depth) +{ + if (depth == CV_8F_E4M3FN) { fp8_t* d = (fp8_t*)dst; for (int i = 0; i < len; i++) d[i] = fp8_t(src[i]); } + else { fp8a_t* d = (fp8a_t*)dst; for (int i = 0; i < len; i++) d[i] = fp8a_t(src[i]); } +} +static inline bool isFP8Depth(int d) { return d >= CV_8F_E4M3FN && d <= CV_8F_E4M3FNUZ; } + static void randf_16_or_32f( void* dst, int len_, int cn, uint64* state, const Vec2f* p, float* fbuf, int flags ) { int depth = CV_MAT_DEPTH(flags); uint64 temp = *state; int k = 0, len = len_*cn; - float* arr = depth == CV_16F || depth == CV_16BF ? fbuf : (float*)dst; + float* arr = depth == CV_16F || depth == CV_16BF || isFP8Depth(depth) ? fbuf : (float*)dst; cn--; for( int i = 0; i < len; i++ ) { @@ -251,6 +259,8 @@ static void randf_16_or_32f( void* dst, int len_, int cn, uint64* state, const V hal::cvt32f16f(fbuf, (hfloat*)dst, len); else if (depth == CV_16BF) hal::cvt32f16bf(fbuf, (bfloat*)dst, len); + else if (isFP8Depth(depth)) + cvt32fToFP8(fbuf, dst, len, depth); } static void @@ -280,7 +290,8 @@ static RandFunc randTab[CV_DEPTH_MAX][CV_DEPTH_MAX] = (RandFunc)randi_16s, (RandFunc)randi_32s, (RandFunc)randf_16_or_32f, (RandFunc)randf_64f, (RandFunc)randf_16_or_32f, (RandFunc)randf_16_or_32f, (RandFunc)randi_8b, (RandFunc)randi_64u, (RandFunc)randi_64s, - (RandFunc)randi_32u, 0, 0, 0 + (RandFunc)randi_32u, + (RandFunc)randf_16_or_32f, (RandFunc)randf_16_or_32f // CV_8F_E4M3FN, E4M3FNUZ }, { (RandFunc)randBits_8u, (RandFunc)randBits_8s, (RandFunc)randBits_16u, @@ -425,7 +436,7 @@ randnScale_16_or_32f(float* fbuf, float* dst, int len, int cn, { bool stdmtx = (flags & RNG_FLAG_STDMTX) != 0; int depth = CV_MAT_DEPTH(flags); - float* arr = depth == CV_16F || depth == CV_16BF ? fbuf : dst; + float* arr = depth == CV_16F || depth == CV_16BF || isFP8Depth(depth) ? fbuf : dst; int i, j, k; if( !stdmtx || cn == 1 ) @@ -483,6 +494,8 @@ randnScale_16_or_32f(float* fbuf, float* dst, int len, int cn, hal::cvt32f16f(fbuf, (hfloat*)dst, len); else if (depth == CV_16BF) hal::cvt32f16bf(fbuf, (bfloat*)dst, len); + else if (isFP8Depth(depth)) + cvt32fToFP8(fbuf, dst, len, depth); } #define DEF_RANDNSCALE_FUNC(suffix, T, PT) \ @@ -510,7 +523,8 @@ static RandnScaleFunc randnScaleTab[CV_DEPTH_MAX] = (RandnScaleFunc)randnScale_16s, (RandnScaleFunc)randnScale_32s, (RandnScaleFunc)randnScale_16_or_32f, (RandnScaleFunc)randnScale_64f, (RandnScaleFunc)randnScale_16_or_32f, (RandnScaleFunc)randnScale_16_or_32f, (RandnScaleFunc)randnScale_8b, (RandnScaleFunc)randnScale_64u, (RandnScaleFunc)randnScale_64s, - (RandnScaleFunc)randnScale_32u, 0, 0, 0 + (RandnScaleFunc)randnScale_32u, + (RandnScaleFunc)randnScale_16_or_32f, (RandnScaleFunc)randnScale_16_or_32f // CV_8F_E4M3FN, E4M3FNUZ }; void RNG::fill( InputOutputArray _mat, int disttype, diff --git a/modules/core/test/test_arithm.cpp b/modules/core/test/test_arithm.cpp index 1e1de3233d..8760b308ec 100644 --- a/modules/core/test/test_arithm.cpp +++ b/modules/core/test/test_arithm.cpp @@ -13,6 +13,15 @@ const int ARITHM_MAX_CHANNELS = 4; const int ARITHM_MAX_NDIMS = 4; const int ARITHM_MAX_SIZE_LOG = 10; +// fp8 (E4M3) is excluded from the tolerance-checked element-wise pool: its 3-bit +// mantissa can't meet these tests' error bounds and out-of-range inputs overflow to +// NaN. fp8 conversion/arithmetic is covered directly in test_fp8.cpp. +static const _OutputArray::DepthMask DEPTH_MASK_ALL_NO_FP8 = + _OutputArray::DepthMask(_OutputArray::DEPTH_MASK_ALL & + ~((1 << CV_8F_E4M3FN) | (1 << CV_8F_E4M3FNUZ))); +static const _OutputArray::DepthMask DEPTH_MASK_ALL_BUT_8S_NO_FP8 = + _OutputArray::DepthMask(DEPTH_MASK_ALL_NO_FP8 & ~_OutputArray::DEPTH_MASK_8S); + struct BaseElemWiseOp { enum @@ -41,7 +50,7 @@ struct BaseElemWiseOp virtual int getRandomType(RNG& rng) { - return cvtest::randomType(rng, _OutputArray::DEPTH_MASK_ALL_BUT_8S, 1, + return cvtest::randomType(rng, DEPTH_MASK_ALL_BUT_8S_NO_FP8, 1, ninputs > 1 ? ARITHM_MAX_CHANNELS : 4); } @@ -895,8 +904,8 @@ struct ConvertScaleOp : public BaseElemWiseOp } int getRandomType(RNG& rng) { - int srctype = cvtest::randomType(rng, _OutputArray::DEPTH_MASK_ALL, 1, ARITHM_MAX_CHANNELS); - ddepth = cvtest::randomType(rng, _OutputArray::DEPTH_MASK_ALL, 1, 1); + int srctype = cvtest::randomType(rng, DEPTH_MASK_ALL_NO_FP8, 1, ARITHM_MAX_CHANNELS); + ddepth = cvtest::randomType(rng, DEPTH_MASK_ALL_NO_FP8, 1, 1); return srctype; } double getMaxErr(int) @@ -994,7 +1003,7 @@ struct ConvertScaleAbsOp : public BaseElemWiseOp } int getRandomType(RNG& rng) { - return cvtest::randomType(rng, _OutputArray::DEPTH_MASK_ALL, 1, + return cvtest::randomType(rng, DEPTH_MASK_ALL_NO_FP8, 1, ninputs > 1 ? ARITHM_MAX_CHANNELS : 4); } double getMaxErr(int) diff --git a/modules/core/test/test_cuda.cpp b/modules/core/test/test_cuda.cpp index 8d0f140526..e249203048 100755 --- a/modules/core/test/test_cuda.cpp +++ b/modules/core/test/test_cuda.cpp @@ -21,9 +21,9 @@ TEST_P(GpuMat, convertTo) { int sdepth = get<0>(GetParam()); int ddepth = get<1>(GetParam()); - if (sdepth == CV_16F || sdepth == CV_Bool || sdepth == CV_16BF) + if (sdepth == CV_16F || sdepth == CV_Bool || sdepth == CV_16BF || (sdepth >= CV_8F_E4M3FN && sdepth <= CV_8F_E4M3FNUZ)) throw SkipTestException("Unsupported src type"); - if (ddepth == CV_16F || ddepth == CV_Bool || ddepth == CV_16BF) + if (ddepth == CV_16F || ddepth == CV_Bool || ddepth == CV_16BF || (ddepth >= CV_8F_E4M3FN && ddepth <= CV_8F_E4M3FNUZ)) throw SkipTestException("Unsupported dst type"); Mat ref(16, 20, CV_8U), testMat; @@ -47,9 +47,9 @@ TEST_P(GpuMat, convertToScale) { int sdepth = get<0>(GetParam()); int ddepth = get<1>(GetParam()); - if (sdepth == CV_16F || sdepth == CV_Bool || sdepth == CV_16BF) + if (sdepth == CV_16F || sdepth == CV_Bool || sdepth == CV_16BF || (sdepth >= CV_8F_E4M3FN && sdepth <= CV_8F_E4M3FNUZ)) throw SkipTestException("Unsupported src type"); - if (ddepth == CV_16F || ddepth == CV_Bool || ddepth == CV_16BF) + if (ddepth == CV_16F || ddepth == CV_Bool || ddepth == CV_16BF || (ddepth >= CV_8F_E4M3FN && ddepth <= CV_8F_E4M3FNUZ)) throw SkipTestException("Unsupported dst type"); Mat ref(16, 20, CV_8U), testMat; diff --git a/modules/core/test/test_fp8.cpp b/modules/core/test/test_fp8.cpp new file mode 100644 index 0000000000..2083411c54 --- /dev/null +++ b/modules/core/test/test_fp8.cpp @@ -0,0 +1,168 @@ +// 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 { + +// The two FP8 depths and their wrapper types share one set of expectations. +// Values chosen to be exactly representable (so round-trips are bit-exact) plus +// the special/overflow cases that distinguish the formats. + +TEST(Core_FP8, type_basics) +{ + const int depths[] = { CV_8F_E4M3FN, CV_8F_E4M3FNUZ }; + for (int d : depths) + { + EXPECT_EQ(CV_ELEM_SIZE1(d), 1) << "depth " << d; + Mat m(3, 4, CV_MAKETYPE(d, 1)); + EXPECT_EQ(m.depth(), d); + EXPECT_EQ(m.channels(), 1); + EXPECT_EQ(m.elemSize(), (size_t)1); + EXPECT_EQ(m.elemSize1(), (size_t)1); + EXPECT_EQ(m.total(), (size_t)12); + // depthToString should not return null for a registered depth + EXPECT_NE(cv::depthToString(d), (const char*)NULL); + } + Mat c3(2, 2, CV_8FC(3)); + EXPECT_EQ(c3.channels(), 3); + EXPECT_EQ(c3.elemSize(), (size_t)3); +} + +TEST(Core_FP8, scalar_roundtrip_exact) +{ + // {0, .5, 1, 1.5, 2, 3, 4, 6} and negatives are exact in every FP8 format here. + const float exact[] = { 0.f, 0.5f, 1.f, 1.5f, 2.f, 3.f, 4.f, 6.f, -2.5f, -0.75f }; + for (float v : exact) + { + EXPECT_EQ((float)cv::fp8_t(v), v) << v; + EXPECT_EQ((float)cv::fp8a_t(v), v) << v; + } + // round-to-nearest-even onto the grid + EXPECT_EQ((float)cv::fp8_t(1.234f), 1.25f); // 3 mantissa bits +} + +TEST(Core_FP8, format_specific_limits) +{ + // max finite values + EXPECT_EQ((float)cv::fp8_t(448.f), 448.f); + EXPECT_EQ((float)cv::fp8a_t(240.f), 240.f); + + // overflow: these formats have no inf -> overflow to NaN + EXPECT_TRUE(cvIsNaN((float)cv::fp8_t(1e6f))); + EXPECT_TRUE(cvIsNaN((float)cv::fp8a_t(1e6f))); + // 448 exceeds the FNUZ E4M3 range (max 240) -> NaN + EXPECT_TRUE(cvIsNaN((float)cv::fp8a_t(448.f))); + + // NaN propagates + EXPECT_TRUE(cvIsNaN((float)cv::fp8_t(std::numeric_limits::quiet_NaN()))); + + // smallest E4M3FN subnormal is 2^-9 + EXPECT_EQ((float)cv::fp8_t(0.001953125f), 0.001953125f); +} + +TEST(Core_FP8, mat_convert_roundtrip) +{ + float vals[] = { 0.f, 0.5f, 1.f, 1.5f, 2.f, 3.f, 4.f, 6.f, -1.f, -4.f }; + Mat f(1, 10, CV_32F, vals); + const int depths[] = { CV_8F_E4M3FN, CV_8F_E4M3FNUZ }; + for (int d : depths) + { + Mat q, back; + f.convertTo(q, d); + EXPECT_EQ(q.depth(), d); + EXPECT_EQ(q.elemSize(), (size_t)1); + q.convertTo(back, CV_32F); + ASSERT_EQ(back.type(), CV_32FC1); + for (int i = 0; i < 10; i++) + EXPECT_EQ(back.at(i), vals[i]) << "depth " << d << " idx " << i; + } +} + +TEST(Core_FP8, convert_from_and_to_other_types) +{ + // f16 -> fp8 -> f32 (f16 source is lossless into the conversion) + Mat f32(1, 5, CV_32F); + float v[] = { 0.5f, 1.f, 2.f, 4.f, -3.f }; + memcpy(f32.data, v, sizeof(v)); + Mat f16; f32.convertTo(f16, CV_16F); + Mat q; f16.convertTo(q, CV_8F_E4M3FN); + Mat back; q.convertTo(back, CV_32F); + for (int i = 0; i < 5; i++) + EXPECT_EQ(back.at(i), v[i]); + + // fp8 -> int (saturate_cast rounds to nearest) + Mat qi; f32.convertTo(qi, CV_8F_E4M3FN); + Mat i32; qi.convertTo(i32, CV_32S); + EXPECT_EQ(i32.at(0), 0); // 0.5 -> 0 (round to even) + EXPECT_EQ(i32.at(1), 1); + EXPECT_EQ(i32.at(2), 2); + EXPECT_EQ(i32.at(3), 4); + EXPECT_EQ(i32.at(4), -3); +} + +TEST(Core_FP8, cross_fp8_conversion) +{ + float v[] = { 0.5f, 1.5f, 6.f, 100.f, -2.f }; + Mat f(1, 5, CV_32F, v); + Mat e4m3, e4m3u, back; + f.convertTo(e4m3, CV_8F_E4M3FN); + e4m3.convertTo(e4m3u, CV_8F_E4M3FNUZ); // FP8 -> FP8 + e4m3u.convertTo(back, CV_32F); + // values <=6 are representable in both grids -> preserved exactly + EXPECT_EQ(back.at(0), 0.5f); + EXPECT_EQ(back.at(1), 1.5f); + EXPECT_EQ(back.at(2), 6.f); + EXPECT_EQ(back.at(4), -2.f); +} + +TEST(Core_FP8, convert_scale) +{ + Mat f = (Mat_(1, 4) << 1.f, 2.f, 3.f, 4.f); + Mat q, back; + f.convertTo(q, CV_8F_E4M3FN, 2.0, 1.0); // 2x+1 -> {3,5,7,9} + q.convertTo(back, CV_32F); + EXPECT_EQ(back.at(0), 3.f); // 1.5*2, exact + EXPECT_EQ(back.at(1), 5.f); // 1.25*4, exact + EXPECT_EQ(back.at(2), 7.f); // 1.75*4, exact + EXPECT_EQ(back.at(3), 9.f); // 9 = 1.125*8 is exact in E4M3 (3 mantissa bits) +} + +TEST(Core_FP8, set_scalar) +{ + Mat m(3, 3, CV_8F_E4M3FN); + m.setTo(Scalar(2.5)); + Mat back; m.convertTo(back, CV_32F); + for (int i = 0; i < 9; i++) + EXPECT_EQ(back.at(i), 2.5f); + + Mat z = Mat::zeros(2, 2, CV_8F_E4M3FNUZ); + Mat zf; z.convertTo(zf, CV_32F); + EXPECT_EQ(countNonZero(zf), 0); +} + +// both fp8 flavors <-> every other depth, both directions; values exact in all types +TEST(Core_FP8, convert_all_depths) +{ + const int fp8[] = { CV_8F, CV_8F_E4M3FNUZ }; + const int others[] = { CV_8U, CV_8S, CV_16U, CV_16S, CV_32S, CV_32F, + CV_64F, CV_16F, CV_16BF, CV_64U, CV_64S, CV_32U }; + float vals[] = { 0.f, 1.f, 2.f, 3.f, 4.f, 6.f }; + Mat f(1, 6, CV_32F, vals); + for (int d : fp8) + for (int o : others) + { + Mat q, viaO, back; + f.convertTo(q, d); q.convertTo(viaO, o); viaO.convertTo(back, CV_32F); + Mat so, q2, back2; + f.convertTo(so, o); so.convertTo(q2, d); q2.convertTo(back2, CV_32F); + for (int i = 0; i < 6; i++) + { + EXPECT_EQ(back.at(i), vals[i]) << "fp8 " << d << " -> " << o << " idx " << i; + EXPECT_EQ(back2.at(i), vals[i]) << o << " -> fp8 " << d << " idx " << i; + } + } +} + +}} // namespace diff --git a/modules/core/test/test_io.cpp b/modules/core/test/test_io.cpp index 6289db4de6..5f4617d22e 100644 --- a/modules/core/test/test_io.cpp +++ b/modules/core/test/test_io.cpp @@ -2370,10 +2370,61 @@ TEST_P(FileStorage_exact_type, long_int_mat) EXPECT_EQ(cv::norm(src, dst, NORM_INF), 0.0); } +TEST_P(FileStorage_exact_type, fp8_mat) +{ + const int fp8[] = { CV_8F_E4M3FN, CV_8F_E4M3FNUZ }; + float vals[] = { 0.f, 0.5f, 1.f, 1.5f, 2.f, 3.f, 4.f, 6.f, -1.5f, -2.f }; + Mat f(1, 10, CV_32F, vals); + for (int d : fp8) + { + Mat src; f.convertTo(src, d); + Mat dst = fsWriteRead(src, GetParam()); + ASSERT_EQ(src.type(), dst.type()); + ASSERT_EQ(src.size, dst.size); + EXPECT_EQ(0, memcmp(src.data, dst.data, src.total() * src.elemSize())) << "fp8 depth " << d; + } +} + INSTANTIATE_TEST_CASE_P(Core_InputOutput, FileStorage_exact_type, Values(".yml", ".xml", ".json", ".xml.gz", ".xml.gz0", ".xml.gz9") ); +TEST(Core_InputOutput, fp8_base64) +{ + const int fp8[] = { CV_8F_E4M3FN, CV_8F_E4M3FNUZ }; + float vals[] = { 0.f, 0.5f, 1.f, 1.5f, 2.f, 3.f, 4.f, 6.f, -1.5f, -2.f }; + Mat f(1, 10, CV_32F, vals); + for (const char* ext : { ".yml", ".xml", ".json" }) + for (int d : fp8) + { + Mat src; f.convertTo(src, d); + std::string fn = cv::tempfile(ext); + { FileStorage fs(fn, FileStorage::WRITE_BASE64); fs << "m" << src; } + Mat dst; { FileStorage fs(fn, FileStorage::READ); fs["m"] >> dst; } + remove(fn.c_str()); + ASSERT_EQ(src.type(), dst.type()); + ASSERT_EQ(src.size, dst.size); + EXPECT_EQ(0, memcmp(src.data, dst.data, src.total() * src.elemSize())) + << "fp8 depth " << d << " ext " << ext; + } +} + +TEST(Core_InputOutput, fp8_scalar) +{ + for (const char* ext : { ".yml", ".xml", ".json" }) + { + cv::fp8_t a(2.5f); + cv::fp8a_t b(-1.5f); + std::string fn = cv::tempfile(ext); + { FileStorage fs(fn, FileStorage::WRITE); fs << "a" << a << "b" << b; } + cv::fp8_t a2; cv::fp8a_t b2; + { FileStorage fs(fn, FileStorage::READ); fs["a"] >> a2; fs["b"] >> b2; } + remove(fn.c_str()); + EXPECT_EQ((float)a, (float)a2) << ext; + EXPECT_EQ((float)b, (float)b2) << ext; + } +} + TEST(Core_InputOutput, YAML_Compatibility) { string filename = cv::tempfile(".yaml"); diff --git a/modules/ts/src/ts_func.cpp b/modules/ts/src/ts_func.cpp index ece80c48a3..0f0856260e 100644 --- a/modules/ts/src/ts_func.cpp +++ b/modules/ts/src/ts_func.cpp @@ -332,6 +332,12 @@ convertTo(const _Tp* src, void* dst, int dtype, case CV_16BF: convert_(src, (cv::bfloat*)dst, total, alpha, beta); break; + case CV_8F_E4M3FN: + convert_(src, (cv::fp8_t*)dst, total, alpha, beta); + break; + case CV_8F_E4M3FNUZ: + convert_(src, (cv::fp8a_t*)dst, total, alpha, beta); + break; case CV_Bool: convert_to_bool(src, (bool*)dst, total, alpha, beta); break; @@ -415,6 +421,12 @@ void convert(const Mat& src, cv::OutputArray _dst, case CV_16BF: convertTo((const cv::bfloat*)sptr, dptr, dtype, total, alpha, beta); break; + case CV_8F_E4M3FN: + convertTo((const cv::fp8_t*)sptr, dptr, dtype, total, alpha, beta); + break; + case CV_8F_E4M3FNUZ: + convertTo((const cv::fp8a_t*)sptr, dptr, dtype, total, alpha, beta); + break; default: CV_Error(cv::Error::StsNotImplemented, "unknown/unsupported depth"); } @@ -2251,6 +2263,15 @@ int check( const Mat& a, double fmin, double fmax, vector* _idx ) // success_err_level is maximum allowed difference, idx is the index of the first // element for which difference is >success_err_level // (or index of element with the maximum difference) +static inline double decodeFP8(const uchar* p, int depth) +{ + switch (depth) + { + case CV_8F_E4M3FN: return (double)(float)*reinterpret_cast(p); + default: return (double)(float)*reinterpret_cast(p); + } +} + int cmpEps( const Mat& arr_, const Mat& refarr_, double* _realmaxdiff, double success_err_level, vector* _idx, bool element_wise_relative_error ) @@ -2375,6 +2396,38 @@ int cmpEps( const Mat& arr_, const Mat& refarr_, double* _realmaxdiff, } } break; + case CV_8F_E4M3FN: + case CV_8F_E4M3FNUZ: + for( j = 0; j < total; j++ ) + { + if( ((uchar*)sptr1)[j] == ((uchar*)sptr2)[j] ) + continue; + double a_val = decodeFP8((const uchar*)sptr1 + j, depth); + double b_val = decodeFP8((const uchar*)sptr2 + j, depth); + double threshold; + if( cvIsNaN(a_val) || cvIsInf(a_val) ) + { + result = CMP_EPS_INVALID_TEST_DATA; + idx = startidx + j; + break; + } + if( cvIsNaN(b_val) || cvIsInf(b_val) ) + { + result = CMP_EPS_INVALID_REF_DATA; + idx = startidx + j; + break; + } + a_val = fabs(a_val - b_val); + threshold = element_wise_relative_error ? fabs(b_val) + 1 : maxval; + if( a_val > threshold*success_err_level ) + { + realmaxdiff = a_val/threshold; + if( idx == 0 ) + idx = startidx + j; + break; + } + } + break; case CV_32F: for( j = 0; j < total; j++ ) { @@ -3404,6 +3457,14 @@ static void writeElems(std::ostream& out, const void* data, int nelems, int dept writeElems(out, data, nelems, starpos); out.precision(pp); } + else if(depth == CV_8F_E4M3FN || depth == CV_8F_E4M3FNUZ) + { + std::streamsize pp = out.precision(); + out.precision(4); + if(depth == CV_8F_E4M3FN) writeElems(out, data, nelems, starpos); + else writeElems(out, data, nelems, starpos); + out.precision(pp); + } else if(depth == CV_32F) { std::streamsize pp = out.precision();