use crate::compute::driver::Hip;
use crate::compute::stream::Stream;
use cubecl_hip_sys::{hipGraph_t, hipGraphExec_t};
use cubecl_server::command::GraphDriver;
use cubecl_server::driver::checked;
use cubecl_server::server::ServerError;
pub struct Executable(hipGraphExec_t);
impl Drop for Executable {
fn drop(&mut self) {
unsafe {
cubecl_hip_sys::hipGraphExecDestroy(self.0);
}
}
}
impl GraphDriver for Hip {
type Executable = Executable;
fn begin(stream: &mut Stream) -> Result<(), ServerError> {
let status = unsafe {
cubecl_hip_sys::hipStreamBeginCapture(
stream.sys,
cubecl_hip_sys::hipStreamCaptureMode_hipStreamCaptureModeGlobal,
)
};
Ok(checked("hipStreamBeginCapture", status)?)
}
fn instantiate(
stream: &mut Stream,
doomed: Option<ServerError>,
) -> Result<Executable, ServerError> {
unsafe { instantiate_recording(stream.sys, doomed) }
}
fn upload(exec: &Executable, stream: &mut Stream) {
let uploaded = unsafe { cubecl_hip_sys::hipGraphUpload(exec.0, stream.sys) };
if let Err(err) = checked("hipGraphUpload", uploaded) {
log::warn!(
"Pre-uploading the captured graph failed; \
the first replay will upload on demand: {err}"
);
}
}
fn replay(exec: &Executable, stream: &mut Stream) -> Result<(), ServerError> {
let status = unsafe { cubecl_hip_sys::hipGraphLaunch(exec.0, stream.sys) };
Ok(checked("hipGraphLaunch", status)?)
}
}
unsafe fn instantiate_recording(
sys: cubecl_hip_sys::hipStream_t,
doomed: Option<ServerError>,
) -> Result<Executable, ServerError> {
unsafe {
let mut graph: hipGraph_t = std::ptr::null_mut();
checked(
"hipStreamEndCapture",
cubecl_hip_sys::hipStreamEndCapture(sys, &mut graph),
)?;
if let Some(doomed) = doomed {
cubecl_hip_sys::hipGraphDestroy(graph);
return Err(doomed);
}
let alloc_nodes = count_memory_nodes(graph);
if alloc_nodes > 0 {
cubecl_hip_sys::hipGraphDestroy(graph);
return Err(ServerError::graph_state(format!(
"capture recorded {alloc_nodes} memory node(s): an allocation inside the capture \
window makes the graph un-relaunchable, so the capture is rejected (the \
persistent pool should have served this allocation)"
)));
}
let mut exec: hipGraphExec_t = std::ptr::null_mut();
let instantiated = checked(
"hipGraphInstantiate",
cubecl_hip_sys::hipGraphInstantiate(
&mut exec,
graph,
std::ptr::null_mut(),
std::ptr::null_mut(),
0,
),
);
cubecl_hip_sys::hipGraphDestroy(graph);
instantiated.map(|_| Executable(exec)).map_err(Into::into)
}
}
unsafe fn count_memory_nodes(graph: cubecl_hip_sys::hipGraph_t) -> usize {
let mut num_nodes: usize = 0;
let counted =
unsafe { cubecl_hip_sys::hipGraphGetNodes(graph, std::ptr::null_mut(), &mut num_nodes) };
if let Err(err) = checked("hipGraphGetNodes", counted) {
log::warn!("{err} while counting the graph's nodes; skipping the memory-node check");
return 0;
}
let mut nodes: Vec<cubecl_hip_sys::hipGraphNode_t> = vec![std::ptr::null_mut(); num_nodes];
let mut num_read = num_nodes;
let read =
unsafe { cubecl_hip_sys::hipGraphGetNodes(graph, nodes.as_mut_ptr(), &mut num_read) };
if let Err(err) = checked("hipGraphGetNodes", read) {
log::warn!(
"{err} while reading the graph's {num_nodes} node(s); \
skipping the memory-node check"
);
return 0;
}
nodes
.iter()
.take(num_read)
.filter(|node| {
let mut ty: cubecl_hip_sys::hipGraphNodeType =
cubecl_hip_sys::hipGraphNodeType_hipGraphNodeTypeKernel;
let queried = unsafe { cubecl_hip_sys::hipGraphNodeGetType(**node, &mut ty) };
if let Err(err) = checked("hipGraphNodeGetType", queried) {
log::warn!("{err}; treating the node as not a memory node");
return false;
}
matches!(
ty,
cubecl_hip_sys::hipGraphNodeType_hipGraphNodeTypeMemAlloc
| cubecl_hip_sys::hipGraphNodeType_hipGraphNodeTypeMemFree
)
})
.count()
}