Files
TheRock/rocm-systems/emulation/rocjitsu/tests/hip_cvt_narrow_test.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

682 lines
21 KiB
C++

// Copyright (c) 2026 Advanced Micro Devices, Inc.
// SPDX-License-Identifier: MIT
#include <hip/hip_runtime.h>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <limits>
#include <vector>
#include "util/data_types.h"
#define HIP_CHECK(e) \
do { \
hipError_t e_ = (e); \
if (e_ != hipSuccess) { \
printf("HIP error %d: %s at %s:%d\n", e_, hipGetErrorString(e_), __FILE__, __LINE__); \
return 1; \
} \
} while (0)
// Vector types for builtins (ext_vector_type, not HIP vector types).
using v2f = float __attribute__((ext_vector_type(2)));
using v2s = short __attribute__((ext_vector_type(2)));
using v16f = float __attribute__((ext_vector_type(16)));
using v32f = float __attribute__((ext_vector_type(32)));
using v6u = unsigned __attribute__((ext_vector_type(6)));
static int g_pass = 0;
static int g_fail = 0;
static int g_skip = 0;
static void pass(const char *name) {
printf("[ PASS ] %s\n", name);
g_pass++;
}
static void fail(const char *name, const char *msg) {
printf("[ FAIL ] %s: %s\n", name, msg);
g_fail++;
}
static void skip(const char *name) {
printf("[ SKIP ] %s\n", name);
g_skip++;
}
static bool have_device() {
int n = 0;
return hipGetDeviceCount(&n) == hipSuccess && n > 0;
}
// ---- Non-scaled FP8 kernels ----
__global__ void k_pk_fp8_f32(float a, float b, int *out) {
*out = __builtin_amdgcn_cvt_pk_fp8_f32(a, b, 0, false);
}
__global__ void k_pk_bf8_f32(float a, float b, int *out) {
*out = __builtin_amdgcn_cvt_pk_bf8_f32(a, b, 0, false);
}
__global__ void k_pk_f32_fp8(int src, float *out) {
v2f r = __builtin_amdgcn_cvt_pk_f32_fp8(src, false);
out[0] = r[0];
out[1] = r[1];
}
__global__ void k_pk_f32_bf8(int src, float *out) {
v2f r = __builtin_amdgcn_cvt_pk_f32_bf8(src, false);
out[0] = r[0];
out[1] = r[1];
}
__global__ void k_sr_fp8_f32(float val, int rnd, int *out) {
*out = __builtin_amdgcn_cvt_sr_fp8_f32(val, rnd, 0, 0);
}
__global__ void k_sr_bf8_f32(float val, int rnd, int *out) {
*out = __builtin_amdgcn_cvt_sr_bf8_f32(val, rnd, 0, 0);
}
// ---- Scaled FP8/BF8 kernels ----
__global__ void k_scalef32_pk_fp8_f32(float a, float b, float scale, unsigned *out) {
v2s zero = {0, 0};
v2s r = __builtin_amdgcn_cvt_scalef32_pk_fp8_f32(zero, a, b, scale, false);
unsigned packed;
__builtin_memcpy(&packed, &r, sizeof(packed));
*out = packed;
}
__global__ void k_scalef32_pk_bf8_f32(float a, float b, float scale, unsigned *out) {
v2s zero = {0, 0};
v2s r = __builtin_amdgcn_cvt_scalef32_pk_bf8_f32(zero, a, b, scale, false);
unsigned packed;
__builtin_memcpy(&packed, &r, sizeof(packed));
*out = packed;
}
__global__ void k_scalef32_pk_f32_fp8(unsigned src, float scale, float *out) {
v2f r = __builtin_amdgcn_cvt_scalef32_pk_f32_fp8(src, scale, false);
out[0] = r[0];
out[1] = r[1];
}
__global__ void k_scalef32_pk_f32_bf8(unsigned src, float scale, float *out) {
v2f r = __builtin_amdgcn_cvt_scalef32_pk_f32_bf8(src, scale, false);
out[0] = r[0];
out[1] = r[1];
}
template <int BYTE> __global__ void k_scalef32_f32_fp8(unsigned src, float scale, float *out) {
out[0] = __builtin_amdgcn_cvt_scalef32_f32_fp8(src, scale, BYTE);
}
__global__ void k_scalef32_sr_fp8_f32(float val, unsigned rnd, float scale, int *out) {
*out = __builtin_amdgcn_cvt_scalef32_sr_fp8_f32(0, val, rnd, scale, 0);
}
__global__ void k_scalef32_sr_bf8_f32(float val, unsigned rnd, float scale, int *out) {
*out = __builtin_amdgcn_cvt_scalef32_sr_bf8_f32(0, val, rnd, scale, 0);
}
// ---- Scaled FP4 kernels ----
__global__ void k_scalef32_pk_fp4_f32(float a, float b, float scale, int *out) {
*out = __builtin_amdgcn_cvt_scalef32_pk_fp4_f32(0, a, b, scale, 0);
}
__global__ void k_scalef32_pk_f32_fp4(unsigned src, float scale, float *out) {
v2f r = __builtin_amdgcn_cvt_scalef32_pk_f32_fp4(src, scale, 0);
out[0] = r[0];
out[1] = r[1];
}
__global__ void k_scalef32_sr_pk_fp4_f32(float a, float b, unsigned seed, float scale, int *out) {
v2f data = {a, b};
*out = __builtin_amdgcn_cvt_scalef32_sr_pk_fp4_f32(0, data, seed, scale, 0);
}
// ---- Wide FP6/BF6 kernels ----
__global__ void k_scalef32_2xpk16_fp6_f32(const float *in, unsigned *out, float scale) {
v16f lo, hi;
for (int i = 0; i < 16; ++i) {
lo[i] = in[i];
hi[i] = in[16 + i];
}
v6u r = __builtin_amdgcn_cvt_scalef32_2xpk16_fp6_f32(lo, hi, scale);
for (int i = 0; i < 6; ++i)
out[i] = r[i];
}
__global__ void k_scalef32_pk32_f32_fp6(const unsigned *in, float *out, float scale) {
v6u src;
for (int i = 0; i < 6; ++i)
src[i] = in[i];
v32f r = __builtin_amdgcn_cvt_scalef32_pk32_f32_fp6(src, scale);
for (int i = 0; i < 32; ++i)
out[i] = r[i];
}
__global__ void k_scalef32_2xpk16_bf6_f32(const float *in, unsigned *out, float scale) {
v16f lo, hi;
for (int i = 0; i < 16; ++i) {
lo[i] = in[i];
hi[i] = in[16 + i];
}
v6u r = __builtin_amdgcn_cvt_scalef32_2xpk16_bf6_f32(lo, hi, scale);
for (int i = 0; i < 6; ++i)
out[i] = r[i];
}
__global__ void k_scalef32_pk32_f32_bf6(const unsigned *in, float *out, float scale) {
v6u src;
for (int i = 0; i < 6; ++i)
src[i] = in[i];
v32f r = __builtin_amdgcn_cvt_scalef32_pk32_f32_bf6(src, scale);
for (int i = 0; i < 32; ++i)
out[i] = r[i];
}
// ---- Device memory helpers ----
template <class T> static T *alloc_dev(size_t n) {
T *d = nullptr;
(void)hipMalloc(&d, n * sizeof(T));
return d;
}
template <class T> static T read_dev(const T *d) {
T h;
(void)hipMemcpy(&h, d, sizeof(T), hipMemcpyDeviceToHost);
return h;
}
template <class T> static void write_dev(T *d, const T &val) {
(void)hipMemcpy(d, &val, sizeof(T), hipMemcpyHostToDevice);
}
template <class T> static std::vector<T> read_dev_vec(const T *d, size_t n) {
std::vector<T> h(n);
(void)hipMemcpy(h.data(), d, n * sizeof(T), hipMemcpyDeviceToHost);
return h;
}
template <class T> static T *to_dev(const std::vector<T> &h) {
T *d = alloc_dev<T>(h.size());
(void)hipMemcpy(d, h.data(), h.size() * sizeof(T), hipMemcpyHostToDevice);
return d;
}
// ---- Test implementations ----
static int test_nonscaled_pk_fp8_f32() {
int *dO = alloc_dev<int>(1);
k_pk_fp8_f32<<<1, 1>>>(1.0f, 2.0f, dO);
HIP_CHECK(hipDeviceSynchronize());
int result = read_dev(dO);
uint8_t lo = result & 0xFF;
uint8_t hi = (result >> 8) & 0xFF;
uint8_t exp_lo = util::f32_to_fp8_e4m3_rne(1.0f);
uint8_t exp_hi = util::f32_to_fp8_e4m3_rne(2.0f);
(void)hipFree(dO);
if (lo != exp_lo || hi != exp_hi) {
printf(" got lo=%02x hi=%02x, expected lo=%02x hi=%02x\n", lo, hi, exp_lo, exp_hi);
return 1;
}
return 0;
}
static int test_nonscaled_pk_bf8_f32() {
int *dO = alloc_dev<int>(1);
k_pk_bf8_f32<<<1, 1>>>(1.0f, 2.0f, dO);
HIP_CHECK(hipDeviceSynchronize());
int result = read_dev(dO);
uint8_t lo = result & 0xFF;
uint8_t hi = (result >> 8) & 0xFF;
uint8_t exp_lo = util::f32_to_bf8_e5m2_rne(1.0f);
uint8_t exp_hi = util::f32_to_bf8_e5m2_rne(2.0f);
(void)hipFree(dO);
if (lo != exp_lo || hi != exp_hi) {
printf(" got lo=%02x hi=%02x, expected lo=%02x hi=%02x\n", lo, hi, exp_lo, exp_hi);
return 1;
}
return 0;
}
static int test_nonscaled_pk_f32_fp8() {
float *dO = alloc_dev<float>(2);
uint8_t fp8_1 = util::f32_to_fp8_e4m3_rne(1.0f);
uint8_t fp8_2 = util::f32_to_fp8_e4m3_rne(2.0f);
int packed = fp8_1 | (fp8_2 << 8);
k_pk_f32_fp8<<<1, 1>>>(packed, dO);
HIP_CHECK(hipDeviceSynchronize());
auto got = read_dev_vec(dO, 2);
(void)hipFree(dO);
if (got[0] != 1.0f || got[1] != 2.0f) {
printf(" got [%f, %f], expected [1.0, 2.0]\n", got[0], got[1]);
return 1;
}
return 0;
}
static int test_nonscaled_pk_f32_bf8() {
float *dO = alloc_dev<float>(2);
uint8_t bf8_1 = util::f32_to_bf8_e5m2_rne(1.0f);
uint8_t bf8_2 = util::f32_to_bf8_e5m2_rne(2.0f);
int packed = bf8_1 | (bf8_2 << 8);
k_pk_f32_bf8<<<1, 1>>>(packed, dO);
HIP_CHECK(hipDeviceSynchronize());
auto got = read_dev_vec(dO, 2);
(void)hipFree(dO);
if (got[0] != 1.0f || got[1] != 2.0f) {
printf(" got [%f, %f], expected [1.0, 2.0]\n", got[0], got[1]);
return 1;
}
return 0;
}
static int test_nonscaled_sr_fp8_f32() {
int *dO = alloc_dev<int>(1);
k_sr_fp8_f32<<<1, 1>>>(1.0f, 0, dO);
HIP_CHECK(hipDeviceSynchronize());
int result = read_dev(dO);
uint8_t got = result & 0xFF;
uint8_t expected = util::f32_to_fp8_e4m3_sr(1.0f, 0);
(void)hipFree(dO);
if (got != expected) {
printf(" got %02x, expected %02x\n", got, expected);
return 1;
}
return 0;
}
static int test_nonscaled_sr_bf8_f32() {
int *dO = alloc_dev<int>(1);
k_sr_bf8_f32<<<1, 1>>>(1.0f, 0, dO);
HIP_CHECK(hipDeviceSynchronize());
int result = read_dev(dO);
uint8_t got = result & 0xFF;
uint8_t expected = util::f32_to_bf8_e5m2_sr(1.0f, 0);
(void)hipFree(dO);
if (got != expected) {
printf(" got %02x, expected %02x\n", got, expected);
return 1;
}
return 0;
}
// ---- Scaled FP8 tests ----
static int test_scaled_pk_fp8_f32() {
unsigned *dO = alloc_dev<unsigned>(1);
k_scalef32_pk_fp8_f32<<<1, 1>>>(1.0f, 2.0f, 1.0f, dO);
HIP_CHECK(hipDeviceSynchronize());
unsigned packed = read_dev(dO);
uint8_t lo = packed & 0xFF;
uint8_t hi = (packed >> 8) & 0xFF;
uint8_t exp_lo = util::f32_to_fp8_e4m3_rne(1.0f);
uint8_t exp_hi = util::f32_to_fp8_e4m3_rne(2.0f);
(void)hipFree(dO);
if (lo != exp_lo || hi != exp_hi) {
printf(" got lo=%02x hi=%02x, expected lo=%02x hi=%02x\n", lo, hi, exp_lo, exp_hi);
return 1;
}
return 0;
}
static int test_scaled_pk_bf8_f32() {
unsigned *dO = alloc_dev<unsigned>(1);
k_scalef32_pk_bf8_f32<<<1, 1>>>(1.0f, 2.0f, 1.0f, dO);
HIP_CHECK(hipDeviceSynchronize());
unsigned packed = read_dev(dO);
uint8_t lo = packed & 0xFF;
uint8_t hi = (packed >> 8) & 0xFF;
uint8_t exp_lo = util::f32_to_bf8_e5m2_rne(1.0f);
uint8_t exp_hi = util::f32_to_bf8_e5m2_rne(2.0f);
(void)hipFree(dO);
if (lo != exp_lo || hi != exp_hi) {
printf(" got lo=%02x hi=%02x, expected lo=%02x hi=%02x\n", lo, hi, exp_lo, exp_hi);
return 1;
}
return 0;
}
static int test_scaled_pk_f32_fp8() {
float *dO = alloc_dev<float>(2);
uint8_t fp8_1 = util::f32_to_fp8_e4m3_rne(1.0f);
uint8_t fp8_2 = util::f32_to_fp8_e4m3_rne(2.0f);
unsigned packed = fp8_1 | (fp8_2 << 8);
k_scalef32_pk_f32_fp8<<<1, 1>>>(packed, 1.0f, dO);
HIP_CHECK(hipDeviceSynchronize());
auto got = read_dev_vec(dO, 2);
(void)hipFree(dO);
if (got[0] != 1.0f || got[1] != 2.0f) {
printf(" got [%f, %f], expected [1.0, 2.0]\n", got[0], got[1]);
return 1;
}
return 0;
}
static int test_scaled_pk_f32_bf8() {
float *dO = alloc_dev<float>(2);
uint8_t bf8_1 = util::f32_to_bf8_e5m2_rne(1.0f);
uint8_t bf8_2 = util::f32_to_bf8_e5m2_rne(2.0f);
unsigned packed = bf8_1 | (bf8_2 << 8);
k_scalef32_pk_f32_bf8<<<1, 1>>>(packed, 1.0f, dO);
HIP_CHECK(hipDeviceSynchronize());
auto got = read_dev_vec(dO, 2);
(void)hipFree(dO);
if (got[0] != 1.0f || got[1] != 2.0f) {
printf(" got [%f, %f], expected [1.0, 2.0]\n", got[0], got[1]);
return 1;
}
return 0;
}
static int test_scaled_f32_fp8_byte_sel() {
float *dO = alloc_dev<float>(1);
uint8_t fp8_vals[4] = {
util::f32_to_fp8_e4m3_rne(1.0f),
util::f32_to_fp8_e4m3_rne(2.0f),
util::f32_to_fp8_e4m3_rne(3.0f),
util::f32_to_fp8_e4m3_rne(4.0f),
};
unsigned packed = fp8_vals[0] | (fp8_vals[1] << 8) | (fp8_vals[2] << 16) | (fp8_vals[3] << 24);
float expected[4] = {1.0f, 2.0f, 3.0f, 4.0f};
auto run_byte = [&](int byte) -> int {
switch (byte) {
case 0:
k_scalef32_f32_fp8<0><<<1, 1>>>(packed, 1.0f, dO);
break;
case 1:
k_scalef32_f32_fp8<1><<<1, 1>>>(packed, 1.0f, dO);
break;
case 2:
k_scalef32_f32_fp8<2><<<1, 1>>>(packed, 1.0f, dO);
break;
case 3:
k_scalef32_f32_fp8<3><<<1, 1>>>(packed, 1.0f, dO);
break;
}
return hipDeviceSynchronize();
};
for (int byte = 0; byte < 4; ++byte) {
HIP_CHECK(static_cast<hipError_t>(run_byte(byte)));
float got = read_dev(dO);
if (got != expected[byte]) {
printf(" byte_sel=%d: got %f, expected %f\n", byte, got, expected[byte]);
(void)hipFree(dO);
return 1;
}
}
(void)hipFree(dO);
return 0;
}
static int test_scaled_sr_fp8_f32() {
int *dO = alloc_dev<int>(1);
k_scalef32_sr_fp8_f32<<<1, 1>>>(1.0f, 0u, 1.0f, dO);
HIP_CHECK(hipDeviceSynchronize());
int result = read_dev(dO);
uint8_t got = result & 0xFF;
uint8_t expected = util::f32_to_fp8_e4m3_sr(1.0f, 0);
(void)hipFree(dO);
if (got != expected) {
printf(" got %02x, expected %02x\n", got, expected);
return 1;
}
return 0;
}
static int test_scaled_sr_bf8_f32() {
int *dO = alloc_dev<int>(1);
k_scalef32_sr_bf8_f32<<<1, 1>>>(1.0f, 0u, 1.0f, dO);
HIP_CHECK(hipDeviceSynchronize());
int result = read_dev(dO);
uint8_t got = result & 0xFF;
uint8_t expected = util::f32_to_bf8_e5m2_sr(1.0f, 0);
(void)hipFree(dO);
if (got != expected) {
printf(" got %02x, expected %02x\n", got, expected);
return 1;
}
return 0;
}
// ---- FP4 tests ----
static int test_scaled_fp4_roundtrip() {
// Hardcoded truth table for positive FP4 E2M1 values.
static constexpr float kFp4Truth[8] = {0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 6.0f};
int *dNarrow = alloc_dev<int>(1);
float *dWide = alloc_dev<float>(2);
int errs = 0;
for (int ca = 0; ca < 8; ++ca) {
for (int cb = 0; cb < 8; ++cb) {
float a = kFp4Truth[ca];
float b = kFp4Truth[cb];
k_scalef32_pk_fp4_f32<<<1, 1>>>(a, b, 1.0f, dNarrow);
HIP_CHECK(hipDeviceSynchronize());
int packed = read_dev(dNarrow);
uint8_t nibble0 = packed & 0xF;
uint8_t nibble1 = (packed >> 4) & 0xF;
if (nibble0 != ca || nibble1 != cb) {
printf(" narrow: a=%d b=%d -> nib0=%d nib1=%d (expected %d %d)\n", ca, cb, nibble0,
nibble1, ca, cb);
errs++;
if (errs > 5)
break;
}
k_scalef32_pk_f32_fp4<<<1, 1>>>(static_cast<unsigned>(packed), 1.0f, dWide);
HIP_CHECK(hipDeviceSynchronize());
auto widened = read_dev_vec(dWide, 2);
if (widened[0] != a || widened[1] != b) {
printf(" widen: a=%d b=%d -> [%f, %f] (expected [%f, %f])\n", ca, cb, widened[0],
widened[1], a, b);
errs++;
if (errs > 5)
break;
}
}
if (errs > 5)
break;
}
(void)hipFree(dNarrow);
(void)hipFree(dWide);
return errs > 0 ? 1 : 0;
}
static int test_scaled_sr_pk_fp4_f32() {
static constexpr float kFp4Truth[8] = {0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 6.0f};
int *dO = alloc_dev<int>(1);
int errs = 0;
for (int ca = 0; ca < 8; ++ca) {
for (int cb = 0; cb < 8; ++cb) {
float a = kFp4Truth[ca];
float b = kFp4Truth[cb];
k_scalef32_sr_pk_fp4_f32<<<1, 1>>>(a, b, 0xdeadbeefu, 1.0f, dO);
HIP_CHECK(hipDeviceSynchronize());
int packed = read_dev(dO);
uint8_t nibble0 = packed & 0xF;
uint8_t nibble1 = (packed >> 4) & 0xF;
if (nibble0 != ca || nibble1 != cb) {
printf(" sr_pk_fp4: a=%d b=%d -> nib0=%d nib1=%d (expected %d %d)\n", ca, cb, nibble0,
nibble1, ca, cb);
errs++;
if (errs > 5)
break;
}
}
if (errs > 5)
break;
}
(void)hipFree(dO);
return errs > 0 ? 1 : 0;
}
// ---- Wide FP6/BF6 tests ----
// Round-trip: narrow 32 floats to 6-bit packed, widen back, compare against
// the original values (which are exact FP6/BF6 representable values).
static int test_wide_fp6_roundtrip() {
float *dIn = alloc_dev<float>(32);
unsigned *dPacked = alloc_dev<unsigned>(6);
float *dOut = alloc_dev<float>(32);
int errs = 0;
for (int base = 0; base < 64; base += 32) {
std::vector<float> in(32);
for (int c = 0; c < 32; ++c)
in[c] = util::fp6_e2m3_to_f32(static_cast<uint8_t>(base + c));
(void)hipMemcpy(dIn, in.data(), 32 * sizeof(float), hipMemcpyHostToDevice);
k_scalef32_2xpk16_fp6_f32<<<1, 1>>>(dIn, dPacked, 1.0f);
HIP_CHECK(hipDeviceSynchronize());
k_scalef32_pk32_f32_fp6<<<1, 1>>>(dPacked, dOut, 1.0f);
HIP_CHECK(hipDeviceSynchronize());
auto widened = read_dev_vec(dOut, 32);
// 2xpk16 interleaves: field[2k]=lo[k], field[2k+1]=hi[k]
// pk32_f32 reads contiguously, so out[2k]=in[k], out[2k+1]=in[16+k]
for (int k = 0; k < 16; ++k) {
if (widened[2 * k] != in[k]) {
printf(" fp6 rt base=%d lo[%d]: got %f, expected %f\n", base, k, widened[2 * k], in[k]);
errs++;
}
if (widened[2 * k + 1] != in[16 + k]) {
printf(" fp6 rt base=%d hi[%d]: got %f, expected %f\n", base, k, widened[2 * k + 1],
in[16 + k]);
errs++;
}
}
}
(void)hipFree(dIn);
(void)hipFree(dPacked);
(void)hipFree(dOut);
return errs > 0 ? 1 : 0;
}
static int test_wide_bf6_roundtrip() {
float *dIn = alloc_dev<float>(32);
unsigned *dPacked = alloc_dev<unsigned>(6);
float *dOut = alloc_dev<float>(32);
int errs = 0;
for (int base = 0; base < 64; base += 32) {
std::vector<float> in(32);
for (int c = 0; c < 32; ++c)
in[c] = util::bf6_e3m2_to_f32(static_cast<uint8_t>(base + c));
(void)hipMemcpy(dIn, in.data(), 32 * sizeof(float), hipMemcpyHostToDevice);
k_scalef32_2xpk16_bf6_f32<<<1, 1>>>(dIn, dPacked, 1.0f);
HIP_CHECK(hipDeviceSynchronize());
k_scalef32_pk32_f32_bf6<<<1, 1>>>(dPacked, dOut, 1.0f);
HIP_CHECK(hipDeviceSynchronize());
auto widened = read_dev_vec(dOut, 32);
// 2xpk16 interleaves: field[2k]=lo[k], field[2k+1]=hi[k]
// pk32_f32 reads contiguously, so out[2k]=in[k], out[2k+1]=in[16+k]
for (int k = 0; k < 16; ++k) {
if (widened[2 * k] != in[k]) {
printf(" bf6 rt base=%d lo[%d]: got %f, expected %f\n", base, k, widened[2 * k], in[k]);
errs++;
}
if (widened[2 * k + 1] != in[16 + k]) {
printf(" bf6 rt base=%d hi[%d]: got %f, expected %f\n", base, k, widened[2 * k + 1],
in[16 + k]);
errs++;
}
}
}
(void)hipFree(dIn);
(void)hipFree(dPacked);
(void)hipFree(dOut);
return errs > 0 ? 1 : 0;
}
// ---- Scale edge cases ----
static int test_scale_variation() {
unsigned *dO = alloc_dev<unsigned>(1);
k_scalef32_pk_fp8_f32<<<1, 1>>>(1.0f, 2.0f, 2.0f, dO);
HIP_CHECK(hipDeviceSynchronize());
unsigned packed = read_dev(dO);
uint8_t lo = packed & 0xFF;
uint8_t hi = (packed >> 8) & 0xFF;
uint8_t exp_lo = util::f32_to_fp8_e4m3_rne(1.0f / 2.0f);
uint8_t exp_hi = util::f32_to_fp8_e4m3_rne(2.0f / 2.0f);
(void)hipFree(dO);
if (lo != exp_lo || hi != exp_hi) {
printf(" scale=2: got lo=%02x hi=%02x, expected lo=%02x hi=%02x\n", lo, hi, exp_lo, exp_hi);
return 1;
}
return 0;
}
static int test_nan_inf_saturation() {
int *dO = alloc_dev<int>(1);
float nan_val = std::numeric_limits<float>::quiet_NaN();
float inf_val = std::numeric_limits<float>::infinity();
k_scalef32_pk_fp4_f32<<<1, 1>>>(nan_val, inf_val, 1.0f, dO);
HIP_CHECK(hipDeviceSynchronize());
int packed = read_dev(dO);
uint8_t nib0 = packed & 0xF;
uint8_t nib1 = (packed >> 4) & 0xF;
(void)hipFree(dO);
if (nib0 != 0) {
printf(" NaN->fp4: got %d, expected 0\n", nib0);
return 1;
}
if (nib1 != 0x7) {
printf(" Inf->fp4: got %d, expected 7 (max)\n", nib1);
return 1;
}
return 0;
}
// ---- Main ----
struct TestCase {
const char *name;
int (*func)();
};
int main() {
if (!have_device()) {
printf("No HIP device found, skipping all tests\n");
return 0;
}
TestCase tests[] = {
{"NonScaled.PkFp8F32", test_nonscaled_pk_fp8_f32},
{"NonScaled.PkBf8F32", test_nonscaled_pk_bf8_f32},
{"NonScaled.PkF32Fp8", test_nonscaled_pk_f32_fp8},
{"NonScaled.PkF32Bf8", test_nonscaled_pk_f32_bf8},
{"NonScaled.SrFp8F32", test_nonscaled_sr_fp8_f32},
{"NonScaled.SrBf8F32", test_nonscaled_sr_bf8_f32},
{"Scaled.PkFp8F32", test_scaled_pk_fp8_f32},
{"Scaled.PkBf8F32", test_scaled_pk_bf8_f32},
{"Scaled.PkF32Fp8", test_scaled_pk_f32_fp8},
{"Scaled.PkF32Bf8", test_scaled_pk_f32_bf8},
{"Scaled.F32Fp8ByteSel", test_scaled_f32_fp8_byte_sel},
{"Scaled.SrFp8F32", test_scaled_sr_fp8_f32},
{"Scaled.SrBf8F32", test_scaled_sr_bf8_f32},
{"Scaled.Fp4RoundTrip", test_scaled_fp4_roundtrip},
{"Scaled.SrPkFp4F32", test_scaled_sr_pk_fp4_f32},
{"Wide.Fp6RoundTrip", test_wide_fp6_roundtrip},
{"Wide.Bf6RoundTrip", test_wide_bf6_roundtrip},
{"Scale.Variation", test_scale_variation},
{"Edge.NanInfSaturation", test_nan_inf_saturation},
};
for (auto &tc : tests) {
printf("[ RUN ] %s\n", tc.name);
int result = tc.func();
if (result == 0)
pass(tc.name);
else
fail(tc.name, "see above");
}
printf("\n%d passed, %d failed, %d skipped\n", g_pass, g_fail, g_skip);
return g_fail > 0 ? 1 : 0;
}