From 1e61fe317ef21534f7b0fc37e13582a181fe85ec Mon Sep 17 00:00:00 2001 From: Chris Perkins Date: Mon, 11 Dec 2023 19:43:54 -0800 Subject: [PATCH] Bfloat16 support for sycl::vec --- .../sycl/detail/generic_type_lists.hpp | 62 ++++++++-- .../sycl/detail/generic_type_traits.hpp | 17 ++- sycl/include/sycl/ext/oneapi/bfloat16.hpp | 63 +++++++++- .../oneapi/matrix/matrix-unified-utils.hpp | 3 + sycl/include/sycl/stream.hpp | 39 +++++- sycl/include/sycl/types.hpp | 111 ++++++++++++++---- sycl/test/basic_tests/generic_type_traits.cpp | 4 + .../implicit_device_copyable_types.cpp | 3 + sycl/test/basic_tests/types.cpp | 12 ++ 9 files changed, 264 insertions(+), 50 deletions(-) diff --git a/sycl/include/sycl/detail/generic_type_lists.hpp b/sycl/include/sycl/detail/generic_type_lists.hpp index a18bb5fd764f..476f33e1cb3a 100644 --- a/sycl/include/sycl/detail/generic_type_lists.hpp +++ b/sycl/include/sycl/detail/generic_type_lists.hpp @@ -12,6 +12,8 @@ #include // for type_list, address_space_list #include // for half +#include // bfloat16 + #include // for byte, size_t #include // for conditional_t, is_signed_v, is_... @@ -41,6 +43,28 @@ using scalar_vector_half_list = tl_append; using half_list = tl_append; +using scalar_bfloat16_list = type_list; + +using vector_bfloat16_list = type_list< + vec, vec, + vec, vec, + vec, vec>; + +using marray_bfloat16_list = type_list, + marray, + marray, + marray, + marray, + marray>; + +using scalar_vector_bfloat16_list = + tl_append; + +using bfloat16_list = + tl_append; + +using half_bfloat16_list = tl_append; + using scalar_float_list = type_list; using vector_float_list = @@ -73,14 +97,14 @@ using scalar_vector_double_list = using double_list = tl_append; -using scalar_floating_list = - tl_append; +using scalar_floating_list = tl_append; -using vector_floating_list = - tl_append; +using vector_floating_list = tl_append; -using marray_floating_list = - tl_append; +using marray_floating_list = tl_append; using scalar_vector_floating_list = tl_append; @@ -91,6 +115,8 @@ using floating_list = // geometric floating point types using scalar_geo_half_list = type_list; +using scalar_geo_bfloat16_list = type_list; + using scalar_geo_float_list = type_list; using scalar_geo_double_list = type_list; @@ -98,6 +124,10 @@ using scalar_geo_double_list = type_list; using vector_geo_half_list = type_list, vec, vec, vec>; +using vector_geo_bfloat16_list = type_list< + vec, vec, + vec, vec>; + using vector_geo_float_list = type_list, vec, vec, vec>; @@ -112,16 +142,21 @@ using marray_geo_double_list = using geo_half_list = tl_append; +using geo_bfloat16_list = + tl_append; + using geo_float_list = tl_append; using geo_double_list = tl_append; -using scalar_geo_list = tl_append; +using scalar_geo_list = + tl_append; -using vector_geo_list = tl_append; +using vector_geo_list = + tl_append; using marray_geo_list = tl_append; @@ -131,12 +166,15 @@ using geo_list = tl_append; // cross floating point types using cross_half_list = type_list, vec>; +using cross_bfloat16_list = type_list, + vec>; + using cross_float_list = type_list, vec>; using cross_double_list = type_list, vec>; -using cross_floating_list = - tl_append; +using cross_floating_list = tl_append; using cross_marray_list = type_list, marray, marray, marray>; diff --git a/sycl/include/sycl/detail/generic_type_traits.hpp b/sycl/include/sycl/detail/generic_type_traits.hpp index 7e8b6a272df8..fea829057d35 100644 --- a/sycl/include/sycl/detail/generic_type_traits.hpp +++ b/sycl/include/sycl/detail/generic_type_traits.hpp @@ -56,6 +56,14 @@ inline constexpr bool is_genfloath_v = is_contained_v; template inline constexpr bool is_half_v = is_contained_v; +template +inline constexpr bool is_bfloat16_v = + is_contained_v; + +template +inline constexpr bool is_half_or_bf16_v = + is_contained_v; + template inline constexpr bool is_svgenfloath_v = is_contained_v; @@ -539,10 +547,9 @@ using select_cl_scalar_t = std::conditional_t< std::is_integral_v, select_cl_scalar_integral_t, std::conditional_t< std::is_floating_point_v, select_cl_scalar_float_t, - // half is a special case: it is implemented differently on - // host and device and therefore, might lower to different - // types - std::conditional_t, + // half and bfloat16 are special cases: they are implemented differently + // on host and device and therefore might lower to different types + std::conditional_t, sycl::detail::half_impl::BIsRepresentationT, select_cl_scalar_complex_or_T_t>>>; @@ -559,7 +566,7 @@ struct select_cl_vector_or_scalar_or_ptr< // select_cl_scalar_t returns _Float16, so, we try to instantiate vec // class with _Float16 DataType, which is not expected there // So, leave vector as-is - vec>, + vec>, mptr_or_vec_elem_type_t, select_cl_scalar_t>>, T::size()>; diff --git a/sycl/include/sycl/ext/oneapi/bfloat16.hpp b/sycl/include/sycl/ext/oneapi/bfloat16.hpp index bd3052e9a048..9fdc0e7c9fac 100644 --- a/sycl/include/sycl/ext/oneapi/bfloat16.hpp +++ b/sycl/include/sycl/ext/oneapi/bfloat16.hpp @@ -9,7 +9,6 @@ #pragma once #include // for half -#include // for isnan #include // for __DPCPP_SYCL_EXTERNAL #include // for half @@ -22,6 +21,13 @@ __devicelib_ConvertBF16ToFINTEL(const uint16_t &) noexcept; namespace sycl { inline namespace _V1 { + +#ifdef __INTEL_PREVIEW_BREAKING_CHANGES +// forward declaration of sycl::isnan built-in. +// extern __DPCPP_SYCL_EXTERNAL bool isnan(float a); +bool isnan(float a); +#endif + namespace ext::oneapi { class bfloat16; @@ -30,9 +36,31 @@ namespace detail { using Bfloat16StorageT = uint16_t; Bfloat16StorageT bfloat16ToBits(const bfloat16 &Value); bfloat16 bitsToBfloat16(const Bfloat16StorageT Value); + +// sycl::vec support +namespace bf16 { +#ifdef __SYCL_DEVICE_ONLY__ +using Vec2StorageT = Bfloat16StorageT __attribute__((ext_vector_type(2))); +using Vec3StorageT = Bfloat16StorageT __attribute__((ext_vector_type(3))); +using Vec4StorageT = Bfloat16StorageT __attribute__((ext_vector_type(4))); +using Vec8StorageT = Bfloat16StorageT __attribute__((ext_vector_type(8))); +using Vec16StorageT = Bfloat16StorageT __attribute__((ext_vector_type(16))); +#else +using Vec2StorageT = std::array; +using Vec3StorageT = std::array; +using Vec4StorageT = std::array; +using Vec8StorageT = std::array; +using Vec16StorageT = std::array; +#endif +} // namespace bf16 + +#ifndef __INTEL_PREVIEW_BREAKING_CHANGES +static bool float_is_nan(float x) { return x != x; } +#endif } // namespace detail class bfloat16 { +protected: detail::Bfloat16StorageT value; friend inline detail::Bfloat16StorageT @@ -42,13 +70,21 @@ class bfloat16 { public: bfloat16() = default; - bfloat16(const bfloat16 &) = default; + constexpr bfloat16(const bfloat16 &) = default; + constexpr bfloat16(bfloat16 &&) = default; + constexpr bfloat16 &operator=(const bfloat16 &rhs) = default; ~bfloat16() = default; private: static detail::Bfloat16StorageT from_float_fallback(const float &a) { +#ifdef __INTEL_PREVIEW_BREAKING_CHANGES if (sycl::isnan(a)) return 0xffc1; +#else + if (detail::float_is_nan(a)) + return 0xffc1; +#endif + union { uint32_t intStorage; float floatValue; @@ -92,6 +128,14 @@ class bfloat16 { #endif } +protected: + friend class sycl::vec; + friend class sycl::vec; + friend class sycl::vec; + friend class sycl::vec; + friend class sycl::vec; + friend class sycl::vec; + public: // Implicit conversion from float to bfloat16 bfloat16(const float &a) { value = from_float(a); } @@ -128,7 +172,7 @@ class bfloat16 { #elif defined(__SYCL_DEVICE_ONLY__) && defined(__SPIR__) return bfloat16{-__devicelib_ConvertBF16ToFINTEL(lhs.value)}; #else - return -to_float(lhs.value); + return bfloat16{-to_float(lhs.value)}; #endif } @@ -199,6 +243,19 @@ class bfloat16 { // Bitwise(|,&,~,^), modulo(%) and shift(<<,>>) operations are not supported // for floating-point types. + + // Stream Operator << and >> + inline friend std::ostream &operator<<(std::ostream &O, bfloat16 const &rhs) { + O << static_cast(rhs); + return O; + } + + inline friend std::istream &operator>>(std::istream &I, bfloat16 &rhs) { + float ValFloat = 0.0f; + I >> ValFloat; + rhs = ValFloat; + return I; + } }; namespace detail { diff --git a/sycl/include/sycl/ext/oneapi/matrix/matrix-unified-utils.hpp b/sycl/include/sycl/ext/oneapi/matrix/matrix-unified-utils.hpp index 8a50c435fb0c..4695d461805b 100644 --- a/sycl/include/sycl/ext/oneapi/matrix/matrix-unified-utils.hpp +++ b/sycl/include/sycl/ext/oneapi/matrix/matrix-unified-utils.hpp @@ -7,6 +7,9 @@ // ===--------------------------------------------------------------------=== // #pragma once + +#include + namespace sycl { inline namespace _V1 { namespace ext { diff --git a/sycl/include/sycl/stream.hpp b/sycl/include/sycl/stream.hpp index dd79bd532cc6..915f67ef063d 100644 --- a/sycl/include/sycl/stream.hpp +++ b/sycl/include/sycl/stream.hpp @@ -20,6 +20,7 @@ #include // for __SYCL_EXPORT #include // for id, range #include // for OwnerLessBase +#include // for bfloat16 #include // for group #include // for h_item #include // for half, operator-, operator< @@ -83,10 +84,10 @@ constexpr size_t MAX_ARRAY_SIZE = constexpr unsigned FLUSH_BUF_OFFSET_SIZE = 2; template -using EnableIfFP = typename std::enable_if_t || - std::is_same_v || - std::is_same_v, - T>; +using EnableIfFP = typename std::enable_if_t< + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v, + T>; using GlobalBufAccessorT = accessor; @@ -346,6 +347,26 @@ checkForInfNan(char *Buf, T Val) { return 0; } +template +inline typename std::enable_if_t, + unsigned> +checkForInfNan(char *Buf, T Val) { + if (Val != Val) + return append(Buf, "nan"); + + // Extract the sign from the bits + const uint16_t Sign = reinterpret_cast(Val) & 0x8000; + // Extract the exponent from the bits + const uint16_t Exp16 = (reinterpret_cast(Val) & 0x7f80) >> 7; + + if (Exp16 == 0x7f) { + if (Sign) + return append(Buf, "-inf"); + return append(Buf, "inf"); + } + return 0; +} + template EnableIfFP floatingPointToDecStr(T AbsVal, char *Digits, int Precision, bool IsSci) { @@ -1053,6 +1074,8 @@ class __SYCL_EXPORT __SYCL_SPECIAL_CLASS __SYCL_TYPE(stream) stream friend const stream &operator<<(const stream &, const float &); friend const stream &operator<<(const stream &, const double &); friend const stream &operator<<(const stream &, const half &); + friend const stream &operator<<(const stream &, + const ext::oneapi::bfloat16 &); friend const stream &operator<<(const stream &, const stream_manipulator); @@ -1159,6 +1182,14 @@ inline const stream &operator<<(const stream &Out, const half &RHS) { return Out; } +inline const stream &operator<<(const stream &Out, + const ext::oneapi::bfloat16 &RHS) { + detail::writeFloatingPoint( + Out.GlobalFlushBuf, Out.FlushBufferSize, Out.WIOffset, Out.get_flags(), + Out.get_width(), Out.get_precision(), RHS); + return Out; +} + // Pointer template // for __SYCL_BINOP, __SYCL_... #include // for multi_ptr +#include // bfloat16 + #include // for array #include // for assert #include // for size_t, NULL, byte @@ -312,6 +314,9 @@ template class vec { std::is_same_v; + static constexpr bool IsBfloat16 = + std::is_same_v; + #if defined(__INTEL_PREVIEW_BREAKING_CHANGES) static constexpr size_t AdjustedNum = (NumElements == 3) ? 4 : NumElements; @@ -495,7 +500,13 @@ template class vec { template constexpr vec(const std::array, NumElements> &Arr, std::index_sequence) - : m_Data{vec_data_t(static_cast(Arr[Is]))...} {} + : m_Data{([&](vec_data_t v) constexpr { + if constexpr (std::is_same_v) + return v.value; + // return sycl::ext::oneapi::detail::bfloat16ToBits(v); + else + return vec_data_t(static_cast(v)); + })(Arr[Is])...} {} public: using element_type = DataT; @@ -580,7 +591,7 @@ template class vec { template typename std::enable_if_t< std::is_fundamental_v> || - std::is_same_v, half>, + detail::is_half_or_bf16_v>, vec &> operator=(const EnableIfNotUsingArrayOnDevice &Rhs) { m_Data = (DataType)vec_data::get(Rhs); @@ -596,7 +607,7 @@ template class vec { template typename std::enable_if_t< std::is_fundamental_v> || - std::is_same_v, half>, + detail::is_half_or_bf16_v>, vec &> operator=(const EnableIfUsingArrayOnDevice &Rhs) { for (int i = 0; i < NumElements; ++i) { @@ -613,7 +624,7 @@ template class vec { template typename std::enable_if_t< std::is_fundamental_v> || - std::is_same_v, half>, + detail::is_half_or_bf16_v>, vec &> operator=(const DataT &Rhs) { for (int i = 0; i < NumElements; ++i) { @@ -962,7 +973,7 @@ template class vec { typename std::enable_if_t< \ std::is_convertible_v && \ (std::is_fundamental_v> || \ - std::is_same_v, half>), \ + detail::is_half_or_bf16_v>), \ vec> \ operator BINOP(const T & Rhs) const { \ return *this BINOP vec(static_cast(Rhs)); \ @@ -995,7 +1006,7 @@ template class vec { typename std::enable_if_t< \ std::is_convertible_v && \ (std::is_fundamental_v> || \ - std::is_same_v, half>), \ + detail::is_half_or_bf16_v>), \ vec> \ operator BINOP(const T & Rhs) const { \ return *this BINOP vec(static_cast(Rhs)); \ @@ -1098,7 +1109,7 @@ template class vec { template \ typename std::enable_if_t && \ (std::is_fundamental_v> || \ - std::is_same_v), \ + detail::is_half_or_bf16_v), \ vec> \ operator RELLOGOP(const T & Rhs) const { \ return *this RELLOGOP vec(static_cast(Rhs)); \ @@ -1116,7 +1127,7 @@ template class vec { template \ typename std::enable_if_t && \ (std::is_fundamental_v> || \ - std::is_same_v), \ + detail::is_half_or_bf16_v), \ vec> \ operator RELLOGOP(const T & Rhs) const { \ return *this RELLOGOP vec(static_cast(Rhs)); \ @@ -1258,17 +1269,48 @@ template class vec { // operator - template EnableIfNotUsingArray operator-() const { - vec Ret{-m_Data}; - if constexpr (std::is_same_v) { - Ret.ConvertToDataT(); + namespace oneapi = sycl::ext::oneapi; + if constexpr (IsBfloat16 && NumElements == 1) { + vec Ret{}; + oneapi::bfloat16 v = oneapi::detail::bitsToBfloat16(m_Data); + oneapi::bfloat16 w = -v; + Ret.m_Data = oneapi::detail::bfloat16ToBits(w); + } else if constexpr (IsBfloat16) { + vec Ret{}; + for (size_t I = 0; I < NumElements; ++I) { + oneapi::bfloat16 v = oneapi::detail::bitsToBfloat16(m_Data[I]); + oneapi::bfloat16 w = -v; + Ret.m_Data[I] = oneapi::detail::bfloat16ToBits(w); + } + return Ret; + } + else { + vec Ret{-m_Data}; + if constexpr (std::is_same_v) { + Ret.ConvertToDataT(); + } + return Ret; } - return Ret; } template EnableIfUsingArray operator-() const { + namespace oneapi = sycl::ext::oneapi; vec Ret{}; - for (size_t I = 0; I < NumElements; ++I) - Ret.setValue(I, vec_data::get(-vec_data::get(getValue(I)))); + if constexpr (IsBfloat16 && NumElements == 1) { + oneapi::bfloat16 v = oneapi::detail::bitsToBfloat16(m_Data); + oneapi::bfloat16 w = -v; + Ret.m_Data = oneapi::detail::bfloat16ToBits(w); + } else if constexpr (IsBfloat16) { + for (size_t I = 0; I < NumElements; I++) { + oneapi::bfloat16 v = oneapi::detail::bitsToBfloat16(m_Data[I]); + oneapi::bfloat16 w = -v; + Ret.m_Data[I] = oneapi::detail::bfloat16ToBits(w); + } + } else { + for (size_t I = 0; I < NumElements; ++I) + Ret.setValue(I, + vec_data::get(-vec_data::get(getValue(I)))); + } return Ret; } #endif // defined(__INTEL_PREVIEW_BREAKING_CHANGES) @@ -1376,8 +1418,6 @@ template class vec { #endif // !defined(__INTEL_PREVIEW_BREAKING_CHANGES) - // CP --------------- - // OP is: &&, || // vec operatorOP(const vec &Rhs) const; // vec operatorOP(const DataT &Rhs) const; @@ -1614,13 +1654,13 @@ class SwizzleOp { using EnableIfScalarType = typename std::enable_if_t< std::is_convertible_v && (std::is_fundamental_v> || - std::is_same_v, half>)>; + detail::is_half_or_bf16_v>)>; template using EnableIfNoScalarType = typename std::enable_if_t< !std::is_convertible_v || !(std::is_fundamental_v> || - std::is_same_v, half>)>; + detail::is_half_or_bf16_v>)>; template using Swizzle = @@ -2196,7 +2236,7 @@ class SwizzleOp { template \ typename std::enable_if_t< \ std::is_fundamental_v> || \ - std::is_same_v, half>, \ + detail::is_half_or_bf16_v>, \ vec> \ operator BINOP(const T & Lhs, const vec &Rhs) { \ return vec(Lhs) BINOP Rhs; \ @@ -2208,7 +2248,7 @@ class SwizzleOp { typename std::enable_if_t< \ std::is_convertible_v && \ (std::is_fundamental_v> || \ - std::is_same_v, half>), \ + detail::is_half_or_bf16_v>), \ vec> \ operator BINOP( \ const T & Lhs, \ @@ -2252,7 +2292,7 @@ __SYCL_BINOP(<<) typename std::enable_if_t< \ std::is_convertible_v && \ (std::is_fundamental_v> || \ - std::is_same_v, half>), \ + detail::is_half_or_bf16_v>), \ vec, Num>> \ operator RELLOGOP(const T & Lhs, const vec &Rhs) { \ return vec(static_cast(Lhs)) RELLOGOP Rhs; \ @@ -2264,7 +2304,7 @@ __SYCL_BINOP(<<) typename std::enable_if_t< \ std::is_convertible_v && \ (std::is_fundamental_v> || \ - std::is_same_v, half>), \ + detail::is_half_or_bf16_v>), \ vec, Num>> \ operator RELLOGOP( \ const T & Lhs, \ @@ -2440,10 +2480,10 @@ struct VecStorage>> { #endif // __INTEL_PREVIEW_BREAKING_CHANGES }; -// Single element floating-point (except half) +// Single element floating-point (except half/bfloat16) template struct VecStorage< - T, 1, typename std::enable_if_t && is_sgenfloat_v>> { + T, 1, typename std::enable_if_t && is_sgenfloat_v>> { using DataType = T; #ifdef __INTEL_PREVIEW_BREAKING_CHANGES #ifdef __SYCL_DEVICE_ONLY__ @@ -2451,13 +2491,13 @@ struct VecStorage< #endif // __SYCL_DEVICE_ONLY__ #endif // __INTEL_PREVIEW_BREAKING_CHANGES }; -// Multiple elements signed/unsigned integers and floating-point (except half) +// Multiple elements signed/unsigned integers and floating-point (except half/bfloat16) template struct VecStorage< T, N, typename std::enable_if_t || - (is_sgenfloat_v && !is_half_v))>> { + (is_sgenfloat_v && !is_half_or_bf16_v))>> { using DataType = typename VecStorageImpl::DataType, N>::DataType; #ifdef __INTEL_PREVIEW_BREAKING_CHANGES @@ -2501,6 +2541,25 @@ __SYCL_DEFINE_HALF_VECSTORAGE(4) __SYCL_DEFINE_HALF_VECSTORAGE(8) __SYCL_DEFINE_HALF_VECSTORAGE(16) #undef __SYCL_DEFINE_HALF_VECSTORAGE + +// Single element bfloat16 +template <> struct VecStorage { + using DataType = sycl::ext::oneapi::detail::Bfloat16StorageT; + using VectorDataType = sycl::ext::oneapi::detail::Bfloat16StorageT; +}; +// Multiple elements bfloat16 +#define __SYCL_DEFINE_BF16_VECSTORAGE(Num) \ + template <> struct VecStorage { \ + using DataType = sycl::ext::oneapi::detail::bf16::Vec##Num##StorageT; \ + using VectorDataType = \ + sycl::ext::oneapi::detail::bf16::Vec##Num##StorageT; \ + }; +__SYCL_DEFINE_BF16_VECSTORAGE(2) +__SYCL_DEFINE_BF16_VECSTORAGE(3) +__SYCL_DEFINE_BF16_VECSTORAGE(4) +__SYCL_DEFINE_BF16_VECSTORAGE(8) +__SYCL_DEFINE_BF16_VECSTORAGE(16) +#undef __SYCL_DEFINE_BF16_VECSTORAGE } // namespace detail /// This macro must be defined to 1 when SYCL implementation allows user diff --git a/sycl/test/basic_tests/generic_type_traits.cpp b/sycl/test/basic_tests/generic_type_traits.cpp index 307dc6c4212c..7805adec4578 100644 --- a/sycl/test/basic_tests/generic_type_traits.cpp +++ b/sycl/test/basic_tests/generic_type_traits.cpp @@ -63,6 +63,10 @@ int main() { static_assert(d::is_half_v); + static_assert(d::is_bfloat16_v); + static_assert(d::is_half_or_bf16_v); + static_assert(d::is_half_or_bf16_v); + // TODO add checks for the following type traits /* is_doublen diff --git a/sycl/test/basic_tests/implicit_device_copyable_types.cpp b/sycl/test/basic_tests/implicit_device_copyable_types.cpp index dd15f80b8120..ee368207bc24 100644 --- a/sycl/test/basic_tests/implicit_device_copyable_types.cpp +++ b/sycl/test/basic_tests/implicit_device_copyable_types.cpp @@ -100,6 +100,9 @@ int main() { #ifdef __INTEL_PREVIEW_BREAKING_CHANGES // Extra checks static_assert(sycl::is_device_copyable_v>); + static_assert(sycl::is_device_copyable_v>); + static_assert( + sycl::is_device_copyable_v>); struct S { sycl::vec v; diff --git a/sycl/test/basic_tests/types.cpp b/sycl/test/basic_tests/types.cpp index cc07202f393d..6aab1e433c7a 100644 --- a/sycl/test/basic_tests/types.cpp +++ b/sycl/test/basic_tests/types.cpp @@ -204,6 +204,18 @@ void checkVariousVecUnaryOps() { checkVecUnaryOps(vd1); sycl::vec vd{0, 4, 5, -9, 0, 4, 5, -9, 0, 4, 5, -9, 0, 4, 5, -9}; checkVecUnaryOps(vd); + + sycl::vec vh1{1}; + checkVecUnaryOps(vh1); + sycl::vec vh{0, 4, 5, -9, 0, 4, 5, -9, + 0, 4, 5, -9, 0, 4, 5, -9}; + checkVecUnaryOps(vh); + + sycl::vec vbf1{1}; + checkVecUnaryOps(vbf1); + sycl::vec vbf{0, 4, 5, -9, 0, 4, 5, -9, + 0, 4, 5, -9, 0, 4, 5, -9}; + checkVecUnaryOps(vbf); } int main() {