From 82db0d36fee3488f15e1064229207d4ad6c1af7b Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Sun, 9 Aug 2026 13:33:33 -0700 Subject: [PATCH] fix(bindings): two silent wrong results in the vector_add examples 1. A wrong value in the LAST element is reported as a pass. Both examples transliterate the C sample's success test: for i in range(n): sum_all = h_a[i] + h_b[i] if math.fabs(h_c[i] - sum_all) > 1e-7: break ... if i + 1 != n: print("Result = FAIL", file=sys.stderr) sys.exit(1) In C, `if (i == N)` works because a completed loop leaves `i == N`. In Python a completed loop leaves `i == n - 1`, hence the `i + 1 != n` rewrite -- but `break` at the final index `n - 1` produces `i + 1 == n` too. So a kernel that computes h_c[n-1] wrongly exits 0 and prints nothing. Use an explicit flag, which does not depend on where the loop stopped. 2. simple_malloc_multi_device_mmap grants access to only the last device. access_descriptors = [cuda.CUmemAccessDesc()] * len(mapping_devices) List multiplication stores N references to ONE mutable CUmemAccessDesc, so the loop that fills in location.id overwrites the same object N times and cuMemSetAccess receives mapping_devices[-1] repeated N times. It succeeds; the devices that were supposed to be granted access instead fault on first touch. The helper is written as a general multi-device routine -- that is what its docstring and its mapping_devices parameter are for -- and is masked today only because main() passes a single device. Neither is reachable from CI: tests/test_examples.py runs each example with no arguments on a machine where the kernel is correct. --- .../examples/0_Introduction/vector_add_drv.py | 8 +++++++- .../0_Introduction/vector_add_mmap.py | 19 +++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/cuda_bindings/examples/0_Introduction/vector_add_drv.py b/cuda_bindings/examples/0_Introduction/vector_add_drv.py index d2356c0d3a1..fb431ad80b7 100644 --- a/cuda_bindings/examples/0_Introduction/vector_add_drv.py +++ b/cuda_bindings/examples/0_Introduction/vector_add_drv.py @@ -101,9 +101,15 @@ def main(): # h_C contains the result in host memory check_cuda_errors(cuda.cuMemcpyDtoH(h_c, d_c, nbytes)) + # A completed C `for` loop leaves i == n, which is why the C sample can + # test `i == N` for success. A completed Python loop leaves i == n - 1, so + # a flag is needed: `i + 1 != n` is also what `break` at the last index + # produces, and a wrong value in h_c[n - 1] would be reported as a pass. + mismatch = False for i in range(n): sum_all = h_a[i] + h_b[i] if math.fabs(h_c[i] - sum_all) > 1e-7: + mismatch = True break # Free device memory @@ -112,7 +118,7 @@ def main(): check_cuda_errors(cuda.cuMemFree(d_c)) check_cuda_errors(cuda.cuCtxDestroy(cu_context)) - if i + 1 != n: + if mismatch: print("Result = FAIL", file=sys.stderr) sys.exit(1) diff --git a/cuda_bindings/examples/0_Introduction/vector_add_mmap.py b/cuda_bindings/examples/0_Introduction/vector_add_mmap.py index 9faa45bedb8..90540ced8d2 100644 --- a/cuda_bindings/examples/0_Introduction/vector_add_mmap.py +++ b/cuda_bindings/examples/0_Introduction/vector_add_mmap.py @@ -157,8 +157,13 @@ def simple_malloc_multi_device_mmap(size, resident_devices, mapping_devices, ali simple_free_multi_device_mmap(dptr, size) return status, None, None - # Each accessDescriptor will describe the mapping requirement for a single device - access_descriptors = [cuda.CUmemAccessDesc()] * len(mapping_devices) + # Each accessDescriptor will describe the mapping requirement for a single device. + # One CUmemAccessDesc per device: `[CUmemAccessDesc()] * n` would store n + # references to a single mutable object, so the loop below would overwrite + # the same descriptor n times and cuMemSetAccess would receive the last + # device repeated n times -- succeeding, while every other device faults on + # first touch. + access_descriptors = [cuda.CUmemAccessDesc() for _ in mapping_devices] # Prepare the access descriptor array indicating where and how the backings should be visible. for idx in range(len(mapping_devices)): @@ -290,10 +295,16 @@ def main(): # h_C contains the result in host memory check_cuda_errors(cuda.cuMemcpyDtoH(h_c, d_c, size)) - # Verify result + # Verify result. + # A completed C `for` loop leaves i == n, which is why the C sample can + # test `i == N` for success. A completed Python loop leaves i == n - 1, so + # a flag is needed: `i + 1 != n` is also what `break` at the last index + # produces, and a wrong value in h_c[n - 1] would be reported as a pass. + mismatch = False for i in range(n): sum_all = h_a[i] + h_b[i] if math.fabs(h_c[i] - sum_all) > 1e-7: + mismatch = True break check_cuda_errors(simple_free_multi_device_mmap(d_a, allocation_size)) @@ -302,7 +313,7 @@ def main(): check_cuda_errors(cuda.cuCtxDestroy(cu_context)) - if i + 1 != n: + if mismatch: print("Result = FAIL", file=sys.stderr) sys.exit(1)