The owned-CUDA capability floor is read exclusively from environment variables that nothing produces:
// crates/synapse-module/src/lib.rs:5925
fn owned_cuda_floor_decision() -> CudaFloorDecision {
let driver_api = ["SYNAPSE_CUDA_DRIVER_API", "CUDA_DRIVER_API"] ...
let compute = ["SYNAPSE_CUDA_COMPUTE_CAPABILITY", "CUDA_COMPUTE_CAPABILITY"] ...
let (Some(driver_api), Some((major, minor))) = (driver_api, compute) else {
return CudaFloorDecision::Unsupported {
reason: CudaUnsupportedReason::HardwareUnavailable, observed: None,
};
};
If either variable is unset, the decision is HardwareUnavailable — backend_unavailable before any worker is spawned, with no observed values in the refusal. There is no probe anywhere that fills these in, so a machine that comfortably passes the floor (RTX 4050, CC 8.9, driver API 13030 here) refuses every owned-CUDA load until the operator finds the variable names in source. They are documented nowhere: SYNAPSE_CUDA_DRIVER_API appears in exactly one place (the function above), and neither name appears in README or docs.
Two papercuts observed while setting this up
- The driver value is the CUDA driver-API integer, not the marketing version. This box's NVIDIA driver is
610.88, but the floor compares against OWNED_CUDA_MINIMUM_DRIVER_API = 12040, so the correct value is 13030 (cuDriverGetVersion() — verified by calling it directly). Nothing in the env-var name or the refusal says which of the two numbers is expected; an operator who sets 610.88 silently parses to None (it is not a u32) and gets the same HardwareUnavailable as setting nothing.
- The refusal drops the observed hardware.
HardwareUnavailable carries observed: None, so the error cannot say "your CC is 8.9, the floor is 7.5, but the driver API was missing" — it cannot distinguish "no hardware" from "hardware fine, env incomplete".
Why this is a fixable gap, not an inherent one
The engine already talks to the CUDA driver directly (cuInit, cuCtxGetDevice, cuDevicePrimaryCtxRetain in crates/synapse-engine-cuda/src/cuda.rs), and device_meets_floor(driver_api, major, minor) already exists in synapse-engine-cuda/src/lib.rs:188. The two missing reads are cuDriverGetVersion and cuDeviceGetAttribute(..., COMPUTE_CAPABILITY_MAJOR/MINOR) — the same FFI pattern, a handful of lines.
Since the module deliberately does not link the CUDA engine (the worker is a separate process), the probe probably belongs on the worker side: the worker already reports machine facts in PONG (rss_mb, models_loaded), and the module could fold a worker-reported driver/CC pair into the floor decision (or at minimum into the refusal's observed). Design choice yours; the issue is that today there is no path at all.
Proposed behavior
- Keep env vars as an override.
- When unset, probe:
cuDriverGetVersion + device attributes, and cache the result per process.
Unsupported refusals carry observed whenever the probe ran, so the message distinguishes below-floor from missing-input.
- Document both variables (and the API-integer format) in README next to the CUDA section.
Happy to implement if the shape above looks right — I can't run the CUDA build gate locally, so I'd want your steer on whether the probe lives in the worker or the module before writing it.
The owned-CUDA capability floor is read exclusively from environment variables that nothing produces:
If either variable is unset, the decision is
HardwareUnavailable—backend_unavailablebefore any worker is spawned, with no observed values in the refusal. There is no probe anywhere that fills these in, so a machine that comfortably passes the floor (RTX 4050, CC 8.9, driver API 13030 here) refuses every owned-CUDA load until the operator finds the variable names in source. They are documented nowhere:SYNAPSE_CUDA_DRIVER_APIappears in exactly one place (the function above), and neither name appears in README or docs.Two papercuts observed while setting this up
610.88, but the floor compares againstOWNED_CUDA_MINIMUM_DRIVER_API = 12040, so the correct value is13030(cuDriverGetVersion()— verified by calling it directly). Nothing in the env-var name or the refusal says which of the two numbers is expected; an operator who sets610.88silently parses toNone(it is not au32) and gets the sameHardwareUnavailableas setting nothing.HardwareUnavailablecarriesobserved: None, so the error cannot say "your CC is 8.9, the floor is 7.5, but the driver API was missing" — it cannot distinguish "no hardware" from "hardware fine, env incomplete".Why this is a fixable gap, not an inherent one
The engine already talks to the CUDA driver directly (
cuInit,cuCtxGetDevice,cuDevicePrimaryCtxRetainincrates/synapse-engine-cuda/src/cuda.rs), anddevice_meets_floor(driver_api, major, minor)already exists insynapse-engine-cuda/src/lib.rs:188. The two missing reads arecuDriverGetVersionandcuDeviceGetAttribute(..., COMPUTE_CAPABILITY_MAJOR/MINOR)— the same FFI pattern, a handful of lines.Since the module deliberately does not link the CUDA engine (the worker is a separate process), the probe probably belongs on the worker side: the worker already reports machine facts in
PONG(rss_mb,models_loaded), and the module could fold a worker-reported driver/CC pair into the floor decision (or at minimum into the refusal'sobserved). Design choice yours; the issue is that today there is no path at all.Proposed behavior
cuDriverGetVersion+ device attributes, and cache the result per process.Unsupportedrefusals carryobservedwhenever the probe ran, so the message distinguishes below-floor from missing-input.Happy to implement if the shape above looks right — I can't run the CUDA build gate locally, so I'd want your steer on whether the probe lives in the worker or the module before writing it.