Repository navigation
feat(libtorch): run the bridge on the host when the SDK is CPU-only - #36
NicolasRouquette wants to merge 3 commits into
Conversation
|
Hey Nicolas, thanks! CPU-only LibTorch support is worth having. One issue before merging: NN/Tests/Suite.lean uses requireNativeRuntime for TORCHLEAN_REQUIRE_CUDA=1, but nativeAvailable now includes host execution. That lets a CUDA-required run pass on CPU. Could you make that strict check require an actual CUDA device, while retaining a separate native-runtime check for CPU sessions? Please also check the combination with #35, whose device-information tests currently assume CUDA. Our pending NVRTC/custom-kernel work will need conditional CUDA compilation and a clear unsupported error on the host; that is an integration requirement for our local changes, not a claim that this PR fails to build independently. Holding off until the strict CUDA check is fixed. |
The LibTorch bridge pinned `c10::kCUDA`, so a build without a CUDA device had no backend at all: `unavailable.c` fails every buffer operation, and the device buffer suite could only run on a GPU host. The bridge now selects its device once, at its first call, and runs on the host through ATen's CPU kernels when the SDK has no CUDA support or when `TORCHLEAN_LIBTORCH_DEVICE=cpu` asks for it. Nothing is reimplemented: every ATen operation already goes through `torchlean::options()`. Selection: `TORCHLEAN_LIBTORCH_DEVICE=cpu` selects the host and `=cuda` insists on a CUDA device. Unset, a visible CUDA device is selected when the SDK has CUDA support; otherwise the host is selected only when the SDK has no CUDA support at all, so a CUDA-enabled SDK without a visible device stays `RuntimeStatus.nativeUnavailable` rather than silently running on the host. CMake no longer requires the `torch_cuda` target. A CPU-only SDK compiles the backend with `TORCHLEAN_LIBTORCH_CUDA=0`, without CUDA headers, the CUDA toolkit, NVRTC or the driver API; the CPU-only pip wheel is a complete SDK for it. `sdk.txt` records the choice. Runtime-compiled kernels (the generated operations of `NN.Kernel`) need NVRTC and the driver API, so they compile only with a CUDA-enabled SDK, and `run_kernel` refuses at its entry unless a CUDA device is selected: on the host a generated operation fails through `IO` with an error that names the requirement, before any CUDA call. The CUDA suite runs its custom-computation checks on a CUDA device and checks that refusal on the host. Lean: `Runtime.Autograd.LibTorch.DeviceKind` and `deviceKind` report the selection. `RuntimeStatus` keeps its three states, with `.nativeAvailable` meaning that a device is selected, CUDA or host, so `requireNativeRuntime` and every existing match are unchanged. On the host the native allocator counters and driver memory read zero, `synchronize` and `emptyCache` return at once, `setMemoryFraction` and `setDevice` are rejected, and the suite skips its native memory probes. The suite prints the device and checks that the selection agrees with the visible device count and with the environment variable.
…nd CUDA Programs made of the IEEE basic operations, gathers and lookups give bit-identical results on the two devices; transcendental functions come from different libraries on the host and on CUDA and differ at the ulp level, and reductions differ in order.
`nativeAvailable` covers the host now that the bridge can run there, so `requireNativeRuntime`,
which the suite called for `TORCHLEAN_REQUIRE_CUDA=1`, let a run on a CPU-only SDK or under
`TORCHLEAN_LIBTORCH_DEVICE=cpu` pass a check meant to say that a GPU ran it.
`Buffer.requireCudaDevice` asks for the device kind as well, and the suite uses it for that
variable. `TORCHLEAN_REQUIRE_NATIVE=1` keeps the bridge-only meaning for CPU-only SDK sessions,
so that such a job fails on the unlinked default backend instead of passing with the device suite
skipped.
In the bridge, `require_cuda("<feature>")` is the entry check for an operation with no host
implementation (device selection, the allocator memory fraction and runtime-compiled kernels use
it), so it fails with a message naming the feature rather than with a CUDA call on a CPU tensor. The
READMEs, the installation page and the execution-modes chapter say what each variable rejects, and
quote the new messages.
Configured binary arithmetic computes only in runtime-compiled kernels, which the tape reaches
through pure entry points, where an error ends the process. The encoded upload therefore calls
`require_cuda` too, so on the host such a computation fails through `IO` when its tensor is
uploaded. The host branch of the CUDA suite checks both refusals and runs the custom-autograd checks
that need no generated kernel: the native-precision recordings and the resident-buffer checks.
bed0d9b to
5555080
Compare
|
Rebased onto current The strict check.
Your NVRTC work. It landed on
Combination with #35. #35 is stacked on this branch (its first three commits are this PR's); What else the rebase changed. Verification. RTX A4500, pip torch 2.11.0+cu128 with a CUDA 12.8 toolkit, and pip torch
Builds: |
What
The LibTorch bridge pins
c10::kCUDA, so the backend has one device and the default build hasnone:
unavailable.cfails every buffer operation. Before the move to LibTorch, the default buildlinked the
_stub.chost implementations of the same ABI, which is what kept the device buffersuite runnable on hosted CI ("CPU hosted CI does not validate GPU execution" in
csrc/libtorch/README.md) and what let a CPU-only deployment run code written againstRuntime.Autograd.LibTorch.Buffer.This PR lets the same bridge run on the host through ATen's CPU kernels. Nothing is reimplemented:
the device is chosen once, and every operation already goes through
torchlean::options().TORCHLEAN_LIBTORCH_DEVICE=cpuselects the host,=cudainsists on a CUDAdevice. Unset, a visible CUDA device is selected when the SDK has CUDA support; otherwise the
host is selected only when the SDK has no CUDA support at all. A CUDA-enabled SDK without a
visible device stays
RuntimeStatus.nativeUnavailable, as today, rather than silently runningon the host.
torch_cudatarget is no longer an error; it compiles the backendwith
TORCHLEAN_LIBTORCH_CUDA=0, with no CUDA headers and no toolkit, and links neither NVRTCnor the CUDA driver (the toolkit is looked for, and both are linked, only for a CUDA-enabled
SDK). The CPU-only pip wheel
(
pip install --index-url https://download.pytorch.org/whl/cpu torch==2.11.0, 746 MB, notoolkit) is a complete SDK for it.
Runtime.Autograd.LibTorch.DeviceKind(cuda | host) anddeviceKind.RuntimeStatuskeeps its three states;.nativeAvailablenow means "a device is selected",CUDA or host, so
requireNativeRuntimeand every existingmatchare unchanged. Because thehost satisfies it, a check that means "a GPU ran this" needs more:
Buffer.requireCudaDevicerequires the bridge and
deviceKind = .cuda, the suite'sTORCHLEAN_REQUIRE_CUDA=1calls itand fails on the host, and
TORCHLEAN_REQUIRE_NATIVE=1is the check for a host session (thebridge linked and a device selected), so a CPU-only SDK job cannot pass on the unlinked default
backend. The suite prints the device and checks that the selection agrees with the visible
count and with the environment variable.
synchronizeandemptyCachereturn at once;setMemoryFractionandsetDeviceare rejected throughrequire_cuda(feature), the one call an entry point without a host implementation makesfirst, so it fails naming the feature rather than failing inside a CUDA call on a CPU tensor;
the suite's native memory probes (accounting, saved attention buffers, OOM recovery) are
skipped. Code that needs the CUDA headers compiles under
#if TORCHLEAN_LIBTORCH_CUDA; codethat needs the selected device calls
require_cuda.NN.Kernel, compiled through NVRTC)follow both rules: the NVRTC compile and launch path compiles under the macro, and
run_kernelcalls
require_cudaat its entry. Configured binary arithmetic reaches its kernels through apure entry point, where an error would end the process, so its encoded upload calls
require_cudaas well. On the host a generated operation therefore fails throughIO, namingthe requirement. The CUDA suite runs its custom-computation and custom-autograd checks on a CUDA
device; on the host it checks both refusals and runs the custom-autograd checks that need no
generated kernel.
-K cuda=truekeeps its meaning, "link the LibTorch backend"; the SDK decides the device. Theextern names (
torchlean_cuda_*) and theConfig.device := .cudasession name also keep theirmeaning of "the bridge"; if you would rather have a
libtorch-cpudevice name for sessions, thatis a small follow-up and I am happy to do it, but I kept the user-facing vocabulary unchanged here.
Verification
On
5555080f(three commits onmain72754452, Lean v4.35.0):cudaTORCHLEAN_REQUIRE_CUDA=1hostTORCHLEAN_LIBTORCH_DEVICE=cpu TORCHLEAN_REQUIRE_NATIVE=1hostTORCHLEAN_LIBTORCH_DEVICE=cpu TORCHLEAN_REQUIRE_CUDA=1a CUDA device was required, but the LibTorch bridge runs on the host: the SDK is CPU-only, or TORCHLEAN_LIBTORCH_DEVICE=cpu selected the hostTORCHLEAN_LIBTORCH_CUDA=0,lddshowslibtorch_cpuand no CUDA library)hostTORCHLEAN_REQUIRE_NATIVE=1hostTORCHLEAN_REQUIRE_CUDA=1-Kcuda=false), CPU suiteTORCHLEAN_REQUIRE_NATIVE=1the LibTorch bridge was requested, but this executable was built without LibTorch; rebuild and run with `-K cuda=true`The host run covers the same sections as the CUDA run, including the CUDA kernel coverage suite,
the float32 contract checks, determinism, attention, convolution and pooling, and the stress
tests; the differences between the two logs are the skipped memory probes and the generated-kernel
checks, which the host replaces by its refusal checks. Results are
bit-identical across the two devices only for programs made of the IEEE basic operations, gathers
and lookups (a downstream table-lookup retrieval was checked bit for bit, host against GPU);
ATen's transcendental functions come from different libraries on the host and on CUDA and differ
at the ulp level (a downstream Newton inverse differed by at most 1e-6 in float32), and reductions
differ in order. The suite's tolerances were met on both.
Repo lint:
python3 scripts/checks/repo_lint.py --fail-on-warnOK.Rationale
The curated device suite can run on a hosted runner with the CPU wheel and no toolkit, so the real
buffer ABI is exercised on every push instead of only on a GPU box, and
TORCHLEAN_REQUIRE_NATIVE=1makes that job fail rather than skip if the bridge is not linked. It also
gives downstream code written against the buffer ABI a CPU deployment target again, which the old
_stub.cfiles provided andunavailable.cdoes not.This PR enables executing the same binary on GPU or on CPU.
Files
csrc/libtorch/torchlean.cpp,torchlean_libtorch.h,CMakeLists.txt,unavailable.c,README.md;scripts/libtorch_build.py,scripts/README.md;NN/Runtime/Autograd/Engine/LibTorch/{Controls,Buffer,Trusted}.lean,NN/Kernel/Runtime.lean;NN/Tests/Suite.lean,NN/Tests/Runtime/Cuda/{Stress,Elementwise}.lean;lakefile.lean,README.md,home_page/installation/index.md, the execution-modes chapter of the blueprint.