COOLJAPAN
← All posts

OxiCUDA 0.5.3 Released — Closing an Async-Copy Race in FFT Transforms and DeviceBuffer

OxiCUDA 0.5.3 fixes an async-copy race condition where cuMemcpyDtoHAsync/cuMemcpyHtoDAsync and DeviceBuffer::copy_from_host could return before the transfer had actually landed, letting the very next read observe stale or zeroed device/host memory. 38,675 tests, ~1.30M SLoC, 74 crates.

release oxicuda cuda gpu pure-rust race-condition async correctness bugfix

A cudaMemcpy*Async call that returns before the copy has actually landed isn’t asynchronous — it’s a race condition wearing an async API’s clothes.

Today we released OxiCUDA 0.5.3 — a correctness release closing an async-copy race condition across oxicuda-fft’s CPU-fallback transform paths and oxicuda-memory’s DeviceBuffer::copy_from_host, where a cuMemcpy*Async/cuMemcpyHtoD_v2 call could return before the transfer had actually landed in device or host memory.

No CUDA SDK. No nvcc. No C/C++ toolchain. OxiCUDA is a type-safe, memory-safe, pure-Rust replacement for the entire NVIDIA CUDA Toolkit software stack — cuBLAS, cuDNN, cuFFT, cuSPARSE, cuSOLVER, cuRAND, and more, across 74 crates and ~1.30M lines of safe Rust. libcuda.so/nvcuda.dll is loaded dynamically at runtime via libloading; nothing about building or running OxiCUDA requires the CUDA SDK, headers, or a C/C++ compiler. The result compiles to a single static binary and runs everywhere the driver is present.

Why OxiCUDA 0.5.3 is a game changer

Async CUDA copy APIs are one of the easiest primitives in the whole ecosystem to get subtly wrong:

OxiCUDA 0.5.3 ends all of that.

Technical Deep Dive

  1. Foundation — oxicuda-memory. DeviceBuffer::copy_from_host is this release’s second fix, and it’s not a rarely-used corner of the API: it’s the exact call every quick-start example uses to get data onto the device in the first place.
  2. FFT transforms — oxicuda-fft. transforms::c2c, c2r, fft2d, fft3d, and r2c all route through the same shared copy_dtoh_async/copy_htod_async pair, so one fix in two functions closes the race across the entire transform family simultaneously.
  3. Streams — the non-blocking-by-default design. Every OxiCUDA Stream is CU_STREAM_NON_BLOCKING from construction, which is precisely why the driver’s own implicit default-stream synchronization — the safety net CUDA newcomers often rely on without realizing it — doesn’t save you here. OxiCUDA’s copy helpers now provide that guarantee explicitly instead.
  4. The wider domain layer. oxicuda-blas, oxicuda-dnn, oxicuda-sparse, oxicuda-solver, and the dozens of domain crates built on oxicuda-memory/oxicuda-fft all inherit both fixes today, without a single line of their own code changing.

Getting Started

cargo add oxicuda --features blas

Default features remain driver, memory, and launch; every subsystem — ptx, autotune, blas, dnn, fft, sparse, solver, rand, nvrtc, primitives, vulkan, metal, webgpu, rocm, level-zero, and full for everything — is its own opt-in flag.

A complete GEMM, end to end. Look closely at the copy_from_host calls below — that’s exactly the function this release fixed:

use oxicuda::prelude::*;

fn main() -> Result<(), oxicuda::Error> {
    // Initialize driver and select GPU device
    let device = Device::get(0)?;
    let ctx = Context::new(device)?;
    let stream = Stream::new(&ctx)?;

    // Allocate device memory
    let mut d_a = DeviceBuffer::<f32>::zeroed(1024)?;
    let mut d_b = DeviceBuffer::<f32>::zeroed(1024)?;
    let mut d_c = DeviceBuffer::<f32>::zeroed(1024)?;

    // Copy host data to device -- now correctly synchronized before returning
    d_a.copy_from_host(&host_a)?;
    d_b.copy_from_host(&host_b)?;

    // Launch a GEMM: C = alpha * A @ B + beta * C
    let handle = BlasHandle::new(&stream)?;
    handle.gemm(
        Transpose::None, Transpose::None,
        m, n, k,
        1.0f32,            // alpha
        &d_a, lda,
        &d_b, ldb,
        0.0f32,            // beta
        &mut d_c, ldc,
    )?;

    stream.synchronize()?;

    // Copy result back to host
    let mut result = vec![0.0f32; m * n];
    d_c.copy_to_host(&mut result)?;
    Ok(())
}

On a machine with an NVIDIA GPU, run the same validation this release’s fixes were checked against:

cargo test --features gpu-tests

What’s New in 0.5.3

Tips

This is the foundation

OxiCUDA is the GPU layer beneath the rest of the COOLJAPAN ecosystem. Its own architecture diagram puts SciRS2, OxiONNX, TrustformeRS, and ToRSh directly on top of it, with OxiBLAS and OxiFFT rounding out the list of projects that lean on this stack — and the oxicuda crate itself is an umbrella re-export over all 74 crates, so a single cargo add oxicuda pulls in whichever subsystem a dependent project needs. Every one of them inherits 0.5.3’s correctness fixes today, without a single line of their own code changing.

Repository: https://github.com/cool-japan/oxicuda

Star the repo if you believe an “async” copy should mean the data is actually there when the call returns to your control flow. Every star tells us to keep building.

The era of silent GPU data races is over. Pure Rust GPU computing is here — and as of 0.5.3, its async copies actually finish before they say they did.

KitaSan at COOLJAPAN OÜ July 27, 2026

↑ Back to all posts