advanced27 min studyLesson 470 in Engineering

Memory-safety checking on devices

Choose the relevant Compute Sanitizer instrument and test boundary cases without confusing clean dynamic runs with a proof.

On this page

What you’ll understand

  • Match memory, race, initialization, and synchronization tools to their supported scope.
  • Derive launch-tail guards and byte bounds for tensor kernels.
  • Design reproducible device checks and state the limits of dynamic coverage.

A tensor kernel may produce plausible outputs while reading outside a logical view, using uninitialized values, or racing over shared storage. Device instrumentation helps identify particular classes of executed errors, but its scope and supported hardware change with releases. This lesson uses the Compute Sanitizer 2026.3 manual and release notes with an Ampere SM80 target. That tool version is stated separately from the CUDA 12.8.1 mechanism baseline used elsewhere in this module.

Choose a checker that covers the suspected error

Memcheck can report supported invalid or misaligned device accesses, CUDA API errors, and leaks when the relevant checking options are enabled. An access within a large underlying allocation can still violate a smaller tensor view’s logical bounds without crossing an allocation boundary visible to the checker. Keep shape and stride assertions in the application as well as tool runs. Precise reports depend on supported instructions, compilation information, and tool compatibility; preserve the actual command, binary, driver, toolkit, and GPU identity for reproduction.

Racecheck targets supported on-chip shared-memory hazards; it is not a universal detector of global-memory races between devices or arbitrary host threads. Initcheck in the stated release supports the documented address-space selection: global checking is the default, while shared-memory checking is selected with --initcheck-address-space shared, or both spaces with all. That shared capability was introduced in a later release and must not be projected onto every older tool. Synccheck identifies supported synchronization misuse, but a clean report is not a proof of all progress, barrier, or memory-order properties.

G=⌈NT⌉,i=bT+t,0≤i<NG=\left\lceil\frac{N}{T}\right\rceil,\qquad i=bT+t,\qquad 0\leq i<N
For a one-dimensional kernel with N positive elements and T positive threads per block, G blocks cover the domain. Block index b and lane-within-block t generate extra threads when N is not divisible by T; the final inequality is the required logical access guard.↔ Scroll wide equations horizontally.

Boundary arithmetic is part of the safety argument

For a contiguous tensor, derive both the element index and the accessed byte interval. A valid first byte is insufficient if a vectorized access extends beyond the end. For views, include offset, stride, element width, and allocation extent rather than reusing a contiguous-array proof. Test empty and small shapes through the appropriate launch policy, nonmultiples of block size, and dimensions that change vectorization or shared-memory participation. Use index types that cannot overflow at supported problem sizes before an otherwise correct bounds comparison.

Run memory-access checks before interpreting other tool findings, because an earlier corruption can produce misleading later symptoms. Then select the checks relevant to the program, including shared-memory initialization and synchronization where supported. Instrumentation changes timing and can add substantial overhead, so do not use sanitized runs as normal throughput measurements. It also cannot inspect every host memory access outside its CUDA-related coverage. Release notes identify capability changes and known limitations; compatibility is part of the experiment rather than an assumed constant.

ai=B+si,[ai,ai+s)⊆[B,B+sN)a_i=B+si,\qquad [a_i,a_i+s)\subseteq[B,B+sN)
B is the byte base and s the positive byte width of one contiguous scalar element. Half-open intervals express complete-access bounds, assuming arithmetic does not overflow. For strided or vectorized views, derive the actual interval instead of using this scalar formula unchanged.↔ Scroll wide equations horizontally.

A clean dynamic run covers its executed cases

Construct adversarial but valid inputs that exercise boundary tiles, different strides, repeated buffer reuse, and concurrent streams. Check numeric outputs against a trusted implementation, because an in-bounds access to the wrong tensor element may satisfy the memory checker while violating the algorithm. A passing run reports no covered violation in that execution; it does not prove every possible interleaving or untested shape safe. Treat nondeterministic failures as evidence to investigate rather than accepting a later passing run as a correction.

The probability lab models only a deliberately assumed input-trigger probability. Reset, set probability to 0.1 and trials to 10, predict the chance of at least one trigger, then Step. Its exact binomial distribution is not a stream of sanitizer executions and says nothing about detector sensitivity. Independent identical trigger probabilities are a hypothesis, often unrealistic for correlated test shapes. Use the model to question coverage claims, not to infer a real kernel’s bug probability or certify correctness.

A launch tail beyond a float array

A kernel processes 1,000 contiguous FP32 elements with 256 threads per block. Assume four-byte elements and sufficient integer range. Calculate the launch and explain the final thread’s invalid access without an index guard.

  1. The grid needs ceil(1,000/256) = 4 blocks, launching 1,024 threads. There are 24 extra indices, from 1,000 through 1,023.

  2. The allocation contains 4,000 bytes. The last valid element, index 999, starts at offset 3,996 and occupies bytes 3,996 through 3,999.

  3. An unconditional access by index 1,023 touches offsets 4,092 through 4,095, outside the allocation. Guard every relevant access with i < 1,000; also preserve valid synchronization participation for threads that perform no data access.

The result

A tail guard prevents this scalar out-of-bounds pattern, while other view, vector-width, initialization, and synchronization contracts still need checking.

Explore the model

Interactive lab

Interactive lab · probability

An assumed input-trigger model

Reset, set trigger probability p = 0.1 and trials n = 10, predict the probability of at least one trigger, then Step: approximately 0.6513. Playback highlights exact binomial outcomes; it does not execute a kernel, generate independent tests, or model sanitizer sensitivity.

An assumed input-trigger modelThese are exact binomial probabilities for independent trials with one fixed success probability. Playback tours the distribution; it does not resample outcomes. Current readings: Mean · np: 5; Variance · np(1−p): 2.5; Total probability: 1. All parameters are adjustable with the labeled controls below.03581000.070.150.220.3Successes kP(X = k)k = 0 · P = 9.77e-4
Exact probabilityHighlighted outcomeMean np
Mean · np
5
Variance · np(1−p)
2.5
Total probability
1
t = 0 s

These are exact binomial probabilities for independent trials with one fixed success probability. Playback tours the distribution; it does not resample outcomes. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.

Look forFinite clean runs do not prove absence of a bug, and a binomial coverage calculation depends on a declared input model.

Check your understanding

Which statement accurately describes the stated Compute Sanitizer tool scope?

Try it yourself

For 513 FP32 elements and 128 threads per block, calculate grid size, launched threads, extra indices, and the last valid element’s byte interval. Explain one limitation of a clean sanitizer run.

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Round the block count upward, then use zero-based element 512 for the last valid access.

Compare with the solution

Five blocks launch 640 threads, leaving 640 − 513 = 127 extra indices. The allocation is 513 × 4 = 2,052 bytes, and element 512 occupies offsets 2,048 through 2,051. Guard scalar accesses against i ≥ 513 while keeping required synchronization valid. A clean run still does not test every shape or interleaving, and an in-allocation access to the wrong logical tensor element may evade allocation-bound checks.

Take it with you

  • Choose tools according to their version, architecture support, and actual coverage.
  • Prove complete byte intervals and tail-thread behavior rather than checking only a starting address.
  • Dynamic checking complements numeric tests and reasoning without replacing them.

Read further

Original sources and deeper explanations to follow at your own pace.

Optional progress, saved in this browser when available. Revisit any lesson at any time.

Lesson not marked complete.