Skip to content

Centralized, dispatchable conversion funnel (cvt) with tables, bit-twiddles, and device overrides - #31

Open
AntonOresten wants to merge 2 commits into
mainfrom
vectorized
Open

Centralized, dispatchable conversion funnel (cvt) with tables, bit-twiddles, and device overrides#31
AntonOresten wants to merge 2 commits into
mainfrom
vectorized

Conversation

@AntonOresten

Copy link
Copy Markdown
Member

Summary

Replaces ad-hoc conversion entry points with a single dispatchable funnel that every conversion into a Microfloat reduces to:

Microfloats.cvt(::Type{T}, x,             mode::RoundingMode, policy::OverflowPolicy)  # scalar
Microfloats.cvt(::Type{NVector{T,N}}, xs, mode::RoundingMode, policy::OverflowPolicy)  # N lanes -> packed

Constructors (T(x, mode; overflow=...), new NVector{T,N}(::SVector/NVector, [mode]; overflow=...)) are thin sugar that only resolve defaults. Optimized implementations stack by ordinary dispatch specificity:

  1. cvt_generic — the existing bit-level rounding kernel, now device-portable: every throw goes through @noinline hooks (throw_no_nan, …) that device backends @device_override, so the ext no longer maintains a duplicated conversion body.
  2. @cvt_table Src => Dst@generated lookup-table methods built lazily per (mode, policy) by running every source bit pattern through cvt_generic: correct by construction, single L1 load per conversion. Registered for all built-in pairs; user @microfloat types opt in after definition (world-age safe by design — the generated method postdates the traits it consults).
  3. Hand bit-twiddles (src/specializations.jl) — exact E2M1 → E4M3/E4M3FN widening, scalar and packed-storage FP4x2/x4 → FP8x2/x4.
  4. Device overrides — CUDACoreExt rewritten (now ext/CUDACoreExt/, matching main's directory layout): narrow @device_override methods on exactly the (target, source, mode, policy) signatures with native PTX cvt instructions, each gated once on compute capability (compute_capability() folds at kernel compile — the static CC check, expressed once).

Semantics fixes over the draft ext

  • PTX cvt into fp8/fp6/fp4/ue8m0 destinations is .satfinite-only, i.e. SAT policy; the draft mapped fp8 natives to OVF, which hardware cannot express. OVF now always takes the generic path. For SAT the native/host agreement is exact even at the floatmax boundary (round-then-clamp ≡ clamp); NaN payloads compared NaN-aware.
  • FiniteOnly targets pre-guard isnan (hardware silently maps NaN where Microfloats throws); ue8m0 pre-guards negatives.
  • Inline-asm wrappers (with the b8 shim for e2m1, mirroring PTX.jl / <cuda_fp4.hpp>) instead of llvm.nvvm.* intrinsics, so availability doesn't depend on the LLVM version Julia ships.

Also ports the bits-based BFloat16 widening fix from main (e9a6faa) — table generation crosses dynamic call boundaries with boxed BFloat16s, which Julia ≥ 1.12 corrupts on AVX-512 BF16 CPUs (BFloat16s.jl#107 family).

Performance (1M-element broadcast, znver4)

E2M1 → FP8 ns/elem
bit-twiddle 0.69
generic (Float32 route) 3.0
lookup table 6.2

Tables gather in vectorized loops; twiddles autovectorize. Hence: table = default for every pair, twiddle = opt-in where it matters.

Testing

  • 17446/17446 pass, including exhaustive sweeps: every table pair × every bit pattern × 6 rounding modes × both policies ≡ the generic path (values and thrown errors); twiddles ≡ generic over all packed storage patterns.
  • GPU testset ran on real hardware (2× RTX 6000 Ada, sm_89): new saturating parity tests confirm native cvt.rn.satfinite.e4m3x2/e5m2x2 match the host bit-for-bit. fp6/fp4/ue8m0 natives need sm_100a and are exercised only through their generic fallback here — an on-device parity sweep on Blackwell is still needed before trusting those (ext header notes this).

Notes for review

  • where T on the device hook overrides is load-bearing (bare ::Type args don't specialize → dynamic calls → InvalidIRError on device).
  • Merging onto main will conflict in src/conversion.jl/src/macro.jl with e9a6faa (this branch re-applies it inside the rewrite) and trivially in ext/CUDACoreExt/CUDACoreExt.jl (add main's include("cudaDataType.jl")).
  • Future work: @cvt_widen generated exact-widening twiddles, f16x2/bf16x2 native sources, a PTX.jl-backed extension replacing the inline asm.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.74627% with 66 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.79%. Comparing base (c196f59) to head (72096ec).

Files with missing lines Patch % Lines
ext/CUDACoreExt/conversion.jl 0.00% 65 Missing ⚠️
src/conversion.jl 97.43% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main      #31       +/-   ##
===========================================
- Coverage   94.94%   81.79%   -13.15%     
===========================================
  Files          11       14        +3     
  Lines         336      456      +120     
===========================================
+ Hits          319      373       +54     
- Misses         17       83       +66     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

AntonOresten and others added 2 commits August 9, 2026 02:09
Every conversion into a Microfloat — constructors, convert, broadcasts,
packed vectors — now reduces to one positional surface:

    cvt(T, x, mode::RoundingMode, policy::OverflowPolicy)             # scalar
    cvt(NVector{T,N}, xs, mode::RoundingMode, policy::OverflowPolicy) # packed

Implementations stack by ordinary dispatch specificity:

- cvt_generic: the existing bit-level rounding kernel, made
  device-portable by routing all throws through @noinline hooks that
  device backends override (no more duplicated conversion body in ext).
- @cvt_table Src => Dst: @generated lookup-table methods, built lazily
  per (mode, policy) by running every source bit pattern through
  cvt_generic — correct by construction; registered for all built-in
  pairs, opt-in for user @microfloat types (world-age safe since the
  method is defined after the traits it consults).
- Hand bit-twiddles (specializations.jl): exact E2M1 -> E4M3/E4M3FN
  widening, scalar and packed-storage FP4x2/x4 -> FP8x2/x4. ~9x faster
  than the table and ~4x faster than generic in vectorized loops
  (tables gather; twiddles autovectorize).
- CUDACoreExt (rewritten, now a directory matching main's layout):
  @device_override on exactly the (target, source, mode, policy)
  combinations with native PTX instructions, gated once per method on
  compute capability (folds at kernel compile). All natively supported
  cvts are .satfinite, i.e. SAT policy — the draft's OVF mapping was
  wrong; OVF always takes the generic path. Native fp8 paths validated
  bit-exact against the host on sm_89.

Vector entry points: NVector{T,N}(::StaticArray, [mode]; overflow=...)
constructors normalize sources and route through the funnel, with a
same-eltype repack disambiguator against BitPacking's constructors.

Also ports the bits-based BFloat16 widening fix from main (e9a6faa):
required here because table generation crosses dynamic call boundaries
with boxed BFloat16s, which Julia >= 1.12 corrupts on AVX-512 BF16 CPUs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant