Skip to content

feat(cuda-device): add f16x2 and bf16x2 unpacking to f32#476

Open
honeyspoon wants to merge 1 commit into
NVlabs:mainfrom
honeyspoon:up/f16x2-unpack
Open

feat(cuda-device): add f16x2 and bf16x2 unpacking to f32#476
honeyspoon wants to merge 1 commit into
NVlabs:mainfrom
honeyspoon:up/f16x2-unpack

Conversation

@honeyspoon

Copy link
Copy Markdown
Contributor

cuda_device::convert has 19 generated conversions and every one runs f32 -> packed narrow type. The inverse is missing, so reading packed f16 pairs means open-coding the bit arithmetic at each call site:

let w0 = f16::from_bits((quad & 0xFFFF) as u16) as f32;
let w1 = f16::from_bits(((quad >> 16) & 0xFFFF) as u16) as f32;

Added

Function Purpose
cvt_f32x2_f16x2(u32) -> (f32, f32) inverse of cvt_f16x2_f32
cvt_f32_f16x2_lo / _hi one half only, so the other conversion is not emitted
cvt_f32x2_bf16x2(u32) -> (f32, f32) inverse of cvt_rz_bf16x2_f32

Why hand-written rather than generated

Worth justifying, since the preference is to route intrinsics through the generator. I checked the pinned LLVM metadata: 2569 NVVM intrinsics, none of which unpack.

  • Every ff2f16x2_* and ff2bf16x2_* record is the packing direction.
  • The *_to_f16x2_* records take fp8/fp4 sources, not f16x2.
  • PTX has no single unpacking instruction either: a split is mov.b32 into two 16-bit registers plus two cvt.f32.f16, which is exactly what these casts lower to.

So there is nothing for the generator to admit. These are plain safe functions — no ptx_asm!, no unsafe.

Tests

  • Half orderingcvt_f16x2_f32 documents its first argument as the low half, so a swap here would be silent. Pinned in both directions.
  • Sign isolation — a negative value in one half must not bleed into the other, which is what an arithmetic shift instead of a logical one would do.
  • Exhaustive — all 65536 f16 bit patterns round-trip, including subnormals and both zeroes, skipping only NaN since it is not ==-comparable.

4 tests, fmt --check clean, zero warnings.

`convert` has 19 generated conversions and every one runs f32 -> packed narrow
type. The inverse is missing, so reading packed f16 pairs means open-coding the
bit arithmetic at each call site:

    let w0 = f16::from_bits((quad & 0xFFFF) as u16) as f32;
    let w1 = f16::from_bits(((quad >> 16) & 0xFFFF) as u16) as f32;

Adds `cvt_f32x2_f16x2`, single-half `cvt_f32_f16x2_{lo,hi}` for when only one
value is wanted so the other conversion is not emitted, and
`cvt_f32x2_bf16x2`.

Hand-written rather than generated, which is worth justifying given the
preference for routing intrinsics through the generator. The pinned LLVM
metadata has 2569 NVVM intrinsics and none of them unpack: every `ff2f16x2_*`
and `ff2bf16x2_*` record is the packing direction, and the `*_to_f16x2_*`
records take fp8/fp4 sources. PTX has no single unpacking instruction either -
a split is `mov.b32` into two 16-bit registers plus two `cvt.f32.f16`, which is
what these casts lower to. So there is nothing for the generator to admit, and
these are plain safe functions with no `ptx_asm!` and no unsafe.

Tests pin the half ordering, since `cvt_f16x2_f32` documents its first argument
as the low half and a swap here would be silent; check that sign bits do not
bleed across halves, which is what an arithmetic shift instead of a logical one
would do; and sweep all 65536 f16 bit patterns including subnormals and both
zeroes, skipping only NaN since it is not `==`-comparable.

Signed-off-by: abder <bobmatt911@gmail.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