Skip to content

feat(agent): track POSIX CUDA VMM state - #205

Closed
galletas1712 wants to merge 1 commit into
feat/cuinterpose-coordinatorfrom
feat/cuinterpose-posix-tracking
Closed

galletas1712 wants to merge 1 commit into
feat/cuinterpose-coordinatorfrom
feat/cuinterpose-posix-tracking

Conversation

@galletas1712

@galletas1712 galletas1712 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Allocation tracking without the lifecycle. Doc: docs/reference/cuinterpose.md §3.1 and §6 (creator anchor, one driver reference per allocation, access merging, lock order).

  • table.c: open-addressing hash tables (logical handle, allocation id) and a sorted range index for mappings, both shrinking as well as growing.
  • interpose.c: exact POSIX descriptor handle type tracked, everything else passes through (one log line for FABRIC); logical handles (0xd94d tag); one driver handle per allocation per process (imports and cuMemRetainAllocationHandle alias it); cuMemMap refuses overlaps, cuMemUnmap is range-based, cuMemSetAccess merges per location, refuses partial overlaps, and marks a mapping unknown after a driver failure so prepare fails closed; raw imports counted; records freed with the last handle and mapping.
  • export_cache.c: the one real export per allocation, served to peers by the listener with only the cache lock (no driver call, no state lock); per-entry drain on drop; quiesce/resume for the lifecycle.
  • Listener: per-connection threads, accept4 retry with backoff, chmod 0600 on the socket path, 10 s socket timeouts, fork handlers.
  • Multicast seam: bind wrappers translate tracked member handles so NVLS workloads keep running; multicast tracking arrives in PR 6.
  • Tests: state_preload_test (alias collapse, range unmap, access merging and PROT_NONE, tickets, imports from a forked child through the listener, raw import counting, churn leaving the tables empty, descriptor exhaustion), table_unit_test, export cache tests in proto_unit_test.

Origin

Re-cut of the tracking parts of #152 and #166.

#152 feat(agent): checkpoint POSIX CUDA VMM state (verbatim, bot blocks removed)

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
#166 fix(agent): collapse duplicate CUDA handle references (verbatim, bot blocks removed)

Summary

  • collapse redundant driver references acquired by cuMemRetainAllocationHandle when a tracked allocation already has a live backing handle
  • apply the same one-backing-reference invariant to repeated tracked POSIX imports
  • preserve distinct logical handles while releasing the redundant physical driver reference immediately
  • add a fake-CUDA regression that verifies alias release keeps the backing alive and final release drops it exactly once

Validation

  • make -C agent/cmd/cuinterpose clean test in the CUDA test image
  • go test ./agent/internal/criu ./agent/internal/cuda -count=1
  • live 1 GiB A/B allocation test:
    • before: 1 GiB remained after releasing all three logical aliases
    • after: residual allocation was 0 bytes
  • GLM-5.2-NVFP4 SGLang TP8/EP8, context length 32768, checkpoint captured successfully with all eight GMS devices saving 60,129,542,144 bytes
  • two restored engines completed CUDA restore; active inference returned 42
  • after active-engine failure, the shadow acquired the lock in 135 ms, registered in 4.70 s, and post-failover inference returned 42
  • after active teardown, GPU memory returned exactly to the single-engine baseline

Review threads carried

No review threads on #152 or #166. The #78 threads on interpose.c are fixed here; see PR 2's table.

Validation

  • Fake-driver suites (GoogleTest, AddressSanitizer + UndefinedBehaviorSanitizer) run in the agent image build against CUDA 13.1 headers at every layer of the stack; at the top: proto 22, table 3, forward 12, coordinator 12, state 14, lifecycle 4, multicast 6, no sanitizer reports.
  • go test ./... in api, operator, agent; make lint, make helm-lint, make verify-license-headers.
  • Agent image built from the top of the stack and deployed on nscale-dev (B200, kernel driver 595.58.03) with this chart.
  • GPU tests (two B200s, cuda-checkpoint --launch-job): POSIX round trip with seeded 1 GiB carriers per rank, multicast round trip with PyTorch's multimem all-reduce and a cuMulticastBindAddr rebind, refusal while a raw import is alive: 3 passed. Host-carrier restore phase 87 to 108 GB/s aggregate over two GPUs (pinned-copy baseline 55 GB/s per GPU).
  • End to end: vLLM 0.27.1 AsyncLLM, Qwen3-0.6B, tensor parallel 2, FlashInfer TRT-LLM attention and fused allreduce (trtllm backend), PodSnapshot of a Deployment shaped with podcontract.ShapeCuinterposeCapture, then restore: checkpoint 52 s (CRIU dump 49 s, cuinterpose prepare 0.43 s; 4 CUDA processes, 1052 records, 382 host carriers, 2.08 GB); restore 5.7 s (cuinterpose 0.35 s: carriers 0.05 s, unicast 0.09 s, multicast 0.18 s); the restored replica answers coherently ("The capital of Italy is Rome").

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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

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
galletas1712 force-pushed the feat/cuinterpose-posix-tracking branch from 8a19292 to b05e96b Compare September 4, 2026 17:52
@galletas1712

Copy link
Copy Markdown
Contributor Author

Superseded by the nine-PR re-cut (packaging → delivery → agent → forwarding → coordinator → tracking → lifecycle → multicast → GPU tests): Go and packaging split by component, tests trimmed, docs held back. The replacement PRs are listed on the packaging PR.

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