use super::sys::{
CUdevice, CUfunction, CudaDriver, CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK,
CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK, CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK,
CU_DEVICE_ATTRIBUTE_WARP_SIZE, CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES,
CU_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK, CU_FUNC_ATTRIBUTE_NUM_REGS,
CU_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES,
};
use crate::error::GpuError;
use crate::launch_budget::{DeviceLimits, KernelAttributes};
use std::os::raw::c_int;
fn driver() -> Result<&'static CudaDriver, GpuError> {
CudaDriver::load().ok_or_else(|| {
GpuError::CudaDriver("libcuda not loadable for budget query".to_string(), -1)
})
}
fn func_attr(d: &CudaDriver, attrib: c_int, func: CUfunction) -> Result<u32, GpuError> {
let mut v: c_int = 0;
CudaDriver::check(unsafe { (d.cuFuncGetAttribute)(&mut v, attrib, func) })?;
u32::try_from(v)
.map_err(|_| GpuError::CudaDriver(format!("negative function attribute {attrib}: {v}"), -1))
}
fn dev_attr(d: &CudaDriver, attrib: c_int, device: CUdevice) -> Result<u32, GpuError> {
let mut v: c_int = 0;
CudaDriver::check(unsafe { (d.cuDeviceGetAttribute)(&mut v, attrib, device) })?;
u32::try_from(v)
.map_err(|_| GpuError::CudaDriver(format!("negative device attribute {attrib}: {v}"), -1))
}
pub unsafe fn kernel_attributes(func: CUfunction) -> Result<KernelAttributes, GpuError> {
let d = driver()?;
Ok(KernelAttributes {
max_threads_per_block: func_attr(d, CU_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK, func)?,
num_regs: func_attr(d, CU_FUNC_ATTRIBUTE_NUM_REGS, func)?,
static_shared_bytes: func_attr(d, CU_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES, func)?,
local_bytes: func_attr(d, CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, func)?,
})
}
pub fn device_limits(device: CUdevice) -> Result<DeviceLimits, GpuError> {
let d = driver()?;
Ok(DeviceLimits {
max_threads_per_block: dev_attr(d, CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK, device)?,
max_shared_per_block: dev_attr(d, CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK, device)?,
max_regs_per_block: dev_attr(d, CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK, device)?,
warp_size: dev_attr(d, CU_DEVICE_ATTRIBUTE_WARP_SIZE, device)?,
})
}
pub unsafe fn occupancy_max_active_blocks_per_sm(
func: CUfunction,
block_size: u32,
dynamic_shared_bytes: u32,
) -> Result<u32, GpuError> {
let d = driver()?;
let mut blocks: c_int = 0;
let bs = c_int::try_from(block_size).map_err(|_| {
GpuError::InvalidParameter(format!("block_size {block_size} exceeds c_int"))
})?;
CudaDriver::check(unsafe {
(d.cuOccupancyMaxActiveBlocksPerMultiprocessor)(
&mut blocks,
func,
bs,
dynamic_shared_bytes as usize,
)
})?;
u32::try_from(blocks)
.map_err(|_| GpuError::CudaDriver(format!("negative occupancy: {blocks}"), -1))
}
pub unsafe fn enforce_register_budget(
func: CUfunction,
device: CUdevice,
block_size: u32,
dynamic_shared_bytes: u32,
) -> Result<(), GpuError> {
let attrs = unsafe { kernel_attributes(func) }?;
let limits = device_limits(device)?;
crate::launch_budget::validate_launch(&attrs, &limits, block_size, dynamic_shared_bytes)
.map_err(|v| GpuError::InvalidParameter(v.to_string()))?;
let blocks =
unsafe { occupancy_max_active_blocks_per_sm(func, block_size, dynamic_shared_bytes) }?;
if blocks == 0 {
return Err(GpuError::InvalidParameter(format!(
"cuOccupancyMaxActiveBlocksPerMultiprocessor == 0 for block_size={block_size}, \
dynamic_shared={dynamic_shared_bytes}: the launch cannot be resident on any SM \
(contract ptx-codegen-safety-v1 register_budget postcondition)"
)));
}
Ok(())
}