Skip to content

feat(agent): checkpoint POSIX CUDA VMM state - #152

Closed
galletas1712 wants to merge 1 commit into
feat/snapshot-cuda-vmm-coordinatorfrom
feat/snapshot-cuda-vmm-posix-checkpoint
Closed

galletas1712 wants to merge 1 commit into
feat/snapshot-cuda-vmm-coordinatorfrom
feat/snapshot-cuda-vmm-posix-checkpoint

Conversation

@galletas1712

@galletas1712 galletas1712 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add POSIX-shareable allocation, logical-handle, mapping, access-range, and ticket-import tracking to interpose.c and posix.c
  • expose the control endpoint consumed by the coordinator added in feat(agent): coordinate CUDA interposer lifecycle #155
  • implement prepare and restore behavior around native CUDA checkpointing
  • record access descriptors for every fully covered mapping and fail closed on partial overlaps
  • keep multicast entirely outside this layer

This is layer 4 of stack #156. Coordinator/protocol and Go orchestration are isolated in #155.

Validation

  • all Go tests
  • pinned CUDA-devel image builds the shim and static coordinator
  • POSIX checkpoint integration tests
  • shim GLIBC requirement is at most 2.34
  • no multicast exports in this layer

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • main

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 69fc8363-8371-414b-a4e1-7a0a8548b977

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@galletas1712
galletas1712 force-pushed the feat/snapshot-cuda-vmm-posix-checkpoint branch from 56520f3 to b5d021d Compare September 1, 2026 06:23
@galletas1712
galletas1712 force-pushed the feat/snapshot-cuda-vmm-posix-checkpoint branch from b5d021d to e5a5144 Compare September 1, 2026 07:45
@galletas1712
galletas1712 force-pushed the feat/snapshot-cuda-vmm-posix-checkpoint branch from e5a5144 to cd4e12c Compare September 1, 2026 07:58
@galletas1712
galletas1712 changed the base branch from feat/snapshot-cuda-vmm-posix-native to feat/snapshot-cuda-vmm-coordinator September 1, 2026 07:59
@galletas1712
galletas1712 force-pushed the feat/snapshot-cuda-vmm-posix-checkpoint branch from cd4e12c to 3428083 Compare September 1, 2026 08:20
Track POSIX-shareable CUDA VMM allocations, mappings, access ranges, and ticket imports. Add the static coordinator and order prepare and restore around native CUDA checkpointing.

Keep multicast outside this layer and preserve the coordinator across CRIU mount replacement through a pre-opened file descriptor.

Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
@galletas1712

Copy link
Copy Markdown
Contributor Author

Superseded by #217 (POSIX tracking) and #218 (lifecycle). The re-cut stack (#212#220) supersedes this PR. Its body is quoted verbatim in the replacement's Origin section, and each review thread here has a row in the replacement's "Review threads carried" table with what was done about it.

galletas1712 added a commit that referenced this pull request Sep 11, 2026
Turn the forwarding shim into one that knows which CUDA allocations are shared
between processes. Allocations created with exactly the POSIX file-descriptor
handle type are tracked behind logical handles; every other handle type passes
through untouched. Exports return sealed memfd tickets instead of the driver's
descriptor; an import of a ticket asks the creator process for the real
descriptor over its control socket. The creator answers from an export cache
that holds the one real descriptor per allocation, so its listener never calls
into the driver and never takes the shim's main lock, and a creator busy inside
a long collective call cannot stall its peers.

Bookkeeping lives in hash tables plus a sorted range index that shrink as well
as grow; records are freed when no handle and no mapping remains, so a server
that maps and unmaps for days does not accumulate dead entries or slow down.
One driver handle backs each allocation per process: repeated imports and
cuMemRetainAllocationHandle alias it, and the last logical handle releases it.
A ticket lives as long as the creator holds a handle or mapping, so freeing
memory frees it.

cuMemSetAccess keeps the union of access per location so one call per peer GPU
survives; a call that partly overlaps a tracked mapping, or would grant more
than 32 locations, is refused before the driver sees it. cuMemMap refuses
overlapping a tracked mapping and cuMemUnmap may cover several whole mappings
but not cut through one. Imports of descriptors that are not tickets are
counted so the coordinator can refuse to checkpoint while one is alive. Fork
children drop inherited records and register on their first CUDA activity,
including through the driver's own symbol resolution, so a child that
initializes CUDA without VMM calls is still visible.

The multicast wrappers translate tracked member handles for the bind calls and
otherwise still forward. The shim does not yet answer the coordinator's
lifecycle requests; that is the next change.

Compared with the earlier tracking layers (#152, #166): the export happens once
at ticket time instead of on demand under the main lock; access sets are merged
per location instead of overwritten by the last call; lists that never shrank
are replaced by tables that do; the CUDA context is captured at creation rather
than only at export; unmap and map are range-checked; the listener retries
transient accept errors; the socket path is chmod 0600; NULL and non-zero-flag
exports return CUDA_ERROR_INVALID_VALUE as the driver does. Fourteen state
tests, table and export-cache unit tests, and the earlier suites run under
AddressSanitizer and UndefinedBehaviorSanitizer during the agent image build.


Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
galletas1712 added a commit that referenced this pull request Sep 11, 2026
Turn the forwarding shim into one that knows which CUDA allocations are shared
between processes. Allocations created with exactly the POSIX file-descriptor
handle type are tracked behind logical handles; every other handle type passes
through untouched. Exports return sealed memfd tickets instead of the driver's
descriptor; an import of a ticket asks the creator process for the real
descriptor over its control socket. The creator answers from an export cache
that holds the one real descriptor per allocation, so its listener never calls
into the driver and never takes the shim's main lock, and a creator busy inside
a long collective call cannot stall its peers.

Bookkeeping lives in hash tables plus a sorted range index that shrink as well
as grow; records are freed when no handle and no mapping remains, so a server
that maps and unmaps for days does not accumulate dead entries or slow down.
One driver handle backs each allocation per process: repeated imports and
cuMemRetainAllocationHandle alias it, and the last logical handle releases it.
A ticket lives as long as the creator holds a handle or mapping, so freeing
memory frees it.

cuMemSetAccess keeps the union of access per location so one call per peer GPU
survives; a call that partly overlaps a tracked mapping, or would grant more
than 32 locations, is refused before the driver sees it. cuMemMap refuses
overlapping a tracked mapping and cuMemUnmap may cover several whole mappings
but not cut through one. Imports of descriptors that are not tickets are
counted so the coordinator can refuse to checkpoint while one is alive. Fork
children drop inherited records and register on their first CUDA activity,
including through the driver's own symbol resolution, so a child that
initializes CUDA without VMM calls is still visible.

The multicast wrappers translate tracked member handles for the bind calls and
otherwise still forward. The shim does not yet answer the coordinator's
lifecycle requests; that is the next change.

Compared with the earlier tracking layers (#152, #166): the export happens once
at ticket time instead of on demand under the main lock; access sets are merged
per location instead of overwritten by the last call; lists that never shrank
are replaced by tables that do; the CUDA context is captured at creation rather
than only at export; unmap and map are range-checked; the listener retries
transient accept errors; the socket path is chmod 0600; NULL and non-zero-flag
exports return CUDA_ERROR_INVALID_VALUE as the driver does. Fourteen state
tests, table and export-cache unit tests, and the earlier suites run under
AddressSanitizer and UndefinedBehaviorSanitizer during the agent image build.

Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
galletas1712 added a commit that referenced this pull request Sep 11, 2026
Turn the forwarding shim into one that knows which CUDA allocations are shared
between processes. Allocations created with exactly the POSIX file-descriptor
handle type are tracked behind logical handles; every other handle type passes
through untouched. Exports return sealed memfd tickets instead of the driver's
descriptor; an import of a ticket asks the creator process for the real
descriptor over its control socket. The creator answers from an export cache
that holds the one real descriptor per allocation, so its listener never calls
into the driver and never takes the shim's main lock, and a creator busy inside
a long collective call cannot stall its peers.

Bookkeeping lives in hash tables plus a sorted range index that shrink as well
as grow; records are freed when no handle and no mapping remains, so a server
that maps and unmaps for days does not accumulate dead entries or slow down.
One driver handle backs each allocation per process: repeated imports and
cuMemRetainAllocationHandle alias it, and the last logical handle releases it.
A ticket lives as long as the creator holds a handle or mapping, so freeing
memory frees it.

cuMemSetAccess keeps the union of access per location so one call per peer GPU
survives; a call that partly overlaps a tracked mapping, or would grant more
than 32 locations, is refused before the driver sees it. cuMemMap refuses
overlapping a tracked mapping and cuMemUnmap may cover several whole mappings
but not cut through one. Imports of descriptors that are not tickets are
counted so the coordinator can refuse to checkpoint while one is alive. Fork
children drop inherited records and register on their first CUDA activity,
including through the driver's own symbol resolution, so a child that
initializes CUDA without VMM calls is still visible.

The multicast wrappers translate tracked member handles for the bind calls and
otherwise still forward. The shim does not yet answer the coordinator's
lifecycle requests; that is the next change.

Compared with the earlier tracking layers (#152, #166): the export happens once
at ticket time instead of on demand under the main lock; access sets are merged
per location instead of overwritten by the last call; lists that never shrank
are replaced by tables that do; the CUDA context is captured at creation rather
than only at export; unmap and map are range-checked; the listener retries
transient accept errors; the socket path is chmod 0600; NULL and non-zero-flag
exports return CUDA_ERROR_INVALID_VALUE as the driver does. Fourteen state
tests, table and export-cache unit tests, and the earlier suites run under
AddressSanitizer and UndefinedBehaviorSanitizer during the agent image build.

Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
galletas1712 added a commit that referenced this pull request Sep 11, 2026
Turn the forwarding shim into one that knows which CUDA allocations are shared
between processes. Allocations created with exactly the POSIX file-descriptor
handle type are tracked behind logical handles; every other handle type passes
through untouched. Exports return sealed memfd tickets instead of the driver's
descriptor; an import of a ticket asks the creator process for the real
descriptor over its control socket. The creator answers from an export cache
that holds the one real descriptor per allocation, so its listener never calls
into the driver and never takes the shim's main lock, and a creator busy inside
a long collective call cannot stall its peers.

Bookkeeping lives in hash tables plus a sorted range index that shrink as well
as grow; records are freed when no handle and no mapping remains, so a server
that maps and unmaps for days does not accumulate dead entries or slow down.
One driver handle backs each allocation per process: repeated imports and
cuMemRetainAllocationHandle alias it, and the last logical handle releases it.
A ticket lives as long as the creator holds a handle or mapping, so freeing
memory frees it.

cuMemSetAccess keeps the union of access per location so one call per peer GPU
survives; a call that partly overlaps a tracked mapping, or would grant more
than 32 locations, is refused before the driver sees it. cuMemMap refuses
overlapping a tracked mapping and cuMemUnmap may cover several whole mappings
but not cut through one. Imports of descriptors that are not tickets are
counted so the coordinator can refuse to checkpoint while one is alive. Fork
children drop inherited records and register on their first CUDA activity,
including through the driver's own symbol resolution, so a child that
initializes CUDA without VMM calls is still visible.

The multicast wrappers translate tracked member handles for the bind calls and
otherwise still forward. The shim does not yet answer the coordinator's
lifecycle requests; that is the next change.

Compared with the earlier tracking layers (#152, #166): the export happens once
at ticket time instead of on demand under the main lock; access sets are merged
per location instead of overwritten by the last call; lists that never shrank
are replaced by tables that do; the CUDA context is captured at creation rather
than only at export; unmap and map are range-checked; the listener retries
transient accept errors; the socket path is chmod 0600; NULL and non-zero-flag
exports return CUDA_ERROR_INVALID_VALUE as the driver does. Fourteen state
tests, table and export-cache unit tests, and the earlier suites run under
AddressSanitizer and UndefinedBehaviorSanitizer during the agent image build.

Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
galletas1712 added a commit that referenced this pull request Sep 11, 2026
Turn the forwarding shim into one that knows which CUDA allocations are shared
between processes. Allocations created with exactly the POSIX file-descriptor
handle type are tracked behind logical handles; every other handle type passes
through untouched. Exports return sealed memfd tickets instead of the driver's
descriptor; an import of a ticket asks the creator process for the real
descriptor over its control socket. The creator answers from an export cache
that holds the one real descriptor per allocation, so its listener never calls
into the driver and never takes the shim's main lock, and a creator busy inside
a long collective call cannot stall its peers.

Bookkeeping lives in hash tables plus a sorted range index that shrink as well
as grow; records are freed when no handle and no mapping remains, so a server
that maps and unmaps for days does not accumulate dead entries or slow down.
One driver handle backs each allocation per process: repeated imports and
cuMemRetainAllocationHandle alias it, and the last logical handle releases it.
A ticket lives as long as the creator holds a handle or mapping, so freeing
memory frees it.

cuMemSetAccess keeps the union of access per location so one call per peer GPU
survives; a call that partly overlaps a tracked mapping, or would grant more
than 32 locations, is refused before the driver sees it. cuMemMap refuses
overlapping a tracked mapping and cuMemUnmap may cover several whole mappings
but not cut through one. Imports of descriptors that are not tickets are
counted so the coordinator can refuse to checkpoint while one is alive. Fork
children drop inherited records and register on their first CUDA activity,
including through the driver's own symbol resolution, so a child that
initializes CUDA without VMM calls is still visible.

The multicast wrappers translate tracked member handles for the bind calls and
otherwise still forward. The shim does not yet answer the coordinator's
lifecycle requests; that is the next change.

Compared with the earlier tracking layers (#152, #166): the export happens once
at ticket time instead of on demand under the main lock; access sets are merged
per location instead of overwritten by the last call; lists that never shrank
are replaced by tables that do; the CUDA context is captured at creation rather
than only at export; unmap and map are range-checked; the listener retries
transient accept errors; the socket path is chmod 0600; NULL and non-zero-flag
exports return CUDA_ERROR_INVALID_VALUE as the driver does. Fourteen state
tests, table and export-cache unit tests, and the earlier suites run under
AddressSanitizer and UndefinedBehaviorSanitizer during the agent image build.

Signed-off-by: Schwinn Saereesitthipitak <schwinns@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant