Repository navigation
fix(multimodal): validate NIXL READ metadata before borrowing embedding buffers - #17935
Conversation
…ng buffers NixlReadEmbeddingReceiver.receive_embeddings borrowed a pooled descriptor before checking that the request described a usable NIXL READ. A malformed request (for example WRITE metadata, which passes RdmaMetadata validation but is rejected by begin_read) or a request larger than the pooled buffer removed the descriptor from the pool and left its _data_size changed. Check the READ contract (kind, exactly one remote descriptor, non-zero pointer, matching size, non-empty notification key, decodable agent metadata) before borrowing, as MmKwargsNixlReceiver does since ai-dynamo#15482. Return the descriptor with its original size if building the view fails. Behavior once begin_read is called is unchanged. Refs DYN-5224 Signed-off-by: svc-glamr@nvidia.com <svc-glamr@nvidia.com>
Signed-off-by: svc-glamr@nvidia.com <svc-glamr@nvidia.com>
|
👋 Hi glamr-agent! Thank you for contributing to ai-dynamo/dynamo. Just a reminder: The 🚀 |
|
factory: Initial validation snapshot. Validation status: complete
The real NIXL read on a GPU ( Detailed evidence — validation completeLeft out of the table because CI repeats them: builds the changed Dynamo source and imports its compiled extension (Passed, Evidence tally: [3/3 validated] Advisory AI assessment: sound Validation result: pass — the new tests fail on Evidence audit: complete [3/3 validated] — the command report comes from recorded runs. Commands and results [3/3 validated]Generated from the commands recorded during this run. Check 1Builds the changed Dynamo source and confirms that Python can import its compiled extension. Result: Passed ( Command: Not shown because the exact command contained private run data. Check 2Checks the changed files with the repository's fast lint and formatting commands. Result: Passed ( Command: bash -c 'set -e; /opt/dynamo/venv/bin/python -m py_compile components/src/dynamo/common/multimodal/embedding_transfer.py components/src/dynamo/common/tests/multimodal/test_embedding_transfer.py && echo py_compile OK; pre-commit run --files components/src/dynamo/common/multimodal/embedding_transfer.py components/src/dynamo/common/tests/multimodal/test_embedding_transfer.py --hook-stage manual'Check 3Runs the relevant Python unit tests without requiring a GPU. Result: Passed ( Command: timeout 600 /opt/dynamo/venv/bin/python -m pytest components/src/dynamo/common/tests/multimodal/test_embedding_transfer.py -k TestNixlReadEmbeddingReceiverPreparation -v -p no:cacheprovider |
|
No description provided. |
|
factory: @dynamo-ops please run full CI for 481341c |
|
/ok to test 481341c |
WalkthroughThe NIXL read receiver now validates READ metadata before buffer allocation. If tensor-view preparation fails, it restores the borrowed descriptor to the warmed pool. New tests cover invalid metadata, oversized requests, and failed reads. ChangesNIXL Read Receiver
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk:
|
…owing Signed-off-by: svc-glamr@nvidia.com <svc-glamr@nvidia.com>
|
factory: @dynamo-ops please run full CI for f6ba3a0 |
|
/ok to test f6ba3a0 |
✅ Dynamo PR CI passed — run 41607830433 (attempt 1) on
|
Failure behavior
When a worker receives multimodal embeddings over NIXL READ, the receiver takes a buffer from its pre-registered pool before it checks the request. A request that passes schema validation but is not a usable READ then fails after the borrow. One example is metadata with a WRITE operation kind. The buffer never returns to the pool and is never tracked as in use, so it is lost. Its recorded size also keeps the request's size instead of the full buffer size. A valid request larger than the pooled buffer drains the pool the same way.
The defect is in Dynamo. Today the vLLM and common multimodal receivers are built with an empty pool, so every request takes the dynamic path and no deployment is known to have hit this.
max_items=0, so they have no pool to drain; the failure needs a pooled receiver, and the ticket reports no deployment that showed it.NixlReadEmbeddingReceiverwith a one-buffer pool and a mocked NIXL agent, on CPU. Without the fix, a WRITE request raised inbegin_readand left the pool empty. Malformed READ requests and an oversized request did the same. With the fix, the pool kept its buffer and original size.Proposed fix
The receiver now checks the READ contract before it borrows a buffer, with the same checks the multimodal kwargs receiver already runs: READ kind, exactly one remote descriptor, a non-zero pointer, a matching size, a notification key, and agent metadata that decodes to non-empty bytes. These checks also run on the dynamic path. If building the buffer view fails after the borrow, the buffer goes back to the pool with its original size. Once a read has started, nothing changes: a failed read still keeps the buffer out of the pool, because NIXL may still write into it.
Validation
This PR adds 13 unit tests in one new class,
TestNixlReadEmbeddingReceiverPreparation, tocomponents/src/dynamo/common/tests/multimodal/test_embedding_transfer.py. They runwithout a GPU, against a mocked native NIXL agent with real CPU buffers.
begin_readraised after the borrow, and the pool was left emptyRelates to #15482
Closes DYN-5224.
Summary by CodeRabbit