Skip to content

fix: use atomic load/store for device limit/sm_limit in shared region - #238

Open
om7057 wants to merge 3 commits into
Project-HAMi:mainfrom
om7057:fix/atomic-shared-limit-fields
Open

fix: use atomic load/store for device limit/sm_limit in shared region#238
om7057 wants to merge 3 commits into
Project-HAMi:mainfrom
om7057:fix/atomic-shared-limit-fields

Conversation

@om7057

@om7057 om7057 commented Jul 28, 2026

Copy link
Copy Markdown

Closes part of Project-HAMi/HAMi#2127.

context_size, hostpid, proc_num, status and last_kernel_time in shared_region_t were all made _Atomic in 24a0f49, but limit and sm_limit were missed.

Both are read on the CUDA allocation hot path (get_current_device_memory_limit, called from every hooked cuMemAlloc) and are exposed through public, tested setters on this side (set_current_device_memory_limit) and on the Go monitor side (SetDeviceMemoryLimit, SetDeviceSmLimit in pkg/monitor/nvidia), so a plain uint64_t here does not match the atomicity already guaranteed for the rest of the struct.

I checked and there is currently only one writer of these two fields in the whole system: do_init_device_memory_limits/do_init_device_sm_limits at shared-region creation, serialized by the region's file lock before the region is published. So this is not fixing an observed bug, it is closing the one gap left from 24a0f49 in a struct where every other field already carries this guarantee, and in tested API surface that a future caller could otherwise reintroduce the same race through.

Compatibility

_Atomic uint64_t has the same size and representation as uint64_t on the supported targets, so this does not change shared_region_t's layout. I verified with sizeof/offsetof before and after the change:

sizeof(shared_region_t) = 2008952
offsetof(limit)         = 1600
offsetof(sm_limit)      = 1728
offsetof(procs)         = 1856
sizeof(limit[0])        = 8

All identical, so the on-disk cudevshr.cache format and the Go-side mirror struct are unaffected.

Re-verified against the current head (3b56dde) with bench/abi_check from #239:

sizeof(shared_region_t)  = 2008952    (expected 2008952) OK
offsetof(limit)          = 1600       (expected 1600) OK
offsetof(sm_limit)       = 1728       (expected 1728) OK
offsetof(procs)          = 1856       (expected 1856) OK
sizeof(limit[0])         = 8          (expected 8) OK

Changes

  • limit/sm_limit in shared_region_t are now _Atomic uint64_t[CUDA_DEVICE_MAX_COUNT].
  • get_current_device_memory_limit, get_current_device_sm_limit, set_current_device_memory_limit use atomic_load_explicit/atomic_store_explicit with acquire/release, matching the style already used elsewhere in this file for proc_num, status, last_kernel_time.
  • do_init_device_memory_limits/do_init_device_sm_limits still take a plain uint64_t* and are otherwise unchanged, including the already-initialized comparison branch that calls them. try_create_shrreg now fills a local uint64_t array through them and copies that into the region with atomic_store_explicit (memory_order_relaxed, since the existing atomic_thread_fence(release) a few lines down already publishes it), instead of writing through a plain-uint64_t* cast into the _Atomic-qualified fields, which would have been a type violation independent of whether it's safe on current targets.
  • set_current_device_sm_limit_scale, get_current_device_sm_limit, set_current_device_memory_limit, and get_current_device_memory_limit now return after LOG_ERROR("Illegal device id...") instead of falling through to index the array anyway, which was an out-of-bounds read for an invalid dev. cuDeviceTotalMem_v2 (src/cuda/device.c) additionally now returns CUDA_ERROR_INVALID_DEVICE directly for an out-of-range dev, rather than forwarding whatever get_current_device_memory_limit's 0 return produces: that 0 means "no limit configured" everywhere else it's read (oom_check, cuMemGetInfo_v2, the NVML hook), so it does not fail closed, and this PR doesn't claim it does for those three; the cuDeviceTotalMem_v2 check is the one place that needed and got an explicit fix instead.

Testing

  • libvgpu.so builds clean with no new warnings.
  • Verified the ABI check above against the pre-change header to confirm zero layout drift.
  • Ran a concurrent initialization benchmark (fork+exec N processes through libvgpu.so, LD_PRELOAD-interposed) before and after this change as a functional regression check; numbers are unchanged.

A companion PR updates the Go side of this same shared struct: Project-HAMi/HAMi#2179.

Note on the CodeRabbit summary below: "kept existing input validation and return behavior unchanged" was accurate for the version it was generated against, but is no longer accurate as of the illegal-dev fix described above; return behavior does change for that case now, on purpose.

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability when reading and updating shared device memory and SM limits across processes and threads.
    • Ensured limit values remain consistent and visible during concurrent operations.
    • Added validation for invalid device IDs, returning an appropriate invalid-device error.
    • Improved initialization of per-device limits to ensure correct setup before use.

@hami-robot

hami-robot Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: om7057
Once this PR has been reviewed and has the lgtm label, please assign archlitchi for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@hami-robot

hami-robot Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Welcome @om7057! It looks like this is your first PR to Project-HAMi/HAMi-core 🎉

@hami-robot hami-robot Bot added the size/S label Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2917e2c0-219d-4fe3-863a-1a4c9f243f8d

📥 Commits

Reviewing files that changed from the base of the PR and between 3b56dde and 4f4b4b4.

📒 Files selected for processing (1)
  • src/multiprocess/multiprocess_memory_limit.c

📝 Walkthrough

Walkthrough

Shared-region memory and SM limit arrays now use atomic fields. Initialization and runtime access use atomic operations. Limit APIs and cuDeviceTotalMem_v2 now validate device IDs before accessing limit data.

Changes

Shared-region limit synchronization

Layer / File(s) Summary
Atomic limit array contract
src/multiprocess/multiprocess_memory_limit.h
shared_region_t declares limit and sm_limit as atomic uint64_t arrays.
Atomic initialization and accessors
src/multiprocess/multiprocess_memory_limit.c
Initialization stores computed limits into shared atomic fields. Accessors use acquire loads and release stores. Limit APIs return error values for invalid device IDs.
Device memory query validation
src/cuda/device.c
cuDeviceTotalMem_v2 validates device IDs and returns CUDA_ERROR_INVALID_DEVICE for invalid IDs.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: chaunceyjiang, archlitchi

Poem

A rabbit stores each limit tight,
With atomic hops from left to right.
Acquire reads and release writes,
Invalid devices lose their rights.
Shared values stay in sync tonight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: using atomic loads and stores for device limits in the shared region.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jul 28, 2026
@om7057
om7057 force-pushed the fix/atomic-shared-limit-fields branch from 453ca60 to b0ea170 Compare July 28, 2026 15:04
om7057 added a commit to om7057/HAMi that referenced this pull request Jul 28, 2026
DeviceMemoryLimit, SetDeviceMemoryLimit and SetDeviceSmLimit in
pkg/monitor/nvidia/{v0,v1} read and write limit/smLimit in
sharedRegionT with plain Go assignments. These are the same
shared-memory words HAMi-core's shared_region_t.limit/sm_limit map
onto, and the C side already guarantees every other field in that
struct is atomic (see the companion fix in
Project-HAMi/HAMi-core#238). These two were the only fields left as
plain reads/writes on either side.

Switches all three methods to sync/atomic.LoadUint64/StoreUint64 on
the same words. No change to sharedRegionT's field types or layout,
so the wire format is unaffected. Existing unit tests in
pkg/monitor/nvidia/{v0,v1} pass unmodified under -race.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/multiprocess/multiprocess_memory_limit.c`:
- Line 1255: Update the device-limit accessor functions around the
invalid-device checks at lines 1238, 1250, 1258, and 1268 to return their
documented error or sentinel immediately when dev is negative or dev >=
CUDA_DEVICE_MAX_COUNT. Ensure each invalid path exits before accessing
shared_region->sm_limit[dev] or limit[dev], while preserving existing logging
and valid-device behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 788ec018-c7d0-4e90-8d55-30d8890677cf

📥 Commits

Reviewing files that changed from the base of the PR and between 52f33fc and 453ca60.

📒 Files selected for processing (2)
  • src/multiprocess/multiprocess_memory_limit.c
  • src/multiprocess/multiprocess_memory_limit.h

Comment thread src/multiprocess/multiprocess_memory_limit.c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/multiprocess/multiprocess_memory_limit.c`:
- Around line 1124-1127: Update the shared-region initialization around
do_init_device_memory_limits and do_init_device_sm_limits so region->limit and
region->sm_limit are written using atomic stores rather than casts to uint64_t*.
Preserve the existing helpers for local copies, and add or use atomic-capable
initialization helpers for the shared arrays.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 62d96b9e-b29a-4d96-96a1-710756f392dd

📥 Commits

Reviewing files that changed from the base of the PR and between 453ca60 and b0ea170.

📒 Files selected for processing (2)
  • src/multiprocess/multiprocess_memory_limit.c
  • src/multiprocess/multiprocess_memory_limit.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/multiprocess/multiprocess_memory_limit.h

Comment thread src/multiprocess/multiprocess_memory_limit.c Outdated
@om7057
om7057 force-pushed the fix/atomic-shared-limit-fields branch from b0ea170 to b83f7c7 Compare July 28, 2026 15:09
om7057 added a commit to om7057/HAMi that referenced this pull request Jul 28, 2026
DeviceMemoryLimit, SetDeviceMemoryLimit and SetDeviceSmLimit in
pkg/monitor/nvidia/{v0,v1} read and write limit/smLimit in
sharedRegionT with plain Go assignments. These are the same
shared-memory words HAMi-core's shared_region_t.limit/sm_limit map
onto, and the C side already guarantees every other field in that
struct is atomic (see the companion fix in
Project-HAMi/HAMi-core#238). These two were the only fields left as
plain reads/writes on either side.

Switches all three methods to sync/atomic.LoadUint64/StoreUint64 on
the same words. No change to sharedRegionT's field types or layout,
so the wire format is unaffected. Existing unit tests in
pkg/monitor/nvidia/{v0,v1} pass unmodified under -race.

Signed-off-by: Om Kulkarni <kulkarniom7057@gmail.com>
@om7057

om7057 commented Jul 28, 2026

Copy link
Copy Markdown
Author

Fixed, added early returns for the illegal-dev path in all four functions.

context_size, hostpid, proc_num, status and last_kernel_time were all
made _Atomic in 24a0f49, but limit and sm_limit were missed. Both are
read on the CUDA allocation hot path (get_current_device_memory_limit,
called from every hooked cuMemAlloc) and are exposed through public,
tested setters on both this side and the Go monitor side, so a plain
uint64_t here does not match the atomicity already guaranteed for the
rest of shared_region_t.

_Atomic uint64_t has the same size and representation as uint64_t on
the supported targets, so this does not change the struct layout: I
verified sizeof(shared_region_t) and offsetof() for every field are
identical before and after (2008952 / 1600 / 1728 / 1856 / 8), so the
on-disk cudevshr.cache format and the Go-side mirror struct are
unaffected.

The two callers that fill the arrays at shared-region creation
(do_init_device_memory_limits/do_init_device_sm_limits) still take a
plain uint64_t*, so a cast is added there; that path already runs
under the region's file lock before the region is published, so no
new synchronization is needed there.

Also fixes four sites in the same functions where an illegal dev
falls through to the array access instead of returning after the
LOG_ERROR (set_current_device_sm_limit_scale,
get_current_device_sm_limit, set_current_device_memory_limit,
get_current_device_memory_limit). The int-returning functions now
return -1 on an out-of-range dev, matching the -1 convention already
used elsewhere in this file. get_current_device_memory_limit returns
0 instead, since it feeds the allocation limit check directly and
UINT64_MAX would silently remove the limit for an invalid dev where 0
fails closed.

do_init_device_memory_limits/do_init_device_sm_limits still fill
their array via plain non-atomic stores (arr[i] = ...), so writing
region->limit/sm_limit through them via a uint64_t* cast performed
a non-atomic store into an _Atomic-qualified object, which is a type
violation independent of whether it happens to be safe on current
targets. Reusing the helper into a local uint64_t array and copying
that into the region with atomic_store_explicit avoids the cast
while keeping do_init_device_memory_limits/do_init_device_sm_limits
themselves unchanged, so nothing else that calls them (the
already-initialized comparison branch just below) is affected.
memory_order_relaxed is enough here: the atomic_thread_fence(release)
a few lines down already publishes everything written before it, the
same reasoning already used for sm_init_flag/utilization_switch/
recent_kernel/proc_num in this same block.
Signed-off-by: Om Kulkarni <kulkarniom7057@gmail.com>
@om7057
om7057 force-pushed the fix/atomic-shared-limit-fields branch from b83f7c7 to 0435096 Compare July 28, 2026 15:19
@hami-robot hami-robot Bot added size/M and removed size/S labels Jul 28, 2026
@om7057

om7057 commented Jul 28, 2026

Copy link
Copy Markdown
Author

Fixed. Kept do_init_device_memory_limits/do_init_device_sm_limits as-is for the local-array case (used by the already-initialized comparison branch below), and now compute into a local array then copy into the region with atomic_store_explicit, so nothing writes through a plain uint64_t* into the _Atomic fields anymore.

@om7057

om7057 commented Jul 28, 2026

Copy link
Copy Markdown
Author

@archlitchi @chaunceyjiang could you please check this PR?

@iemAnshuman

Copy link
Copy Markdown
Contributor

@om7057 these returns fix more than the atomics gap: before, an illegal dev logged and then indexed the 16 entry arrays anyway, so this also closes an OOB, worth a body sentence since the generated summary says input validation and return behavior are unchanged. also cuDeviceTotalMem_v2() just does *bytes = get_current_device_memory_limt(dev), so the new 0 sentinel comes back as zero bytes with CUDA_SUCCESS for a bogus dev, memory safe but not error equivalent

@iemAnshuman

Copy link
Copy Markdown
Contributor

also #232 rewrites and renames the same 'set_current_device_sm_limit_scale' hunk with the identical return -1 guard, and it already has lgtm, so whichever lands second takes a conflict, worth agreeing order with @KaminariOS. also #239's abi_check asserts these exact sizeof/offsetof values, running it against this head and pasting the output would make the layout section machine checked

@archlitchi

Copy link
Copy Markdown
Member

CC @maverick123123

@maverick123123

Copy link
Copy Markdown
Contributor

These two fields (limit / sm_limit) currently have only one writer — they're set once at shared-region init time under the file lock, before the region is published. No other component dynamically updates them within a single process lifecycle, so the lack of atomics hasn't been an observable bug.

That said, making them _Atomic for consistency with the rest of shared_region_t doesn't hurt, and it closes the gap in case a future writer is introduced. No objection from my side.

… dev

get_current_device_memory_limit() returns 0 for an out-of-range dev to
avoid the out-of-bounds array read this PR is fixing elsewhere, but 0
is also the existing sentinel this codebase uses for "no memory limit
configured" (do_init_device_memory_limits leaves an entry at 0 when
neither the per-device nor fallback env var is set, and oom_check,
cuMemGetInfo_v2, and the NVML hook.c all treat limit==0 as unlimited).
cuDeviceTotalMem_v2 forwarded that value straight through as *bytes
with CUDA_SUCCESS, so an out-of-range dev looked like a real device
reporting zero total memory instead of an invalid-device error.

Check the range before calling get_current_device_memory_limit and
return CUDA_ERROR_INVALID_DEVICE directly, matching the real driver's
behavior for an invalid device ordinal. This only touches the one
call site that is CUDA-API-facing; oom_check, cuMemGetInfo_v2, and the
NVML hook already fail closed in the only case that matters in
practice, an unconfigured real device reads as unlimited by design,
same as before this PR.

Signed-off-by: Om Kulkarni <kulkarniom7057@gmail.com>
Signed-off-by: om7057 <kulkarniom7057@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Aug 6, 2026
@om7057

om7057 commented Aug 6, 2026

Copy link
Copy Markdown
Author

@iemAnshuman fixed all four:

  1. Added a bullet to the PR body describing the illegal-dev return fix, and a note that the CodeRabbit auto-summary's "input validation and return behavior unchanged" line is stale as of this fix, return behavior does change for that case now, on purpose.
  2. cuDeviceTotalMem_v2 now returns CUDA_ERROR_INVALID_DEVICE for an out-of-range dev instead of forwarding get_current_device_memory_limit's 0 return as *bytes with CUDA_SUCCESS. Separately: the commit message's claim that 0 "fails closed" was wrong, I checked and oom_check, cuMemGetInfo_v2, and the NVML hook all treat limit == 0 as "no limit configured" by existing design (do_init_device_memory_limits leaves entries at 0 when no env var is set), so 0 from an illegal dev reads as more permissive there, not less. Didn't touch those three since disambiguating "illegal dev" from "real unlimited config" needs a bigger sentinel change than this PR's scope, and dev reaching that deep is already validated upstream in every real call path. Corrected the claim in the PR body instead of leaving it.
  3. Confirmed chore: remove sm scale from sm limit setter #232 touches the identical lines (rename plus the same return -1 guard). No preference on order, happy to rebase on whichever lands second.
  4. abi_check against this head (3b56dde):
sizeof(shared_region_t)  = 2008952    (expected 2008952) OK
offsetof(limit)          = 1600       (expected 1600) OK
offsetof(sm_limit)       = 1728       (expected 1728) OK
offsetof(procs)          = 1856       (expected 1856) OK
sizeof(limit[0])         = 8          (expected 8) OK

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/multiprocess/multiprocess_memory_limit.c`:
- Around line 1248-1251: Move the dev bounds validation in the relevant
initialization function before the sm_init_flag fast path, so invalid IDs log
the error and return -1 even when initialization is complete. Preserve the
existing return 0 behavior for valid devices when shared_region->sm_init_flag is
1.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f4d63c2a-1d81-4a28-903d-240c1c681080

📥 Commits

Reviewing files that changed from the base of the PR and between 52f33fc and 3b56dde.

📒 Files selected for processing (3)
  • src/cuda/device.c
  • src/multiprocess/multiprocess_memory_limit.c
  • src/multiprocess/multiprocess_memory_limit.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/multiprocess/multiprocess_memory_limit.h

Comment thread src/multiprocess/multiprocess_memory_limit.c Outdated
CodeRabbit caught this on PR Project-HAMi#238: set_current_device_sm_limit_scale
checked sm_init_flag==1 and returned 0 before checking dev's bounds,
so an out-of-range dev with sm_init_flag already set to 1 reported
success instead of the -1 this same PR just added for every other
illegal-dev case in this file. Swapped the order so the bounds check
runs first regardless of sm_init_flag's state.

Also fixed pre-existing spacing on the lines this moves past
(sm_init_flag==1, the LOG_INFO argument list, the sm_limit[dev]
assignment), since they were touched by the reorder anyway.

Signed-off-by: Om Kulkarni <kulkarniom7057@gmail.com>
Signed-off-by: om7057 <kulkarniom7057@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants