fix(lora): inline adapter tensors into the engine payload - #265
Open
SakaiXue6666 wants to merge 1 commit into
Open
fix(lora): inline adapter tensors into the engine payload#265SakaiXue6666 wants to merge 1 commit into
SakaiXue6666 wants to merge 1 commit into
Conversation
Adapter mode serialized the gathered adapter under torch's file_system sharing strategy, so the payload carried a /dev/shm path rather than the tensor bytes. That storage is reference counted: the TP workers that map it first drop their reference on return and unlink the file, and a rank that arrives late then opens a file that no longer exists. RuntimeError: unable to open shared memory object </torch_...> in read-write mode: No such file or directory The failure is timing dependent, which is why it surfaces as a single rank dying while its peers load the same adapter successfully. Pickle the tensors instead. An adapter is small enough to inline (~24MB at rank 16 on a 30B model) and the payload then has no lifetime coupling to the producer at all. SGLang reads it unchanged, since MultiprocessingSerializer.deserialize base64-decodes and unpickles. The base-weight path is untouched: those tensors stay on device and serialize to CUDA IPC handles, which do not have this problem.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The adapter push in LoRA adapter mode now carries the tensor bytes in the payload (
serialize_adapter_tensors) instead of a reference to shared host memory. The call site loses its sharing-strategy dance and its keep-alive requirement along with it.Why
A colocate run of Qwen3-Omni-30B-A3B thinker LoRA (rank 16, TP4, 4×A100) gets through model build, LoRA injection, engine startup and the full base-weight sync (19743 parameters, 7.7s), then dies on the first adapter push — but only on one rank. TP1–TP3 log
loading from tensors completes; TP0 raises:4103is the pid of training rank 0, the producer.The payload built under
file_systemdoes not contain the adapter; it contains a/dev/shmfile name. That storage is reference counted, and the count is what the consumers hold: the ranks that map it first release their reference when the call returns, the count reaches zero, the file is unlinked, and a rank that arrives late opens a path that no longer exists. Nothing is wrong with the adapter, the LoRA scope, or the engine — the bytes simply expire in flight.Being timing dependent, it presents as one rank dying while its peers load the very same adapter, which reads like a rank-local fault and is not one.
The current comment in this code records that the default
file_descriptorstrategy was already found unable to cross the Ray → HTTP hops, andfile_systemwas adopted in response. That diagnosis is right;file_systemis simply the next pothole on the same road, because both strategies share the property that matters here — the payload is a reference, and a reference is only as good as the producer's grip on the storage.A CPU-only probe reproduces all three transports end to end (one Ray actor serializes, four consumer actors deserialize, rank 0 arriving late by 8s):
file_descriptor(torch default)AuthenticationErrorfile_system(current)Worth stating plainly: with all four consumers arriving simultaneously,
file_systempasses. The race only materialises when one rank is late — which is precisely why it survived into production and why it should not be left to scheduling luck.How
serialize_adapter_tensorspickles the tensors and base64-encodes them, which is the exact wire format SGLang already expects:MultiprocessingSerializer.deserializebase64-decodes and unpickles, and a plain pickle simply has no reference to resolve. No SGLang-side change is needed.The base-weight path is deliberately untouched. Those tensors stay on device and serialize to CUDA IPC handles, which are self-contained across these hops — which is also why base sync never failed while the adapter push did.
The cost is the payload: 0.1MB of handles becomes 31.6MB of base64 for a rank-16 adapter on a 30B model, once per weight update, on a path that already does a cross-process gather. Adapters are bounded by rank rather than model size, so this stays small where it matters.
Testing
Run inside the Relax training image (
sglang 0.5.12.post1,torch 2.11.0+cu129):The three cases pin the property that broke, not the implementation: the payload round-trips through plain base64 + unpickle, it is at least as large as the tensors it carries, and it contains no
/torch_handle. Substituting the shared-memory serialization back into the helper fails the latter two —assert 530 >= 524288, and the handle found verbatim in the payload — so the tests would have caught this before it reached a GPU.End to end, a 40-step GRPO run (Qwen3-Omni-30B-A3B thinker LoRA, 4×A100 colocate, TP4/EP4, adapter pushed every step) completed 40/40 pushes with reward rising from 0.294 over the first ten steps to 0.392 over the last ten.
pre-commit run --filesis clean on all three touched files.pre-commit run --all-filespassespytest tests/)Type of Change
Screenshots / Logs
Production failure, four TP workers of one engine:
Same chain reproduced on CPU, with and without the fix: