LeanProfiler

6.6. Interpret CUDA spans🔗

Build TorchLean with CUDA and run a device model:

LEAN_PROFILE=1 \
LEAN_PROFILE_OUT=build/mlp-cuda-trace.json \
LEAN_PROFILE_SUMMARY_OUT=build/mlp-cuda-summary.json \
lake -R -K cuda=true exe leanprofiler_torchlean \
  quickstart_mlp --device cuda --backend eager --dtype float --steps 3

The integration forwards cuda=true to TorchLean and adds CUDA link flags to the final executable. Without that forwarding, Lake can leave a dependency on TorchLean's CPU parity stubs even though the workspace root received -K cuda=true.

When the command line contains --device cuda, the runner uses Cuda.spanHooks. The hook checks TorchLean's native runtime, samples its buffer allocator, runs the model, calls cudaDeviceSynchronize, and samples the allocator again:

A synchronized LeanProfiler host span around CUDA launches, copies, kernels, and final synchronization

A three-step quickstart run recorded:

"device": "cuda"
"timing": "device-synchronized"
"alloc_live_bytes": 0
"alloc_peak_bytes": 1596
"alloc_delta_bytes": 0
"hook_error": null

The duration is a synchronized host boundary. It includes queue delay, device work, and synchronization overhead; it is not the sum of kernel execution times. The allocator fields count TorchLean-owned device buffers. LeanProfiler does not read CUDA events, CUPTI activity, stream IDs, memcopy events, or per-kernel allocations, so Perfetto still shows Lean host threads. Use a native device profiler when the synchronized boundary points to CUDA and the next question is which kernel or transfer is responsible.

Run TorchLean's CUDA kernel and allocator checks without the unrelated Python and dataset suites:

CUDA_VISIBLE_DEVICES=0 \
lake -R -K cuda=true exe leanprofiler_torchlean_cuda_tests