1use std::ffi::c_void;
14
15use anyhow::{Result, bail};
16use std::sync::Arc;
17
18use crate::registry::MetraleRegistry;
19
20pub mod arch_preflight;
21mod fault_probe;
22mod gpu_copy;
23mod gpu_impl;
24mod gpu_impl_graph;
25mod n_tile;
26mod redzone;
27pub mod tensormap;
28pub(crate) use redzone::{
29 ALLOC_SEQ, RedZone, redzone_bytes, redzone_fill, redzone_min_idx, redzone_trace_idx,
30};
31mod memory;
32pub use memory::{cuda_free_memory_bytes, spawn_oom_watchdog};
33pub(crate) use memory::{device_is_integrated, effective_free_bytes};
34
35unsafe extern "C" {
36 pub(super) fn cuMemAlloc_v2(dptr: *mut u64, bytesize: usize) -> i32;
37 pub(super) fn cuMemFree_v2(dptr: u64) -> i32;
38 pub(super) fn cuMemcpyHtoDAsync_v2(
39 dst: u64,
40 src: *const c_void,
41 bytes: usize,
42 stream: u64,
43 ) -> i32;
44 pub(super) fn cuMemcpyDtoHAsync_v2(
45 dst: *mut c_void,
46 src: u64,
47 bytes: usize,
48 stream: u64,
49 ) -> i32;
50 pub(super) fn cuMemcpyDtoDAsync_v2(dst: u64, src: u64, bytes: usize, stream: u64) -> i32;
51 pub(super) fn cuStreamSynchronize(stream: u64) -> i32;
52 pub(super) fn cuStreamQuery(stream: u64) -> i32;
53 pub(super) fn cuMemHostGetDevicePointer_v2(
54 dptr: *mut u64,
55 host: *mut std::ffi::c_void,
56 flags: u32,
57 ) -> i32;
58 pub(super) fn cuMemGetInfo_v2(free: *mut usize, total: *mut usize) -> i32;
59 pub(super) fn cuCtxGetDevice(device: *mut i32) -> i32;
61 pub(super) fn cuDeviceGet(device: *mut i32, ordinal: i32) -> i32;
65 pub(super) fn cuDeviceGetAttribute(pi: *mut i32, attrib: u32, dev: i32) -> i32;
66 pub(super) fn cuMemsetD8Async(dst: u64, value: u8, n: usize, stream: u64) -> i32;
67 pub(super) fn cuMemsetD8_v2(dst: u64, value: u8, n: usize) -> i32;
70 pub(super) fn cuMemcpyDtoH_v2(dst: *mut c_void, src: u64, bytes: usize) -> i32;
71 pub(super) fn cuStreamBeginCapture(hStream: u64, mode: u32) -> i32;
72 #[cfg(not(metrale_scale))]
75 pub(super) fn cuStreamIsCapturing(hStream: u64, captureStatus: *mut u32) -> i32;
76 pub(super) fn cuStreamEndCapture(hStream: u64, phGraph: *mut u64) -> i32;
77 #[cfg(not(metrale_scale))]
82 pub(super) fn cuGraphInstantiateWithFlags(
83 phGraphExec: *mut u64,
84 hGraph: u64,
85 flags: u64,
86 ) -> i32;
87 #[cfg(metrale_scale)]
88 pub(super) fn cuGraphInstantiate(phGraphExec: *mut u64, hGraph: u64, flags: u64) -> i32;
89 pub(super) fn cuGraphLaunch(hGraphExec: u64, hStream: u64) -> i32;
90 pub(super) fn cuGraphExecDestroy(hGraphExec: u64) -> i32;
91 pub(super) fn cuGraphDestroy(hGraph: u64) -> i32;
92 fn cuCtxGetCurrent(pctx: *mut u64) -> i32;
93 pub(super) fn cuCtxSetCurrent(ctx: u64) -> i32;
94 pub(super) fn cuStreamCreate(phStream: *mut u64, flags: u32) -> i32;
95 pub(super) fn cuMemAllocHost_v2(pp: *mut *mut c_void, bytesize: usize) -> i32;
96 pub(super) fn cuMemFreeHost(p: *mut c_void) -> i32;
97 pub(super) fn cuMemAllocManaged(dptr: *mut u64, bytesize: usize, flags: u32) -> i32;
98 pub(super) fn cuEventCreate(phEvent: *mut u64, flags: u32) -> i32;
99 pub(super) fn cuEventRecord(hEvent: u64, hStream: u64) -> i32;
100 pub(super) fn cuStreamWaitEvent(hStream: u64, hEvent: u64, flags: u32) -> i32;
101 pub(super) fn cuEventSynchronize(hEvent: u64) -> i32;
102 pub(super) fn cuEventQuery(hEvent: u64) -> i32;
103 pub(super) fn cuEventDestroy_v2(hEvent: u64) -> i32;
104}
105
106pub struct MetraleCudaBackend {
111 registry: Arc<MetraleRegistry>,
114 debug_sync_kernels: bool,
117 op_cache: crate::op_cache::OpCache,
120 live_allocs: Arc<parking_lot::Mutex<std::collections::HashMap<u64, AllocRecord>>>,
136 redzones: parking_lot::Mutex<Vec<RedZone>>,
144 default_stream: u64,
146 cuda_ctx: u64,
149}
150
151impl MetraleCudaBackend {
152 pub fn new(ordinal: usize, ptx_modules: &[(&'static str, &'static [u8])]) -> Result<Self> {
160 metrale_telemetry::run_metrics::reset_for_new_run();
163 let registry = MetraleRegistry::load(ordinal, ptx_modules)
164 .map_err(|e| anyhow::anyhow!("MetraleRegistry load failed: {e}"))?;
165 let default_stream = registry.raw_stream();
166 crate::timing::install_for_stream(default_stream);
167
168 let mut cuda_ctx: u64 = 0;
169 let status = unsafe { cuCtxGetCurrent(&mut cuda_ctx) };
170 if status != 0 || cuda_ctx == 0 {
171 bail!("cuCtxGetCurrent failed: status {status}, ctx {cuda_ctx:#x}");
172 }
173
174 tracing::info!(
175 "MetraleCudaBackend initialized on GPU {ordinal} with {} PTX modules",
176 ptx_modules.len()
177 );
178
179 Ok(Self {
180 live_allocs: Arc::new(parking_lot::Mutex::new(std::collections::HashMap::new())),
181 redzones: parking_lot::Mutex::new(Vec::new()),
182 registry,
183 debug_sync_kernels: std::env::var("METRALE_DEBUG_SYNC_KERNELS").as_deref() == Ok("1"),
184 op_cache: crate::op_cache::OpCache::new(),
185 default_stream,
186 cuda_ctx,
187 })
188 }
189
190 pub(crate) fn live_alloc_len(&self) -> usize {
192 self.live_allocs.lock().len()
193 }
194
195 pub fn sweep_unreleased(&self) -> usize {
203 let outstanding: Vec<(u64, AllocRecord)> = self.live_allocs.lock().drain().collect();
204 let count = outstanding.len();
205 if count > 0 {
206 let bytes: usize = outstanding.iter().map(|(_, r)| r.bytes).sum();
207 let mut by_site: std::collections::HashMap<String, (usize, usize)> =
210 std::collections::HashMap::new();
211 for (_, r) in &outstanding {
212 let e = by_site
213 .entry(format!("{}:{}", r.site.file(), r.site.line()))
214 .or_insert((0, 0));
215 e.0 += r.bytes;
216 e.1 += 1;
217 }
218 let mut rows: Vec<_> = by_site.into_iter().collect();
219 rows.sort_by(|a, b| b.1.0.cmp(&a.1.0));
220 let top: Vec<String> = rows
221 .iter()
222 .take(5)
223 .map(|(site, (b, n))| {
224 format!("{site} ({:.1} MB x{n})", *b as f64 / (1024.0 * 1024.0))
225 })
226 .collect();
227 tracing::warn!(
228 "sweep: {count} allocation(s) totalling {:.2} GB had no owner; \
229 largest sites: {}",
230 bytes as f64 / 1e9,
231 top.join(", ")
232 );
233 }
234 for (raw, _) in outstanding {
235 let status = unsafe { cuMemFree_v2(raw) };
238 if status != 0 && !crate::registry::is_teardown_noop(status) {
239 tracing::warn!("sweep: cuMemFree failed for {raw:#x}: status {status}");
240 }
241 }
242 count
243 }
244
245 pub fn registry(&self) -> &Arc<MetraleRegistry> {
246 &self.registry
247 }
248
249 pub(crate) fn debug_sync_kernels(&self) -> bool {
250 self.debug_sync_kernels
251 }
252}
253
254impl Drop for MetraleCudaBackend {
260 fn drop(&mut self) {
261 let swept = self.sweep_unreleased();
262 if swept > 0 {
263 tracing::warn!(
267 "backend drop reclaimed {swept} allocation(s) that no owner released — \
268 expected if a load was abandoned part-way, otherwise an unregistered owner"
269 );
270 }
271 }
272}
273
274pub(crate) fn polled_free_bytes(
278 cu_free: usize,
279 mem_available: Option<usize>,
280 integrated: Option<bool>,
281) -> usize {
282 effective_free_bytes(cu_free, mem_available, integrated.unwrap_or(false))
283}
284
285fn system_available_memory_bytes() -> Option<usize> {
288 let contents = std::fs::read_to_string("/proc/meminfo").ok()?;
289 for line in contents.lines() {
290 if line.starts_with("MemAvailable:") {
291 let kb: usize = line.split_whitespace().nth(1)?.parse().ok()?;
292 return Some(kb * 1024);
293 }
294 }
295 None
296}
297
298#[path = "cuda_backend/alloc_ledger.rs"]
299mod alloc_ledger;
300use alloc_ledger::AllocRecord;
301pub use alloc_ledger::LedgerProbe;
302
303#[cfg(test)]
304mod tests;