cuda: infrastructure for whole-step CUDA graph capture
- Thread-local launch stream (xserv_cuda::stream): every kernel wrapper, cublasSetStream, and NCCL collective now launches on current_stream_raw() — the legacy null stream by default (behavior unchanged), or the capture stream installed via push_stream during graph capture. Capture is impossible on the legacy stream. - Allocator retain mode: blocks freed inside a retain window are quarantined (RetainedBlocks) instead of pooled, so an instantiated graph keeps exclusive ownership of every intermediate buffer it references across replays. - Capture mode GLOBAL -> THREAD_LOCAL: concurrent TP rank threads must not poison each other's captures with their own cudaMallocs. - embedding_device_ids / rope_inplace_device_pos: variants reading token ids / positions from persistent device buffers, replacing the per-call host upload that a captured region cannot contain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,22 @@ pub fn cached_trim() {
|
||||
/// Called from `GpuBuffer::Drop` for pooled buffers. Takes raw pointer
|
||||
/// and size to avoid re-triggering Drop.
|
||||
pub fn return_to_pool(ptr: *mut u8, len: usize) {
|
||||
// During CUDA graph capture, buffers freed by the captured code are
|
||||
// quarantined instead of pooled: the instantiated graph references their
|
||||
// addresses on every replay, so they must never be handed to another
|
||||
// consumer for as long as the graph lives.
|
||||
let quarantined = RETAINED.with(|cell| {
|
||||
let mut r = cell.borrow_mut();
|
||||
if let Some(list) = r.as_mut() {
|
||||
list.push((ptr, len));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
if quarantined {
|
||||
return;
|
||||
}
|
||||
ALLOCATOR.with(|cell| {
|
||||
let mut alloc = cell.borrow_mut();
|
||||
let bucket = bucket_size(len);
|
||||
@@ -119,6 +135,41 @@ pub fn return_to_pool(ptr: *mut u8, len: usize) {
|
||||
});
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static RETAINED: RefCell<Option<Vec<(*mut u8, usize)>>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
/// Buffers freed while a retain window was active. Holding this keeps their
|
||||
/// memory out of the pool; dropping it returns the blocks (on the owning
|
||||
/// thread) for reuse.
|
||||
pub struct RetainedBlocks(Vec<(*mut u8, usize)>);
|
||||
|
||||
impl Drop for RetainedBlocks {
|
||||
fn drop(&mut self) {
|
||||
for (ptr, len) in self.0.drain(..) {
|
||||
return_to_pool(ptr, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start quarantining buffers freed on this thread (see `return_to_pool`).
|
||||
/// Must be paired with `end_retain` on the same thread; nesting unsupported.
|
||||
pub fn begin_retain() {
|
||||
RETAINED.with(|cell| {
|
||||
let mut r = cell.borrow_mut();
|
||||
assert!(r.is_none(), "begin_retain: retain window already active");
|
||||
*r = Some(Vec::new());
|
||||
});
|
||||
}
|
||||
|
||||
/// Stop quarantining and hand the quarantined blocks to the caller.
|
||||
pub fn end_retain() -> RetainedBlocks {
|
||||
RETAINED.with(|cell| {
|
||||
let list = cell.borrow_mut().take().expect("end_retain without begin_retain");
|
||||
RetainedBlocks(list)
|
||||
})
|
||||
}
|
||||
|
||||
/// Round up to next power-of-2, minimum 512 bytes.
|
||||
fn bucket_size(size: usize) -> usize {
|
||||
let min = 512;
|
||||
|
||||
Reference in New Issue
Block a user