use std::sync::Arc;
use cudarc::driver::sys::CUdeviceptr;
use cudarc::driver::{LaunchConfig, PushKernelArg};
use onnx_runtime_ep_api::{DeviceBuffer, DeviceGraphResource, EpError, ExecutionProvider, Result};
use onnx_runtime_ep_cpu::kernels::planar_block_quant::{
FP4_MICROSCALE_BLOCK as CPU_FP4_MICROSCALE_BLOCK, PlanarBankIdentity, PlanarBlockFormat,
PlanarLayout, validate_planar_values,
};
use onnx_runtime_ir::DataType;
use onnx_runtime_memory_governor::ProviderContextIdentity;
use crate::error::driver_err;
use crate::provider::{CudaExecutionProvider, CudaSealedAllocation};
use crate::runtime::{CudaRuntime, cuptr};
pub(crate) const FP4_MICROSCALE_BLOCK: usize = 32;
pub(crate) const FP4_PACK_FACTOR: usize = 2;
pub const PLANAR_FORMAT_BLOCK_FP8: i32 = 0;
pub const PLANAR_FORMAT_FP4_PLANAR: i32 = 1;
pub(crate) const PLANAR_LINEAR_MODULE: &str = "planar_block_decode_linear_v1";
pub(crate) const PLANAR_BLOCK_DECODE_CUH: &str = r#"
#include <cuda_fp16.h>
#include <cuda_bf16.h>
// E2M1 value LUT, sign bit included (index 8 is -0.0). Matches the CPU
// onnx_runtime_ep_cpu::kernels::block_dequant E2M1 table bit-for-bit.
__device__ __constant__ float planar_e2m1_lut[16] = {
0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 6.0f,
-0.0f, -0.5f, -1.0f, -1.5f, -2.0f, -3.0f, -4.0f, -6.0f
};
// UE8M0 power-of-two scale: 0xff reserved (NaN), 0 -> 2^-127, else 2^(e-127).
// Matches onnx_runtime_ep_cpu::kernels::block_dequant::decode_e8m0_scale.
__device__ __forceinline__ float planar_e8m0_scale(unsigned char e) {
if (e == 0xffu) return __uint_as_float(0x7fc00000u);
if (e == 0u) return __uint_as_float(0x00400000u);
return __uint_as_float((unsigned int)e << 23);
}
__device__ __forceinline__ float planar_e2m1(unsigned char code) {
return planar_e2m1_lut[code & 15u];
}
// E4M3FN: exp==15 && mant==7 reserved (NaN); subnormal m*2^-9; normal
// (1+m/8)*2^(e-7). Matches decode_e4m3fn.
__device__ __forceinline__ float planar_e4m3(unsigned char code) {
const float sign = (code & 0x80u) ? -1.0f : 1.0f;
const unsigned int e = (code >> 3) & 15u;
const unsigned int m = code & 7u;
if (e == 15u && m == 7u) return __uint_as_float(0x7fc00000u);
return sign * (e == 0u ? (float)m * 0x1p-9f
: (1.0f + (float)m / 8.0f) * exp2f((int)e - 7));
}
__device__ __forceinline__ float planar_bf8_element(
const unsigned char* packed, const unsigned char* scale,
int out_features, int in_features, int bs0, int bs1,
int out_row, int in_col) {
const int scale_cols = 1 + (in_features - 1) / bs1;
const unsigned char se = scale[(long long)(out_row / bs0) * scale_cols + (in_col / bs1)];
const unsigned char code = packed[(long long)out_row * in_features + in_col];
return planar_e4m3(code) * planar_e8m0_scale(se);
}
__device__ __forceinline__ float planar_fp4_element(
const unsigned char* packed, const unsigned char* scale,
int out_features, int in_features,
int out_row, int in_col) {
const int scale_cols = in_features / 32;
const unsigned char se = scale[(long long)out_row * scale_cols + (in_col / 32)];
const unsigned char byte = packed[(long long)out_row * (in_features / 2) + (in_col / 2)];
const unsigned char nib = (in_col & 1) ? (byte >> 4) : (byte & 0x0fu);
return planar_e2m1(nib) * planar_e8m0_scale(se);
}
// Activation load / result store helpers so one templated body serves every
// activation precision. Decode + accumulation stay in f32 regardless.
__device__ __forceinline__ float planar_to_f32(float v) { return v; }
__device__ __forceinline__ float planar_to_f32(__half v) { return __half2float(v); }
__device__ __forceinline__ float planar_to_f32(__nv_bfloat16 v) { return __bfloat162float(v); }
__device__ __forceinline__ void planar_store(float* out, float v) { *out = v; }
__device__ __forceinline__ void planar_store(__half* out, float v) { *out = __float2half_rn(v); }
__device__ __forceinline__ void planar_store(__nv_bfloat16* out, float v) { *out = __float2bfloat16(v); }
// C[M,N] = A[M,K] * W[K,N]; W decoded per (out_row = n, in_col = k). One thread
// per (row, col). format: 0 = block_fp8, 1 = fp4_planar.
template<typename T>
__device__ __forceinline__ void planar_linear_impl(
const T* a, const unsigned char* packed, const unsigned char* scale,
T* c, int m_rows, int in_features, int out_features,
int format, int bs0, int bs1) {
const long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;
if (idx >= (long long)m_rows * out_features) return;
const int row = (int)(idx / out_features);
const int col = (int)(idx % out_features);
const T* a_row = a + (long long)row * in_features;
float acc = 0.0f;
for (int k = 0; k < in_features; ++k) {
const float w = (format == 0)
? planar_bf8_element(packed, scale, out_features, in_features, bs0, bs1, col, k)
: planar_fp4_element(packed, scale, out_features, in_features, col, k);
acc += planar_to_f32(a_row[k]) * w;
}
planar_store(&c[(long long)row * out_features + col], acc);
}
extern "C" __global__ void planar_linear_f32(
const float* a, const unsigned char* packed, const unsigned char* scale,
float* c, int m_rows, int in_features, int out_features,
int format, int bs0, int bs1) {
planar_linear_impl<float>(a, packed, scale, c, m_rows, in_features,
out_features, format, bs0, bs1);
}
extern "C" __global__ void planar_linear_f16(
const __half* a, const unsigned char* packed, const unsigned char* scale,
__half* c, int m_rows, int in_features, int out_features,
int format, int bs0, int bs1) {
planar_linear_impl<__half>(a, packed, scale, c, m_rows, in_features,
out_features, format, bs0, bs1);
}
extern "C" __global__ void planar_linear_bf16(
const __nv_bfloat16* a, const unsigned char* packed, const unsigned char* scale,
__nv_bfloat16* c, int m_rows, int in_features, int out_features,
int format, int bs0, int bs1) {
planar_linear_impl<__nv_bfloat16>(a, packed, scale, c, m_rows, in_features,
out_features, format, bs0, bs1);
}
"#;
#[allow(dead_code)]
pub(crate) const PLANAR_LINEAR_ENTRY: &str = "planar_linear_f32";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PlanarActivationDtype {
F32,
F16,
Bf16,
}
impl PlanarActivationDtype {
pub fn entry(self) -> &'static str {
match self {
PlanarActivationDtype::F32 => "planar_linear_f32",
PlanarActivationDtype::F16 => "planar_linear_f16",
PlanarActivationDtype::Bf16 => "planar_linear_bf16",
}
}
pub fn all() -> [PlanarActivationDtype; 3] {
[
PlanarActivationDtype::F32,
PlanarActivationDtype::F16,
PlanarActivationDtype::Bf16,
]
}
fn byte_size(self) -> usize {
match self {
PlanarActivationDtype::F32 => 4,
PlanarActivationDtype::F16 | PlanarActivationDtype::Bf16 => 2,
}
}
pub fn from_data_type(dtype: DataType) -> Result<PlanarActivationDtype> {
match dtype {
DataType::Float32 => Ok(PlanarActivationDtype::F32),
DataType::Float16 => Ok(PlanarActivationDtype::F16),
DataType::BFloat16 => Ok(PlanarActivationDtype::Bf16),
other => Err(EpError::KernelFailed(format!(
"cuda_ep planar linear: unsupported activation dtype {other:?}; \
only f32/f16/bf16 have a proven planar kernel"
))),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PlanarLinearDims {
pub format: i32,
pub m_rows: usize,
pub in_features: usize,
pub out_features: usize,
pub bs0: usize,
pub bs1: usize,
}
fn kernel_err(message: impl Into<String>) -> EpError {
EpError::KernelFailed(format!("cuda_ep planar linear: {}", message.into()))
}
impl PlanarLinearDims {
fn cpu_layout(&self) -> Result<PlanarLayout> {
let (format, block_out, block_in) = match self.format {
PLANAR_FORMAT_BLOCK_FP8 => (PlanarBlockFormat::BlockFp8, self.bs0, self.bs1),
PLANAR_FORMAT_FP4_PLANAR => (PlanarBlockFormat::Fp4Planar, 1, CPU_FP4_MICROSCALE_BLOCK),
other => return Err(kernel_err(format!("unknown planar format id {other}"))),
};
PlanarLayout::new(
format,
self.out_features,
self.in_features,
block_out,
block_in,
)
.map_err(|err| kernel_err(err.to_string()))
}
pub fn expected_lengths(&self) -> Result<PlanarTensorLengths> {
if self.m_rows == 0 || self.in_features == 0 || self.out_features == 0 {
return Err(kernel_err(format!(
"non-positive dims M={} K={} N={}",
self.m_rows, self.in_features, self.out_features
)));
}
for (label, value) in [
("M", self.m_rows),
("K", self.in_features),
("N", self.out_features),
] {
if i32::try_from(value).is_err() {
return Err(kernel_err(format!(
"{label}={value} exceeds the i32 kernel ABI"
)));
}
}
match self.format {
PLANAR_FORMAT_BLOCK_FP8 => {
if self.bs0 == 0 || self.bs1 == 0 {
return Err(kernel_err(format!(
"block_fp8 requires bs0>0 and bs1>0, got bs0={} bs1={}",
self.bs0, self.bs1
)));
}
for (label, value) in [("bs0", self.bs0), ("bs1", self.bs1)] {
if i32::try_from(value).is_err() {
return Err(kernel_err(format!(
"{label}={value} exceeds the i32 kernel ABI"
)));
}
}
let scale_rows = self.out_features.div_ceil(self.bs0);
let scale_cols = self.in_features.div_ceil(self.bs1);
Ok(PlanarTensorLengths {
packed_bytes: self.out_features * self.in_features,
scale_bytes: scale_rows * scale_cols,
output_elems: self.m_rows * self.out_features,
})
}
PLANAR_FORMAT_FP4_PLANAR => {
if !self.in_features.is_multiple_of(FP4_PACK_FACTOR) {
return Err(kernel_err(format!(
"fp4_planar requires an even contraction, got K={}",
self.in_features
)));
}
if !self.in_features.is_multiple_of(FP4_MICROSCALE_BLOCK) {
return Err(kernel_err(format!(
"fp4_planar requires K divisible by the block-{} micro-scale, got K={}",
FP4_MICROSCALE_BLOCK, self.in_features
)));
}
Ok(PlanarTensorLengths {
packed_bytes: self.out_features * (self.in_features / FP4_PACK_FACTOR),
scale_bytes: self.out_features * (self.in_features / FP4_MICROSCALE_BLOCK),
output_elems: self.m_rows * self.out_features,
})
}
other => Err(kernel_err(format!("unknown planar format id {other}"))),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PlanarTensorLengths {
pub packed_bytes: usize,
pub scale_bytes: usize,
pub output_elems: usize,
}
fn validate_planar_linear_host(
dims: &PlanarLinearDims,
activation_elems: usize,
packed: &[u8],
scale: &[u8],
output_elems: usize,
) -> Result<ValidatedPlanarLinear> {
let expected = dims.expected_lengths()?;
let expected_activation = dims.m_rows * dims.in_features;
if activation_elems != expected_activation {
return Err(kernel_err(format!(
"activation has {activation_elems} elements, expected M*K = {expected_activation}"
)));
}
if packed.len() != expected.packed_bytes {
return Err(kernel_err(format!(
"packed weight has {} bytes, expected {}",
packed.len(),
expected.packed_bytes
)));
}
if scale.len() != expected.scale_bytes {
return Err(kernel_err(format!(
"aux scale has {} bytes, expected {}",
scale.len(),
expected.scale_bytes
)));
}
if output_elems != expected.output_elems {
return Err(kernel_err(format!(
"output has {output_elems} elements, expected M*N = {}",
expected.output_elems
)));
}
let bank_identity = validate_planar_values(&dims.cpu_layout()?, packed, scale)
.map_err(|err| kernel_err(err.to_string()))?;
Ok(ValidatedPlanarLinear {
dims: *dims,
bank_identity,
})
}
#[derive(Clone, Copy, Debug)]
struct ValidatedPlanarLinear {
dims: PlanarLinearDims,
bank_identity: PlanarBankIdentity,
}
impl ValidatedPlanarLinear {
fn dims(&self) -> &PlanarLinearDims {
&self.dims
}
}
pub(crate) struct ImmutablePlanarDeviceBuffer {
allocation: CudaSealedAllocation,
}
impl ImmutablePlanarDeviceBuffer {
pub(crate) fn upload(
provider: &Arc<CudaExecutionProvider>,
bytes: &[u8],
label: &str,
) -> Result<Self> {
let allocation = provider
.upload_sealed(bytes, 256)
.map_err(|err| kernel_err(format!("allocate/upload immutable {label}: {err}")))?;
Ok(Self { allocation })
}
pub(crate) fn ptr(&self, access: &super::SealedLaunchAccess) -> CUdeviceptr {
self.allocation.launch_ptr(access)
}
}
struct PlanarLinearBanks {
packed: ImmutablePlanarDeviceBuffer,
scale: ImmutablePlanarDeviceBuffer,
}
pub struct AdmittedPlanarLinear {
banks: Arc<PlanarLinearBanks>,
provider: Arc<CudaExecutionProvider>,
device: onnx_runtime_ir::DeviceId,
provider_context: ProviderContextIdentity,
validation: ValidatedPlanarLinear,
}
impl AdmittedPlanarLinear {
pub fn dims(&self) -> &PlanarLinearDims {
self.validation.dims()
}
pub fn diagnostic_bank_identity(&self) -> PlanarBankIdentity {
self.validation.bank_identity
}
pub fn device_graph_resource(&self) -> DeviceGraphResource {
DeviceGraphResource::new(Arc::as_ptr(&self.banks) as usize, Arc::clone(&self.banks))
}
}
pub fn admit_planar_linear(
provider: &Arc<CudaExecutionProvider>,
dims: &PlanarLinearDims,
activation_elems: usize,
packed: &[u8],
scale: &[u8],
output_elems: usize,
) -> Result<AdmittedPlanarLinear> {
if provider.runtime().is_capturing()? {
return Err(kernel_err(
"cannot admit a planar bank during CUDA graph capture",
));
}
let validation =
validate_planar_linear_host(dims, activation_elems, packed, scale, output_elems)?;
let packed = ImmutablePlanarDeviceBuffer::upload(provider, packed, "packed weights")?;
let scale = ImmutablePlanarDeviceBuffer::upload(provider, scale, "aux scales")?;
Ok(AdmittedPlanarLinear {
banks: Arc::new(PlanarLinearBanks { packed, scale }),
provider: Arc::clone(provider),
device: provider.device_id(),
provider_context: provider.provider_context_identity(),
validation,
})
}
pub fn warm_planar_linear(runtime: &CudaRuntime) -> Result<()> {
runtime.require_nvrtc_half_headers("planar linear")?;
for dtype in PlanarActivationDtype::all() {
runtime.nvrtc_function(PLANAR_LINEAR_MODULE, PLANAR_BLOCK_DECODE_CUH, dtype.entry())?;
}
Ok(())
}
#[derive(Clone, Copy)]
struct PlanarLinearRawPtrs {
activation: CUdeviceptr,
packed: CUdeviceptr,
scale: CUdeviceptr,
output: CUdeviceptr,
}
const PLANAR_LINEAR_BLOCK: u32 = 256;
pub fn launch_planar_linear(
admission: &AdmittedPlanarLinear,
dtype: PlanarActivationDtype,
activation: &DeviceBuffer,
output: &mut DeviceBuffer,
) -> Result<()> {
let dims = admission.dims();
let activation_bytes = dims
.m_rows
.checked_mul(dims.in_features)
.and_then(|elems| elems.checked_mul(dtype.byte_size()))
.ok_or_else(|| kernel_err("activation byte count overflow"))?;
let output_bytes = dims
.m_rows
.checked_mul(dims.out_features)
.and_then(|elems| elems.checked_mul(dtype.byte_size()))
.ok_or_else(|| kernel_err("output byte count overflow"))?;
for (label, buffer, expected) in [
("activation", activation, activation_bytes),
("output", &*output, output_bytes),
] {
if buffer.device() != admission.device {
return Err(kernel_err(format!(
"{label} device {:?} does not match admitted bank device {:?}",
buffer.device(),
admission.device
)));
}
if buffer.len() != expected {
return Err(kernel_err(format!(
"{label} has {} bytes, expected {expected}",
buffer.len()
)));
}
let context = buffer
.bound_owner()
.ok_or_else(|| {
kernel_err(format!(
"{label} has no binding-issued provider-context identity"
))
})?
.identity()
.binding()
.provider_context();
if context != admission.provider_context {
return Err(kernel_err(format!(
"{label} provider context {context:?} does not match admitted bank context {:?}",
admission.provider_context
)));
}
}
let runtime = admission.provider.runtime();
runtime.require_registered_address_capture(
Arc::as_ptr(&admission.banks) as usize,
"planar linear bank",
)?;
let access = super::SealedLaunchAccess::new();
let ptrs = PlanarLinearRawPtrs {
activation: cuptr(activation.as_ptr()),
packed: admission.banks.packed.ptr(&access),
scale: admission.banks.scale.ptr(&access),
output: cuptr(output.as_mut_ptr()),
};
unsafe { launch_planar_linear_raw(runtime, dtype, dims, &ptrs) }
}
unsafe fn launch_planar_linear_raw(
runtime: &CudaRuntime,
dtype: PlanarActivationDtype,
dims: &PlanarLinearDims,
ptrs: &PlanarLinearRawPtrs,
) -> Result<()> {
let function =
runtime.nvrtc_function(PLANAR_LINEAR_MODULE, PLANAR_BLOCK_DECODE_CUH, dtype.entry())?;
let total = (dims.m_rows as u64) * (dims.out_features as u64);
let grid_x = total.div_ceil(u64::from(PLANAR_LINEAR_BLOCK));
let grid_x = u32::try_from(grid_x)
.map_err(|_| kernel_err(format!("grid dimension {grid_x} exceeds u32")))?;
let m_rows = dims.m_rows as i32;
let in_features = dims.in_features as i32;
let out_features = dims.out_features as i32;
let format = dims.format;
let (bs0, bs1) = if format == PLANAR_FORMAT_BLOCK_FP8 {
(
i32::try_from(dims.bs0)
.map_err(|_| kernel_err(format!("bs0={} exceeds the i32 kernel ABI", dims.bs0)))?,
i32::try_from(dims.bs1)
.map_err(|_| kernel_err(format!("bs1={} exceeds the i32 kernel ABI", dims.bs1)))?,
)
} else {
(0, 0)
};
let stream = runtime.stream();
let mut builder = stream.launch_builder(&function);
builder
.arg(&ptrs.activation)
.arg(&ptrs.packed)
.arg(&ptrs.scale)
.arg(&ptrs.output)
.arg(&m_rows)
.arg(&in_features)
.arg(&out_features)
.arg(&format)
.arg(&bs0)
.arg(&bs1);
unsafe {
builder.launch(LaunchConfig {
grid_dim: (grid_x, 1, 1),
block_dim: (PLANAR_LINEAR_BLOCK, 1, 1),
shared_mem_bytes: 0,
})
}
.map_err(|err| driver_err(&format!("launch {}", dtype.entry()), err))?;
Ok(())
}
pub fn planar_matmul_capable_formats() -> &'static [&'static str] {
&["block_fp8", "fp4_planar"]
}
#[allow(dead_code)]
const PLANAR_E2M1_LUT: [f32; 16] = [
0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, -0.0, -0.5, -1.0, -1.5, -2.0, -3.0, -4.0, -6.0,
];
#[allow(dead_code)]
pub(crate) fn mirror_e8m0_scale(exponent: u8) -> f32 {
match exponent {
0xff => f32::NAN,
0 => f32::from_bits(0x0040_0000),
_ => f32::from_bits((exponent as u32) << 23),
}
}
#[allow(dead_code)]
pub(crate) fn mirror_e2m1(code: u8) -> f32 {
PLANAR_E2M1_LUT[usize::from(code & 15)]
}
#[allow(dead_code)]
pub(crate) fn mirror_e4m3(code: u8) -> f32 {
let sign = if code & 0x80 != 0 { -1.0 } else { 1.0 };
let e = u32::from((code >> 3) & 15);
let m = u32::from(code & 7);
if e == 15 && m == 7 {
return f32::NAN;
}
let magnitude = if e == 0 {
m as f32 * 2.0f32.powi(-9)
} else {
(1.0 + m as f32 / 8.0) * 2.0f32.powi(e as i32 - 7)
};
sign * magnitude
}
#[allow(dead_code)]
pub(crate) fn mirror_bf8_element(
packed: &[u8],
scale: &[u8],
in_features: usize,
bs0: usize,
bs1: usize,
out_row: usize,
in_col: usize,
) -> f32 {
let scale_cols = in_features.div_ceil(bs1);
let se = scale[(out_row / bs0) * scale_cols + (in_col / bs1)];
let code = packed[out_row * in_features + in_col];
mirror_e4m3(code) * mirror_e8m0_scale(se)
}
#[allow(dead_code)]
pub(crate) fn mirror_fp4_element(
packed: &[u8],
scale: &[u8],
in_features: usize,
out_row: usize,
in_col: usize,
) -> f32 {
let scale_cols = in_features / FP4_MICROSCALE_BLOCK;
let se = scale[out_row * scale_cols + (in_col / FP4_MICROSCALE_BLOCK)];
let byte = packed[out_row * (in_features / 2) + (in_col / 2)];
let nibble = if in_col & 1 == 1 {
byte >> 4
} else {
byte & 0x0f
};
mirror_e2m1(nibble) * mirror_e8m0_scale(se)
}
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
pub(crate) fn mirror_planar_linear_f32(
a: &[f32],
packed: &[u8],
scale: &[u8],
m_rows: usize,
in_features: usize,
out_features: usize,
format: i32,
bs0: usize,
bs1: usize,
) -> Vec<f32> {
let mut c = vec![0.0f32; m_rows * out_features];
for row in 0..m_rows {
let a_row = &a[row * in_features..][..in_features];
for col in 0..out_features {
let mut acc = 0.0f32;
for (k, &a_val) in a_row.iter().enumerate() {
let w = if format == PLANAR_FORMAT_BLOCK_FP8 {
mirror_bf8_element(packed, scale, in_features, bs0, bs1, col, k)
} else {
mirror_fp4_element(packed, scale, in_features, col, k)
};
acc += a_val * w;
}
c[row * out_features + col] = acc;
}
}
c
}
#[cfg(test)]
mod tests;