metrale_gpu_runtime/cuda_backend/
tensormap.rs1use anyhow::{Result, bail};
21
22use crate::gpu::DevicePtr;
23
24const CU_TENSOR_MAP_DATA_TYPE_BFLOAT16: u32 = 9;
28const CU_TENSOR_MAP_INTERLEAVE_NONE: u32 = 0;
29const CU_TENSOR_MAP_SWIZZLE_NONE: u32 = 0;
30const CU_TENSOR_MAP_L2_PROMOTION_L2_128B: u32 = 2;
31const CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE: u32 = 0;
32
33type EncodeTiledFn = unsafe extern "C" fn(
37 *mut u8,
38 u32,
39 u32,
40 u64,
41 *const u64,
42 *const u64,
43 *const u32,
44 *const u32,
45 u32,
46 u32,
47 u32,
48 u32,
49) -> i32;
50
51fn tensor_map_encode_tiled() -> Option<EncodeTiledFn> {
52 static CACHED: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
53 let addr = (*CACHED.get_or_init(|| {
54 #[cfg(unix)]
57 {
58 unsafe extern "C" {
59 fn dlsym(handle: *mut std::ffi::c_void, symbol: *const i8)
60 -> *mut std::ffi::c_void;
61 }
62 let name = c"cuTensorMapEncodeTiled";
63 let p = unsafe { dlsym(std::ptr::null_mut(), name.as_ptr().cast::<i8>()) };
64 if p.is_null() { None } else { Some(p as usize) }
65 }
66 #[cfg(not(unix))]
67 {
68 None
69 }
70 }))?;
71 Some(unsafe { std::mem::transmute::<usize, EncodeTiledFn>(addr) })
74}
75
76#[repr(C, align(64))]
78#[derive(Clone, Copy)]
79pub struct TensorMap([u8; 128]);
80
81impl std::fmt::Debug for TensorMap {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.write_str("TensorMap(<128 bytes>)")
84 }
85}
86
87impl TensorMap {
88 pub fn bytes(&self) -> &[u8; 128] {
90 &self.0
91 }
92
93 pub fn tiled_2d_bf16(
104 global: DevicePtr,
105 rows: u64,
106 cols: u64,
107 row_stride_elems: u64,
108 box_rows: u32,
109 box_cols: u32,
110 ) -> Result<Self> {
111 if !global.0.is_multiple_of(16) {
112 bail!("TMA global address {:#x} is not 16-byte aligned", global.0);
113 }
114 let row_stride_bytes = row_stride_elems * 2;
115 if !row_stride_bytes.is_multiple_of(16) {
116 bail!(
117 "TMA row stride {row_stride_elems} elems ({row_stride_bytes} B) is not \
118 16-byte aligned; bf16 needs a stride that is a multiple of 8 elements"
119 );
120 }
121 if row_stride_elems < cols {
122 bail!("TMA row stride {row_stride_elems} is narrower than cols {cols}");
123 }
124 if box_rows == 0 || box_cols == 0 {
125 bail!("TMA box dims must be non-zero, got {box_rows}x{box_cols}");
126 }
127
128 let global_dim: [u64; 2] = [cols, rows];
131 let global_strides: [u64; 1] = [row_stride_bytes];
133 let box_dim: [u32; 2] = [box_cols, box_rows];
134 let element_strides: [u32; 2] = [1, 1];
135
136 let f = tensor_map_encode_tiled().ok_or_else(|| {
139 anyhow::anyhow!(
140 "cuTensorMapEncodeTiled not present in libcuda — TMA needs a CUDA 12.0+ driver"
141 )
142 })?;
143 let mut map = TensorMap([0u8; 128]);
144 let rc = unsafe {
145 f(
146 map.0.as_mut_ptr(),
147 CU_TENSOR_MAP_DATA_TYPE_BFLOAT16,
148 2,
149 global.0,
150 global_dim.as_ptr(),
151 global_strides.as_ptr(),
152 box_dim.as_ptr(),
153 element_strides.as_ptr(),
154 CU_TENSOR_MAP_INTERLEAVE_NONE,
155 CU_TENSOR_MAP_SWIZZLE_NONE,
156 CU_TENSOR_MAP_L2_PROMOTION_L2_128B,
157 CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE,
158 )
159 };
160 if rc != 0 {
161 bail!(
162 "cuTensorMapEncodeTiled failed: CUresult {rc} \
163 (rows={rows} cols={cols} stride={row_stride_elems} box={box_rows}x{box_cols})"
164 );
165 }
166 Ok(map)
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
177 fn misaligned_stride_is_rejected_without_touching_the_driver() {
178 let e = TensorMap::tiled_2d_bf16(DevicePtr(0x1000), 4, 5, 5, 2, 5).unwrap_err();
180 assert!(
181 e.to_string().contains("not 16-byte aligned"),
182 "expected a stride-alignment error, got: {e}"
183 );
184 }
185
186 #[test]
187 fn misaligned_address_is_rejected() {
188 let e = TensorMap::tiled_2d_bf16(DevicePtr(0x1004), 4, 8, 8, 2, 8).unwrap_err();
189 assert!(e.to_string().contains("not 16-byte aligned"), "got: {e}");
190 }
191
192 #[test]
193 fn a_stride_narrower_than_the_row_is_rejected() {
194 let e = TensorMap::tiled_2d_bf16(DevicePtr(0x1000), 4, 64, 32, 2, 64).unwrap_err();
195 assert!(e.to_string().contains("narrower than cols"), "got: {e}");
196 }
197
198 #[test]
199 fn the_descriptor_has_the_layout_the_driver_requires() {
200 assert_eq!(std::mem::size_of::<TensorMap>(), 128);
201 assert_eq!(std::mem::align_of::<TensorMap>(), 64);
202 }
203}