[Bugfix] Fail fast with a clear error when CPU offload region exceeds available space - #50358
Conversation
|
@orozery PTAL |
orozery
left a comment
There was a problem hiding this comment.
Thanks @Alex-ai-future !
Please disregard my previous posts (accidently posted by my Claude agent).
| # arbitrates atomically below, the loser lands in the joiner path | ||
| # where this guard already short-circuits, so the guard is not a | ||
| # TOCTOU risk on its own. | ||
| if not os.path.exists(self.mmap_path): |
There was a problem hiding this comment.
I don't understand why we need this check
There was a problem hiding this comment.
This judgment is for semantic consistency. Check should be a behavior on the creator path. Furthermore, I think it can be placed if check is after os.open(self.mmap_path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o600)
do you agree that?
There was a problem hiding this comment.
So something like that, right?
try:
self.fd = os.open(self.mmap_path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o600)
try:
check_shm_free_space(self.total_size_bytes)
os.ftruncate(self.fd, self.total_size_bytes)
except (RuntimeError, OSError) as e:
os.unlink(self.mmap_path)
raise
self._creator = True
except FileExistsError:
...
This looks good to me.
There was a problem hiding this comment.
Claude:
Need a try/except OSError around ftruncate with os.unlink(self.mmap_path) before re-raising — joiners then get immediate FileNotFoundError instead of spinning.
There was a problem hiding this comment.
Is it possible to add mmap.mmap .mmap_obj.madvise torch.frombuffer to be added to error capture?
like this
try:
self.fd = os.open(...)
os.ftruncate(...)
self._creator = True
logger.info(...)
self.mmap_obj = mmap.mmap(...)
self.mmap_obj.madvise(...)
self._base = torch.frombuffer(...)
except FileExistsError: # joiner path
self.fd = os.open(path, O_RDWR)
_wait_for_file_size(...)
logger.info(...)
except OSError:
if self.fd is not None:
os.close(self.fd)
os.unlink(self.mmap_path)
raise
There was a problem hiding this comment.
Claude's answer:
No — keep the except OSError tightly around ftruncate only. Reasons:
- The unlink is specifically to protect joiners from hanging. Joiners hang because they see a file that exists but never reaches the expected size. That can only happen if ftruncate fails (file stays at 0 bytes) or succeeds but the creator dies before MADV_POPULATE_WRITE completes. But MADV_POPULATE_WRITE failure is a different class of error — the file is the right size on disk, just not faulted in. Joiners won't hang because _wait_for_file_size checks fstat.st_size, not whether pages are populated.
- mmap.mmap failure after a successful ftruncate doesn't strand joiners either — the file is already the right size, joiners can open and mmap it fine. Unlinking it here would actually break joiners that already opened it.
- Mixing the joiner path (FileExistsError) and the error-cleanup path (OSError) in the same try block is fragile. OSError is a parent of FileExistsError — if the ordering of except clauses is wrong, or if future code adds an os.open call that raises OSError for a different reason, you'd accidentally catch the wrong thing. Keeping them in separate, narrow try blocks makes the intent explicit.
| mock_open.assert_not_called() | ||
|
|
||
|
|
||
| def test_joiner_skips_capacity_check_when_file_exists(iid, monkeypatch): |
| # --------------------------------------------------------------------------- | ||
|
|
||
|
|
||
| def test_insufficient_space_raises_clear_error(iid, monkeypatch): |
There was a problem hiding this comment.
Claude:
just monkeypatch check_shm_free_space to raise RuntimeError, assert the constructor propagates it. One mock, same coverage of the actual contract.
2ca53e8 to
eb0a969
Compare
Check shared memory capacity before creating CPU KV offload regions and make creator/joiner initialization failure-atomic. Signed-off-by: Alex <jihui.huang@daocloud.io>
eb0a969 to
dc88e69
Compare
|
@orozery It looks a little more complicated. |
Move the creator log after successful initialization, simplify fd cleanup, and exercise constructor failures through the public API. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Alex <jihui.huang@daocloud.io>
Remove the rare unlinked-inode fast path and simplify creator and joiner fd cleanup while preserving capacity error guidance. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Alex <jihui.huang@daocloud.io>
Keep the creator cleanup diff minimal by closing the existing instance fd directly. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Alex <jihui.huang@daocloud.io>
| except (RuntimeError, OSError) as e: | ||
| os.unlink(self.mmap_path) | ||
| os.close(self.fd) | ||
| if isinstance(e, RuntimeError): |
There was a problem hiding this comment.
check_shm_free_space already gives an actionable message.
I think we should drop the exception mutation.
Propagate check_shm_free_space errors unchanged and keep the focused test aligned with the helper message. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Alex <jihui.huang@daocloud.io>
| mock_close.assert_called_once_with(9999) | ||
|
|
||
|
|
||
| def test_joiner_wait_failure_closes_fd(monkeypatch): |
Keep the focused suite limited to capacity validation, creator cleanup, and normal wait behavior. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Alex <jihui.huang@daocloud.io>
|
/ci run |
|
✅ Triggered Buildkite CI #82506 for commit |
orozery
left a comment
There was a problem hiding this comment.
Thanks @Alex-ai-future !
[Bugfix] Fail fast when CPU KV offload exceeds
/dev/shmSummary
Fixes #46949.
Oversized CPU KV offload regions currently fail in
mmap.madvise(MADV_POPULATE_WRITE)with an opaqueOSError: [Errno 14] Bad address. This PR checks/dev/shmcapacity firstand reports the requested and available sizes with actionable configuration
guidance.
After #50094, the default
CPUOffloadingSpecalso usesSharedOffloadRegionon CUDA/ROCm, so both configuration paths are named:--kv-offloading-sizeandcpu_bytes_to_useinkv_connector_extra_config.Changes
check_shm_free_spacehelper only after winningO_EXCL,so joiners do not perform a redundant capacity check.
ftruncatefails.Duplicate check
#47073 and #46959 address the same issue but remain open with different,
stalled implementations. This PR is based on current
main, reuses vLLM'sexisting
check_shm_free_spacehelper, and follows @orozery's requestedcreator-path placement.
Tests
.venv/bin/python -m pytest \ tests/v1/kv_offload/cpu/test_shared_offload_region.py -v \ -k 'wait_for_file_size or insufficient_space or ftruncate_failure' .venv/bin/ruff check \ vllm/v1/kv_offload/cpu/shared_offload_region.py \ tests/v1/kv_offload/cpu/test_shared_offload_region.py .venv/bin/ruff format --check \ vllm/v1/kv_offload/cpu/shared_offload_region.py \ tests/v1/kv_offload/cpu/test_shared_offload_region.py pre-commit run mypy-3.12 --files \ vllm/v1/kv_offload/cpu/shared_offload_region.py \ tests/v1/kv_offload/cpu/test_shared_offload_region.py \ --hook-stage manualCUDA host validation
The failure path was also verified on a CUDA host by requesting one page more
than the available
/dev/shmcapacity:Model evaluation is not applicable; this changes startup validation and
cleanup only.
AI assistance
AI assistance was used for implementation, tests, and this description. The
submitting human reviewed every changed line and ran the tests above.