fix(local-ai): use CUDA for allocatable GPU memory - #1253
Conversation
|
🦞👀 Pull request received. I will update this pull request when review starts. |
cd3a6d6 to
c6765a7
Compare
|
Codex review: needs real behavior proof before merge. Reviewed August 31, 2026, 5:12 PM ET / 21:12 UTC. ClawSweeper reviewWhat this changesThe PR replaces NVML and DXGI Local AI GPU discovery with direct CUDA-driver probing, then updates qualification, setup verification, manifest handling, and tests for CUDA UUIDs and memory values. Merge readiness⛔ Blocked until stronger real behavior proof is added - 8 items remain Keep this PR open. The CUDA probe’s reported capacity is used directly for eligibility, but the related WDDM investigation records a CUDA-reported 46.3 GiB device that failed llama-server allocation at 15.81 GiB, so the PR can still approve an unusable model and reproduce the costly setup failure it aims to prevent. Priority: P1 Review scores
Verification
How this fits togetherLocal AI setup probes an NVIDIA GPU, chooses a fitting runtime and model, then downloads and launches llama-server. The probe feeds eligibility in both the setup UI and tray, so its capacity value determines whether users are offered a Local AI installation. flowchart LR
A[Windows NVIDIA driver] --> B[CUDA hardware probe]
B --> C[Local AI memory qualification]
C --> D[Model and runtime selection]
D --> E[Managed llama-server]
E --> F[Setup verification]
Decision needed
Why: The evidence establishes that replacing DXGI aggregation is necessary, but it does not establish that CUDA’s reported total is a safe cross-hardware admission limit. Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Root-cause clusterRelationship: Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Use a capacity policy proven conservative for WDDM while retaining supported unified-memory hardware, then demonstrate selection, full model allocation, and successful inference on the affected hardware class. Do we have a high-confidence way to reproduce the issue? No. The source path and related WDDM allocation trace establish a credible scenario, but the supplied proof does not show a current-head model allocation and inference on the affected hardware. Is this the best way to solve the issue? No. Removing DXGI aggregation addresses the reported bug, but using Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against f46400aab24e. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (5 earlier review cycles)
|
c6765a7 to
aff93c1
Compare
f84912d to
effc11a
Compare
| { | ||
| if (!OperatingSystem.IsWindows() || CuInit(0) != CudaSuccess || | ||
| CuDeviceGetCount(out int count) != CudaSuccess) | ||
| { |
There was a problem hiding this comment.
we should surface an error to the user here if cuda is not found or can't be initialized
| : null; | ||
| var gpus = new List<GpuInfo>(); | ||
| for (int ordinal = 0; ordinal < count; ordinal++) | ||
| { |
There was a problem hiding this comment.
Suggest to refactor this in a more functional idiom:
private static IReadOnlyList<GpuInfo> CaptureCudaGpus()
{
if (!OperatingSystem.IsWindows() || CuInit(0) != CudaSuccess ||
CuDeviceGetCount(out int count) != CudaSuccess)
{
return [];
}
int? cudaMajorVersion =
CuDriverGetVersion(out int driverVersion) == CudaSuccess && driverVersion > 0
? driverVersion / 1000
: null;
return Enumerable.Range(0, count)
.Select(ordinal => TryCaptureGpu(ordinal, cudaMajorVersion))
.Where(gpu => gpu is not null)
.Select(gpu => gpu!)
.ToList();
}
private static GpuInfo? TryCaptureGpu(int ordinal, int? cudaMajorVersion)
{
if (CuDeviceGet(out int device, ordinal) != CudaSuccess)
return null;
string? name = ReadDeviceName(device);
string? pciBusId = ReadPciBusId(device);
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(pciBusId))
return null;
return WithCudaContext(device, () =>
{
if (CuMemGetInfo(out nuint freeBytes, out nuint totalBytes) != CudaSuccess ||
totalBytes == 0 || totalBytes > long.MaxValue || freeBytes > totalBytes)
{
return null;
}
return new GpuInfo(
GpuVendor.Nvidia,
name,
GpuVisibleMemoryBytes: (long)totalBytes,
FreeGpuVisibleMemoryBytes: (long)freeBytes,
CudaMajorVersion: cudaMajorVersion,
StableId: ToCudaVisibleDevicesSelector(pciBusId));
});
}
private static GpuInfo? WithCudaContext(int device, Func<GpuInfo?> action)
{
if (CuCtxCreate(out IntPtr context, 0, device) != CudaSuccess)
return null;
try
{
return action();
}
finally
{
_ = CuCtxDestroy(context);
}
}
Split CaptureCudaGpus into TryCaptureGpu (per-device capture) and WithCudaContext (context create/destroy) and reassemble via Enumerable.Range(...).Select(...).Where(...).ToList(). Suggested in review: openclaw#1253 (review)
Query the CUDA driver directly for device identity and total/free allocatable memory instead of combining NVML and DXGI accounting. This should qualify both discrete RTX and UMA devices using the same allocator-visible source of truth. Closes openclaw#1191. Related: Dallin's openclaw#1237 and Pedro's openclaw#1239.
Split CaptureCudaGpus into TryCaptureGpu (per-device capture) and WithCudaContext (context create/destroy) and reassemble via Enumerable.Range(...).Select(...).Where(...).ToList(). Suggested in review: openclaw#1253 (review)
8b66f30 to
fa28506
Compare
Summary
nvcuda.dllloading to System32Closes #1191.
Related investigation: #1237 and #1239. CUDA itself supplies the capacity its allocator can use, avoiding DXGI shared-memory overcounting on discrete GPUs while supporting unified-memory devices through the same source of truth.
Commit structure
fix(local-ai): use CUDA for allocatable GPU memory- Joel Fernandesrefactor(local-ai): capture CUDA GPUs with a functional pipeline- Pedro LarroyThe branch contains exactly these two commits and no merge commit.
Validation
./build.ps1- passeddotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore- 3,836 passed, 32 environment-gated skipsdotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore- 2,808 passeddotnet test ./tests/OpenClaw.SetupEngine.Tests/OpenClaw.SetupEngine.Tests.csproj --no-restore- 1,027 passedscripts/run-proof-tests.ps1- 8 passedgit diff --check upstream/main...HEAD- passedReal behavior proof
Current head was probed on Windows x64 with an NVIDIA GeForce RTX 5090:
13GPU-cc66bca6-b5ff-dd70-995c-d81a07add980, matchingnvidia-smi34,190,458,880bytes32,432,455,680bytes at capture timeCUDA_VISIBLE_DEVICESsucceeded and returned the same GPUFocused setup proof verified full-offload log parsing, CUDA model-buffer evidence, allocator-memory delta evidence, and endpoint refresh behavior. A WinUI screenshot was not captured because interactive computer-use is unavailable in this session; the changed hardware path is covered by the live probe and focused tests above.
Security and compatibility
DllImportSearchPath.System32GPU-...NVML manifest identity, preserving existing managed installations