First-class fp16/bf16/fp8/fp4 element types for both backends - #321
Open
johnnynunez wants to merge 2 commits into
Open
First-class fp16/bf16/fp8/fp4 element types for both backends#321johnnynunez wants to merge 2 commits into
johnnynunez wants to merge 2 commits into
Conversation
Adds fk::fp16, fk::bf16, fk::fp8_e4m3, fk::fp8_e5m2 and fk::fp4_e2m1 as first-class FKL element types, usable in PerThreadRead/PerThreadWrite/ executeOperations pipelines, Cast and SaturateCast on BOTH backends. Until now these formats could only enter a pipeline through ad-hoc dequantizing Read IOps (attention/), because the type machinery did not know about them and CPU-only builds had no such types at all. Design (fk-owned types, identical on every backend): - reduced_float_types.h implements a parametric constexpr softfloat core (single MiniFloatFormat encoder/decoder pair, RN-even, NVIDIA-exact special value policies: SATFINITE for fp8/fp4, e4m3 NaN=0x7F without Inf, e2m1 NaN->+6 without NaN/Inf). The same fk types are the element types under nvcc, g++, clang and MSVC: no per-backend aggregate/traits/operator divergence, and everything is constexpr. - Under nvcc, fp16/bf16 conversions take the native __half/__nv_bfloat16 ctor fast paths at runtime (hardware cvt on device) via __builtin_is_constant_evaluated(); toNative()/fromNative() provide free interop with native CUDA buffers (bit-identical layouts, static_asserted). fp8/fp4 are pure software on all paths, so the API does not depend on the installed toolkit version (no cuda_fp8.h/cuda_fp4.h includes). - Conversion surface: implicit operator float() + explicit ctors, so h * 2.0f and float f = h work with zero ambiguity; fp16/bf16 get full constexpr arithmetic/comparisons (promote-compute-demote is correctly rounded: binary32 has more than 2p+2 mantissa bits for both). fp8/fp4 are conversion-only, exactly like the CUDA types. - fp4_e2m1 is one element per byte (low nibble, canonicalized on construction) matching CUDA's scalar type; the packed 2-per-byte format does not fit the RawPtr element model and stays a fused-read use case. Integration: - Vector companions fp16_1..4, bf16_1..4, fp8_*_1..4, fp4_e2m1_1..4 as fk aggregates (CUDA has no 3/4-channel variants and its 2-channel types are non-aggregates); layouts match __half2/__nv_bfloat162/__nv_fp8x2/x4. - Registered in VectorType/VectorTraits/VAll (separate RFV lists: standard lists stay byte-identical) and in the thread fusion tables as an appended block (fp16->fp16_2 like short->short2, fp8/fp4->x4 like char->char4), pinned by exhaustive static_asserts including legacy row boundaries. - Scalar gates widened from std::is_fundamental to fk::validScalar in AreSS/AreSV/AreVS/vector_at/cxp::Exec/cmp_*/max/min/abs/Discard/ ArrayVector; cmp_*/saturate_cast promote reduced operands explicitly to float (mixed implicit conversions are ambiguous by design); isnan/isinf/ abs use bit classification (fp8/fp4 have no operators; e4m3/e2m1 NaN is not detectable via s != s). - std::numeric_limits specialized for the five types (raw-bit constexpr), making fk::maxValue/minValue and cxp limits work unmodified; vlimits now static_asserts is_specialized instead of silently zero-initializing limits for unknown types (numeric_limits<__half> is not specialized in CUDA 13.3 and used to produce silent-zero clamps). - Fixed a latent gap where CanCompound accepted vector pairs whose base type has no compound operators (fp8x2 += fp8x2 would hard-error in the operator body); the vector operator gates moved to default template arguments so the element-operator decltype cannot recurse. - RGB2Gray no longer falls off the end of a non-void function for non-standard output types; math Abs handles sign-magnitude formats. Verification (RTX PRO 6000, CUDA 13.3, g++ 11.5): - utest_reduced_float_native_parity (_cu): bit-exact parity with the NVIDIA converters for ALL 65536 fp16 and bf16 codes (decode), 2M sampled float encodes + directed corpus, all 256 fp8 codes and encodes vs __nv_cvt_float_to_fp8(SATFINITE), all 16 fp4 codes plus a 40k encode sweep vs __nv_fp4_e2m1, and constexpr-vs-runtime agreement in-TU. - utest_reduced_float_types (_cpp/_cu): layout asserts, constexpr conversions/limits/arithmetic, exhaustive software roundtrips (including bf16 subnormals below the binary32 normal range), a double-precision arithmetic oracle, and a CPU-purity tripwire (no CUDA headers may leak into _cpp translation units). - utest_reduced_float_pipelines (_cpp/_cu): executeOperations for all five scalar types, vector pipelines, loose vs .then()-fused bit-equality, and a TF::ENABLED thread fusion run with an odd width. - Full suite: 74/74 ctest targets pass on both backends. Follow-ups (out of scope here): migrating attention/'s Bf16AttentionRead/ Fp8TokenDequantRead onto these types (needs an 'identity read of element type E' trait so the mma/decode cp.async fast paths keep their dispatch), sm_89+ cvt intrinsics for fp8/fp4 runtime paths, and a CUDA-less CI job to machine-check the CPU-purity invariant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces first-class reduced-precision floating-point element types (fp16, bf16, fp8_*, fp4_*) to FKL so they can be used end-to-end in executeOperations pipelines on both CPU and CUDA backends, and updates core traits/utilities so these types participate in vectorization, thread-fusion, constexpr math, and saturating casts.
Changes:
- Adds new reduced-float scalar + vector element types (including traits, conversions, numeric limits, and CUDA interop for fp16/bf16).
- Extends core type-gating/vector utilities and thread-fusion tables to treat reduced floats as valid scalars/vectors.
- Adds comprehensive unit/integration tests for type properties and pipeline correctness on
_cppand_cu, plus native parity tests on_cu.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| utests/core/execution_model/utest_reduced_float_pipelines.h | New pipeline-level tests covering reduced-float reads/casts/muls/saturate/writes and thread fusion. |
| utests/core/data/utest_reduced_float_types.h | New type/trait/limits/constexpr/arithmetic tests for reduced-float scalars and vectors. |
| utests/core/data/utest_reduced_float_native_parity.h | New CUDA-only parity tests comparing software conversions against NVIDIA native converters. |
| tests/operation_test_utils.h | Updates equality and TestCaseBuilder coverage to support reduced-float scalar I/O. |
| include/fused_kernel/core/utils/vlimits.h | Hardens maxValue/minValue to error out when std::numeric_limits is unspecialized. |
| include/fused_kernel/core/utils/vector_utils.h | Registers reduced-float vector types/traits and widens scalar/vector gating to validScalar. |
| include/fused_kernel/core/execution_model/thread_fusion.h | Extends thread-fusion mapping tables with reduced-float entries and adds boundary pinning asserts. |
| include/fused_kernel/core/data/vector_types.h | Adds fk-owned reduced-float vector aggregates and includes reduced-float scalar definitions. |
| include/fused_kernel/core/data/reduced_float_types.h | New reduced-float scalar types, software encode/decode core, traits, numeric_limits, and CUDA interop helpers. |
| include/fused_kernel/core/data/ptr_nd.h | Removes a problematic post-throw return value (now returns T{}) in host at() path. |
| include/fused_kernel/core/data/array.h | Widens ArrayVector scalar constraints from fundamental-only to validScalar. |
| include/fused_kernel/core/constexpr_libs/constexpr_vector_exec.h | Allows constexpr op execution on reduced-float scalars via validScalar. |
| include/fused_kernel/core/constexpr_libs/constexpr_saturate.h | Promotes reduced-float sources to float for saturate_cast correctness and operability. |
| include/fused_kernel/core/constexpr_libs/constexpr_cmath.h | Adds reduced-float handling to isnan/isinf/cmp/max/min/abs via bit classification and promotion. |
| include/fused_kernel/algorithms/image_processing/color_conversion.h | Fixes RGB2Gray to support reduced-float outputs and avoids fallthrough. |
| include/fused_kernel/algorithms/basic_ops/vector_ops.h | Treats reduced-float scalars as valid scalar outputs for vector ops. |
| include/fused_kernel/algorithms/basic_ops/math.h | Extends Abs to reduced floats via bit-based absolute value. |
Comment on lines
+30
to
+32
| #if !defined(NVRTC_COMPILER) | ||
| #include <limits> | ||
| #endif |
| FK_HOST_DEVICE_FUSE auto exec(const ST& s) { | ||
| static_assert(std::is_fundamental_v<ST>, "abs does not support non fundamental types"); | ||
| if constexpr (std::is_signed_v<ST>) { | ||
| static_assert(fk::validScalar<ST>, "abs does not support non fundamental types"); |
Comment on lines
541
to
+542
| throw std::runtime_error("Cannot access data in Device memory from host code"); | ||
| return make_set<T>(0); | ||
| return T{}; |
albertandaluz
force-pushed
the
feat/reduced-precision-types
branch
from
August 27, 2026 15:27
edc0b35 to
b61a5a6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Reduced-precision formats could not be used as FKL element types:
PerThreadRead<ND::_2D, __half>does not compile (noVectorTraits/thread-fusion registration, ~15std::is_fundamentalgates), and CPU-only builds have no such types at all. The only way in was theattention/workaround: ad-hoc Read IOps that dequantize tofloatinsideexec(). This PR adds the types to the library so any pipeline can read, compute (fp16/bf16), cast, saturate and write them on both backends.What this adds
Five element types in
namespace fk—fp16,bf16,fp8_e4m3,fp8_e5m2,fp4_e2m1— plus their 1–4 channel vector companions (fp16_2,bf16_4,fp8_e4m3_4, …), usable inexecuteOperations/Cast/SaturateCast/ thread fusion, e.g.:The same source builds as
_cpp(pure g++, zero CUDA headers) and_cu.Design
MiniFloatFormat+ one RN-even encoder/decoder pair) implements all five formats with NVIDIA-exact special-value policies (SATFINITE for fp8/fp4; e4m3 NaN=0x7F, no Inf; e2m1 NaN→+6, no NaN/Inf). No per-backend divergence of aggregate-ness/traits/operators, fully constexpr, and adding fp6/e8m0 later is a table entry. Layouts are bit-identical to__half/__nv_bfloat16/__nv_fp8_*/__nv_fp4_e2m1(static_asserted against the real headers in the_cuutest).__builtin_is_constant_evaluated()to the native ctors at runtime (hardwarecvton device);toNative()/fromNative()are free bit-casts. fp8/fp4 are pure software on all paths, so core headers never includecuda_fp8.h/cuda_fp4.hand the API does not vary with the installed toolkit (kills theFK_HAS_FP8-style__has_includetrap; sm_89+ cvt intrinsics are a follow-up).operator float()+ explicit ctors:h * 2.0fandfloat f = hwork, and thefloat-vs-__halfambiguity minefield can't happen. fp16/bf16 have full constexpr arithmetic/comparisons (promote-compute-demote in binary32 is correctly rounded: p ≥ 2·p_target+2). fp8/fp4 are conversion-only, exactly like CUDA.__nv_fp4_e2m1. The packed 2-per-byte format cannot fit theRawPtrone-addressable-element model and remains a fused-read use case.RFV*type lists append toVAll; the thread-fusion tables get an appended block (fp16→fp16_2likeshort→short2; 1-byte formats →x4 likechar→char4), pinned by exhaustivestatic_asserts including legacy row boundaries (the table is positional; a shifted entry would corrupt vectorized loads silently).std::is_fundamental→fk::validScalarinAreSS/AreSV/AreVS,vector_at,cxp::Exec,cmp_*,max/min/abs,Discard,ArrayVector;cmp_*/saturate_castpromote reduced operands explicitly tofloat;isnan/isinf/absclassify by bits (fp8/fp4 have no operators, and e4m3/e2m1 NaN is undetectable vias != s).std::numeric_limitsspecializations (raw-bit constexpr) makefk::maxValue/minValueand the cxp limits work unmodified — andvlimits.hnowstatic_assertsis_specializedinstead of silently producing zero limits for unknown types (numeric_limits<__half>is unspecialized in CUDA 13.3; aSaturateCastto it would previously have clamped everything to 0 with no diagnostic).CanCompoundnow also checks the element-level operator exists (before,fp8x2 += fp8x2would have been trait-visible and hard-errored inside the operator body).RGB2Grayno longer falls off the end of a non-void function for non-standard outputs.Verification (RTX PRO 6000, CUDA 13.3 + g++ 11.5)
utest_reduced_float_native_parity(_cu): bit-exact parity with NVIDIA's converters for all 65536 fp16 and all 65536 bf16 codes (software decoder vs__half2float/__bfloat162float), 2M sampled float encodes + a directed corpus vs the native ctors, all 256 fp8 codes and encode parity vs__nv_cvt_float_to_fp8(__NV_SATFINITE), all 16 fp4 codes + a 40k encode sweep vs__nv_fp4_e2m1(including NaN→+6 and nibble packing), plus constexpr-vs-runtime agreement inside the same TU.utest_reduced_float_types(_cpp+_cu): layout/trait/constexpr asserts, exhaustive software roundtrips (which caught a real bug during development: bf16 subnormals decode below the binary32 normal range), a double-precision arithmetic oracle for the operator surface, RN-even tie cases per format, and a CPU-purity tripwire (#errorifcuda_fp16.hleaks into a_cppTU — CUDA-present CI cannot catch that otherwise).utest_reduced_float_pipelines(_cpp+_cu):executeOperationsfor all five scalar types (odd width → non-thread-divisible path), vector pipelines, loose vs.then()-fused bit-equality, aTF::ENABLEDthread-fusion run, andTestCaseBuildercases (the builder gained a third specialization for reduced scalar I/O;equalValuescompares reduced types bit-exactly since a float tolerance quantizes to 0 on fp8/fp4 grids).float2 a; a *= 2.0L;would have stopped working becauseVBase<long double>is unregistered — covered now by the splitCanCompoundspecializations).Out of scope / follow-ups
attention/'sBf16AttentionRead/Fp8TokenDequantReadonto these types — needs an "identity read of element type E" trait first, so the mma/decode cp.async fast paths keep their compile-time dispatch (they currently match on the exact workaround op types).__CUDA_ARCH__branch point in the ctors)._cpptargets, to machine-check the no-CUDA-headers invariant (with the toolkit installed,cuda_fp16.hcompiles silently under plain g++, so current CI can't prove it).fk::fp16↔__halfexpressions are ambiguous by design (both convert implicitly to arithmetic types) — documented at thetoNative()/fromNative()interop functions.🤖 Generated with Claude Code