1use std::ffi::c_void;
21use std::sync::OnceLock;
22
23use super::n_tile::record_published;
24use crate::registry::{RawCudaFunc, cuda_error_text};
25use anyhow::{Result, bail};
26use cudarc::driver::LaunchConfig;
27
28use super::{
29 MetraleCudaBackend, cuMemAlloc_v2, cuMemAllocManaged, cuMemFree_v2, cuMemGetInfo_v2,
30 cuMemcpyDtoDAsync_v2, cuMemcpyDtoHAsync_v2, cuStreamSynchronize,
31};
32use crate::gpu::{DevicePtr, GpuBackend, GraphHandle, KernelHandle};
33
34mod d2h_trace;
35mod h2d;
36mod raw;
37
38use d2h_trace::d2h_trace_tick;
39use h2d::{h2d_enqueue, warn_pinned_transient_source};
40
41impl GpuBackend for MetraleCudaBackend {
42 #[track_caller]
43 fn alloc(&self, bytes: usize) -> Result<DevicePtr> {
44 let site = std::panic::Location::caller();
45 let mut dptr: u64 = 0;
46 let seq = super::ALLOC_SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
53 let pad = if seq >= super::redzone_min_idx() {
54 super::redzone_bytes()
55 } else {
56 0
57 };
58 let status = unsafe { cuMemAlloc_v2(&mut dptr, bytes + pad) };
59 if status != 0 {
60 let mut free: usize = 0;
61 let mut total: usize = 0;
62 unsafe { cuMemGetInfo_v2(&mut free, &mut total) };
63 bail!(
64 "cuMemAlloc_v2 failed: status {status}, requested {bytes} bytes \
65 (device reports {:.1} MB free / {:.1} GB total)",
66 free as f64 / (1024.0 * 1024.0),
67 total as f64 / (1024.0 * 1024.0 * 1024.0),
68 );
69 }
70 if pad > 0 {
71 self.alloc_poison_redzone(dptr, bytes, pad, seq)?;
72 }
73 self.record_alloc(DevicePtr(dptr), bytes, site);
74 if bytes >= 32 * 1024 * 1024 {
75 tracing::debug!(
76 "alloc {:.1} MB (device ptr {dptr:#x})",
77 bytes as f64 / (1024.0 * 1024.0)
78 );
79 }
80 Ok(DevicePtr(dptr))
81 }
82
83 fn scan_redzones(&self) -> Result<usize> {
84 if super::redzone_bytes() == 0 {
85 return Ok(0);
86 }
87 MetraleCudaBackend::scan_redzones(self)
88 }
89
90 fn poison_redzones(&self, lo: usize, hi: usize) -> Result<()> {
91 if super::redzone_bytes() == 0 {
92 return Ok(());
93 }
94 MetraleCudaBackend::poison_redzones(self, lo, hi)
95 }
96
97 #[track_caller]
98 fn alloc_managed(&self, bytes: usize) -> Result<DevicePtr> {
99 let site = std::panic::Location::caller();
100 let mut dptr: u64 = 0;
101 const CU_MEM_ATTACH_GLOBAL: u32 = 0x1;
102 let status = unsafe { cuMemAllocManaged(&mut dptr, bytes, CU_MEM_ATTACH_GLOBAL) };
103 if status != 0 {
104 bail!(
105 "cuMemAllocManaged failed: status {status}, requested {bytes} bytes. \
106 Check system swap space: swapon --show"
107 );
108 }
109 self.record_alloc(DevicePtr(dptr), bytes, site);
110 Ok(DevicePtr(dptr))
111 }
112
113 fn free(&self, ptr: DevicePtr) -> Result<()> {
114 if ptr.is_null() {
115 return Ok(());
116 }
117 self.forget_alloc(ptr);
120 if super::redzone_bytes() > 0 {
121 self.forget_redzone(ptr.0);
122 }
123 let status = unsafe { cuMemFree_v2(ptr.0) };
124 if status != 0 && !crate::registry::is_teardown_noop(status) {
127 bail!("cuMemFree_v2 failed: status {status}, ptr {ptr}");
128 }
129 Ok(())
130 }
131
132 fn live_bytes(&self) -> Option<usize> {
133 Some(MetraleCudaBackend::live_bytes(self))
134 }
135
136 fn alloc_report(&self, top_n: usize, min_mb: usize) -> Option<String> {
137 Some(MetraleCudaBackend::alloc_report(self, top_n, min_mb))
138 }
139
140 fn chunk_slack_bytes(&self) -> Option<usize> {
141 Some(MetraleCudaBackend::chunk_slack_bytes(self))
142 }
143
144 fn live_allocations(&self) -> Option<Vec<(DevicePtr, usize, String)>> {
145 Some(MetraleCudaBackend::live_allocations(self))
146 }
147
148 fn sweep_unreleased(&self) -> usize {
149 MetraleCudaBackend::sweep_unreleased(self)
150 }
151
152 fn copy_h2d(&self, src: &[u8], dst: DevicePtr) -> Result<()> {
153 MetraleCudaBackend::copy_h2d_impl(self, src, dst)
154 }
155
156 fn copy_d2h(&self, src: DevicePtr, dst: &mut [u8]) -> Result<()> {
157 d2h_trace_tick();
158 MetraleCudaBackend::copy_d2h_impl(self, src, dst)
159 }
160
161 fn copy_d2h_on_stream(&self, src: DevicePtr, dst: &mut [u8], stream: u64) -> Result<()> {
162 d2h_trace_tick();
163 MetraleCudaBackend::copy_d2h_on_stream_impl(self, src, dst, stream)
164 }
165
166 fn copy_d2h_async(&self, src: DevicePtr, dst: &mut [u8], stream: u64) -> Result<()> {
167 d2h_trace_tick();
171 let status = unsafe {
172 cuMemcpyDtoHAsync_v2(dst.as_mut_ptr() as *mut c_void, src.0, dst.len(), stream)
173 };
174 if status != 0 {
175 bail!("cuMemcpyDtoHAsync_v2 (async) failed: status {status}");
176 }
177 Ok(())
178 }
179
180 fn copy_d2d(&self, src: DevicePtr, dst: DevicePtr, bytes: usize) -> Result<()> {
181 if metrale_telemetry::launch_trace::on() {
182 metrale_telemetry::launch_trace::record(metrale_telemetry::launch_trace::Entry {
183 kind: "d2d",
184 func: 0,
185 grid: [0, 0, 0],
186 block: [0, 0, 0],
187 smem: 0,
188 args: vec![src.0, dst.0, bytes as u64],
189 });
190 }
191 MetraleCudaBackend::copy_d2d_impl(self, src, dst, bytes)
192 }
193
194 fn launch(
195 &self,
196 func: KernelHandle,
197 grid: [u32; 3],
198 block: [u32; 3],
199 shared_mem: u32,
200 stream: u64,
201 params: &mut [*mut c_void],
202 ) -> Result<()> {
203 let raw_func = RawCudaFunc(func.0 as *mut c_void);
204 let cfg = LaunchConfig {
205 grid_dim: (grid[0], grid[1], grid[2]),
206 block_dim: (block[0], block[1], block[2]),
207 shared_mem_bytes: shared_mem,
208 };
209 let registry = self.registry();
210 let span = crate::timing::kernel_begin(self, func.0, stream);
211 let launched =
212 unsafe { registry.launch_on_stream(raw_func, cfg, stream, params) }.map_err(|e| {
213 super::fault_probe::note_failure("kernel launch", &e.to_string());
216 anyhow::anyhow!("Kernel launch failed: {e}")
217 });
218 crate::timing::kernel_end(span, stream);
219 launched
220 }
221
222 fn stream_is_capturing(&self, stream: u64) -> bool {
223 #[cfg(metrale_scale)]
226 {
227 let _ = stream;
228 false
229 }
230 #[cfg(not(metrale_scale))]
231 {
232 let mut status: u32 = 0;
233 let rc = unsafe { super::cuStreamIsCapturing(stream, &mut status) };
236 rc != 0 || status != 0
237 }
238 }
239
240 fn synchronize(&self, stream: u64) -> Result<()> {
241 metrale_telemetry::global().stream_sync();
242 let status = unsafe { cuStreamSynchronize(stream) };
243 if status != 0 {
244 bail!("cuStreamSynchronize failed: {}", cuda_error_text(status));
245 }
246 Ok(())
247 }
248
249 fn default_stream(&self) -> u64 {
250 self.default_stream
251 }
252
253 fn op_cache(&self) -> &crate::op_cache::OpCache {
254 &self.op_cache
255 }
256
257 fn debug_sync_kernels(&self) -> bool {
258 MetraleCudaBackend::debug_sync_kernels(self)
259 }
260
261 fn kernel_registry(&self) -> Option<std::sync::Arc<crate::registry::MetraleRegistry>> {
262 Some(self.registry().clone())
263 }
264
265 #[track_caller]
266 fn kernel(&self, module: &str, func_name: &str) -> Result<KernelHandle> {
267 let site = std::panic::Location::caller();
270 let cache: OnceLock<RawCudaFunc> = OnceLock::new();
273 let registry = self.registry();
274 match registry.raw_function_cached(&cache, module, func_name) {
275 Ok(raw) => {
276 metrale_telemetry::kernel_audit::record(module, func_name, true, site);
277 metrale_telemetry::launch_trace::name_kernel(raw.0 as u64, module, func_name);
278 let handle = KernelHandle(raw.0 as u64);
279 record_published(registry, &self.op_cache, module, func_name, handle)?;
280 Ok(handle)
281 }
282 Err(e) => {
283 metrale_telemetry::kernel_audit::record(module, func_name, false, site);
286 Err(anyhow::anyhow!("Kernel lookup {module}::{func_name}: {e}"))
287 }
288 }
289 }
290
291 fn has_module(&self, module: &str) -> bool {
292 self.registry().has_module(module)
293 }
294
295 fn copy_h2d_async(&self, src: &[u8], dst: DevicePtr, stream: u64) -> Result<()> {
296 h2d_enqueue(src, dst, stream)?;
297 if crate::pinned_hosts::is_pinned(src) {
301 warn_pinned_transient_source();
302 let sync = unsafe { cuStreamSynchronize(stream) };
303 if sync != 0 {
304 bail!(
305 "cuStreamSynchronize after pinned-source H2D failed: {}",
306 cuda_error_text(sync)
307 );
308 }
309 }
310 Ok(())
311 }
312
313 fn copy_h2d_async_retained(&self, src: &[u8], dst: DevicePtr, stream: u64) -> Result<()> {
314 h2d_enqueue(src, dst, stream)
317 }
318
319 fn copy_d2d_async(
320 &self,
321 src: DevicePtr,
322 dst: DevicePtr,
323 bytes: usize,
324 stream: u64,
325 ) -> Result<()> {
326 let status = unsafe { cuMemcpyDtoDAsync_v2(dst.0, src.0, bytes, stream) };
327 if status != 0 {
328 tracing::error!(
330 "copy_d2d_async failed (status {status}) at:\n{}",
331 std::backtrace::Backtrace::force_capture()
332 );
333 bail!("cuMemcpyDtoDAsync_v2 (copy_d2d_async) failed: status {status}");
334 }
335 Ok(())
336 }
337
338 fn copy_d2d_2d_async(
339 &self,
340 src: DevicePtr,
341 src_pitch: usize,
342 dst: DevicePtr,
343 dst_pitch: usize,
344 width_bytes: usize,
345 height: usize,
346 stream: u64,
347 ) -> Result<()> {
348 raw::memcpy_2d_async(src, src_pitch, dst, dst_pitch, width_bytes, height, stream)
349 }
350
351 fn begin_capture(&self, stream: u64) -> Result<()> {
352 self.begin_capture_cu(stream)
353 }
354 fn end_capture(&self, stream: u64) -> Result<GraphHandle> {
355 self.end_capture_cu(stream)
356 }
357
358 fn abort_capture_if_active(&self, stream: u64) {
359 self.abort_capture_if_active_cu(stream)
360 }
361
362 fn launch_graph(&self, graph: GraphHandle, stream: u64) -> Result<()> {
363 self.launch_graph_cu(graph, stream)
364 }
365 fn destroy_graph(&self, graph: GraphHandle) -> Result<()> {
366 self.destroy_graph_cu(graph)
367 }
368 fn memset(&self, ptr: DevicePtr, value: u8, bytes: usize) -> Result<()> {
369 self.memset_cu(ptr, value, bytes)
370 }
371 fn memset_async(&self, ptr: DevicePtr, value: u8, bytes: usize, stream: u64) -> Result<()> {
372 if metrale_telemetry::launch_trace::on() {
373 metrale_telemetry::launch_trace::record(metrale_telemetry::launch_trace::Entry {
374 kind: "memset",
375 func: 0,
376 grid: [0, 0, 0],
377 block: [0, 0, 0],
378 smem: 0,
379 args: vec![ptr.0, value as u64, bytes as u64],
380 });
381 }
382 self.memset_async_cu(ptr, value, bytes, stream)
383 }
384 fn total_memory(&self) -> Result<usize> {
385 self.total_memory_cu()
386 }
387 fn free_memory(&self) -> Result<usize> {
388 self.free_memory_cu()
389 }
390 fn device_free_memory(&self) -> Result<usize> {
391 self.device_free_memory_cu()
392 }
393 fn live_alloc_count(&self) -> usize {
394 self.live_alloc_len()
395 }
396 fn sm_count(&self) -> Result<u32> {
397 self.sm_count_cu()
398 }
399 fn create_stream(&self) -> Result<u64> {
400 self.create_stream_cu()
401 }
402 fn bind_to_thread(&self) -> Result<()> {
403 self.bind_to_thread_cu()
404 }
405 fn create_event(&self) -> Result<u64> {
406 self.create_event_cu()
407 }
408 fn record_event(&self, event: u64, stream: u64) -> Result<()> {
409 self.record_event_cu(event, stream)
410 }
411 fn stream_wait_event(&self, stream: u64, event: u64) -> Result<()> {
412 self.stream_wait_event_cu(stream, event)
413 }
414 fn event_synchronize(&self, event: u64) -> Result<()> {
415 self.event_synchronize_cu(event)
416 }
417 fn event_query(&self, event: u64) -> Result<bool> {
418 self.event_query_cu(event)
419 }
420 fn destroy_event(&self, event: u64) -> Result<()> {
421 self.destroy_event_cu(event)
422 }
423 fn host_ptr_to_device(&self, host: *mut u8) -> Result<DevicePtr> {
424 let mut dptr: u64 = 0;
425 let status =
426 unsafe { super::cuMemHostGetDevicePointer_v2(&mut dptr, host as *mut c_void, 0) };
427 if status != 0 {
428 bail!("cuMemHostGetDevicePointer_v2 failed: status {status}");
429 }
430 Ok(DevicePtr(dptr))
431 }
432
433 fn alloc_host_pinned(&self, bytes: usize) -> Result<*mut u8> {
434 if bytes >= 32 * 1024 * 1024 {
435 tracing::debug!(
436 "alloc_host_pinned {:.1} MB",
437 bytes as f64 / (1024.0 * 1024.0)
438 );
439 }
440 self.alloc_host_pinned_cu(bytes)
441 }
442 fn free_host_pinned(&self, ptr: *mut u8, _bytes: usize) -> Result<()> {
443 self.free_host_pinned_cu(ptr, _bytes)
444 }
445
446 fn alloc_arena(&self, bytes: usize) -> Result<DevicePtr> {
447 raw::alloc_arena(bytes)
448 }
449 fn free_arena(&self, ptr: DevicePtr) -> Result<()> {
450 raw::free_arena(ptr)
451 }
452}