Skip to content

Design: safe cross-process admission for concurrent physical KV growth #467

Description

@shipiyouniao

Summary

Concurrent kvcached instances on the same physical GPU need cross-process admission for physical KV growth.

#418 makes VMM allocation and mapping failures transactional, and #453 converts physical-capacity exhaustion into a scheduling miss. Those changes are necessary, but they do not arbitrate capacity between processes. Under sustained colocated pressure, both instances can observe the same free-memory snapshot, admit overlapping growth, and race in cuMemCreate().

This is no longer only a code-review concern. We can reproduce the race reliably with two TP=4 serving instances under high concurrency. Without cross-process admission, the CUDA driver rejects physical-page allocations with OOM. Transactional rollback prevents a fatal process abort, but repeated OOM attempts become allocation misses, request failures, and severe asymmetry.

A similar race was discussed as a possibility in #221, but that report stopped reproducing after its TP socket collision was fixed. The reproduction here uses separated instance IPC and current transaction handling, so it is a distinct remaining problem.

Evidence

Transactional rollback without cross-process admission

Two high-pressure rounds were run against two colocated instances:

Round Instance A Instance B
1 411 succeeded, 0 failed 55 succeeded, 356 failed
2 115 succeeded, 296 failed 411 succeeded, 0 failed

The two instances logged 1,932 and 2,552 raw CUDA physical-page OOM failures respectively. There was no process restart or fatal CUDA error because the failures were rolled back, but request-level correctness and fairness were not acceptable.

This is the gap between rollback and admission:

  1. both processes read sufficient free memory;
  2. both start physical growth;
  3. the combined request exceeds real GPU capacity;
  4. the CUDA driver rejects the later cuMemCreate();
  5. fix: make KV tensor VMM batches transactional #418 rolls back the partial transaction;
  6. fix(vllm): report an exhausted KV pool as a scheduling miss, not a crash #453 turns exhaustion into an allocation miss;
  7. sustained retries can still starve one instance and fail requests.

Admission designs evaluated

1. Full physical-growth transaction lock (#455)

The original #455 design:

  • keys a process-shared file lock by physical GPU;
  • refreshes mem_get_info while holding the lock;
  • checks headroom against the current snapshot;
  • holds the lock across the complete physical-growth mapping transaction.

Its focused reproducer changes the uncontrolled race into one successful growth and one recoverable capacity_exhausted result, with both processes exiting normally.

Among the designs tested so far, this remains the strongest correctness boundary and the best current candidate. It makes the capacity decision and the corresponding physical growth one transaction. However, its cost concern is valid: file-lock acquisition, device selection, memory query, and a relatively broad critical section are added to successful growth.

2. Smaller per-page transactions and fairness retries

A version that reduced growth to one page per transaction and retried misses removed raw CUDA OOM and completed all requests, but produced stable starvation:

Metric Baseline Candidate
Aggregate effective throughput 3,246 tok/s 1,574-1,600 tok/s
Change - -50.7% to -51.5%
Slow-side median TTFT about 3 s about 70 s

Mutual exclusion alone did not provide fair admission. One process repeatedly reacquired capacity while the other accumulated retries.

3. Worker-owned all-TP headroom admission

We also evaluated worker-owned admission where each physical growth queried all TP workers and rejected the batch if any rank lacked headroom. It avoided OOM and permanent hangs, and both instances completed 390/390 requests, but it moved a distributed control round trip into the allocation hot path:

Metric Change
Elapsed time +40%
Output throughput -28.5%
Mean TTFT +63%
Headroom rejections 2,182 / 2,176
Allocation misses 1,102 / 1,096

Removing that hot-path admission restored average throughput to within about 1.4% in the same workload, which isolates the regression to the admission path rather than VMM rollback itself. More detail is available in #416.

4. No admission, rely only on the CUDA driver and rollback

This has the lowest uncontended overhead, but the first result above shows that it is not sufficient under sustained colocated pressure. The CUDA driver is a final atomic gate, not a fair or efficient cross-process allocator.

Current conclusion

#418 and #453 remain required, but they do not replace cross-process capacity admission.

All alternatives tested after the original #455 shape either:

  • allow repeated physical allocation OOM and request failures;
  • introduce a distributed slow path on every growth attempt; or
  • avoid OOM at the cost of severe starvation and roughly 50% aggregate throughput loss.

For that reason, #455 has been reopened as a draft. Its original transaction boundary is currently the best candidate among the tested designs, but it should not merge until its uncontended cost and fairness under sustained contention are addressed.

A promising refinement is to keep a process-shared transaction boundary while shortening the critical section around physical capacity reservation, rather than holding it across Python IPC, TP response waits, or synchronization. That needs an explicit ownership model for reserved bytes or handles so the lock cannot be released before the capacity decision becomes real.

Design questions

  1. Can physical handles be reserved under a short process-shared lock, then mapped outside the lock without reopening the TOCTOU race?
  2. Should admission use bounded chunks or tickets to prevent one instance from repeatedly acquiring all newly available capacity?
  3. What is the right cross-container coordination primitive and deployment contract?
  4. How should admission expose contention, wait time, failed reservations, and fairness without placing worker RPC in the allocation hot path?
  5. Can the uncontended path remain close to the current rollback-only baseline while preserving a coherent capacity snapshot?

Acceptance criteria

A replacement for the current draft should demonstrate all of the following with at least two colocated instances:

  • no raw physical-page OOM during expected capacity contention;
  • no process abort, illegal memory access, or partial VMM state;
  • 100% valid request completion unless explicitly cancelled;
  • no instance remains without progress while another repeatedly grows;
  • bounded admission wait and retry counts;
  • no material aggregate throughput or TTFT regression in the uncontended case;
  • a documented shared coordination contract for multi-container deployments.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions