-
Notifications
You must be signed in to change notification settings - Fork 142
feat(multimodal): vLLM SHM tensor transport #1893
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| """Shared multimodal SHM tensor-transport helpers (engine-neutral). | ||
|
|
||
| Reads a ``TensorData`` payload that carries its raw little-endian bytes either | ||
| inline in the gRPC message or via a same-host ``/dev/shm`` handle, and reports | ||
| this process's ``/dev/shm`` namespace identity. Used by the vLLM and TokenSpeed | ||
| gRPC servicers; their ``TensorData``/``ShmHandle`` messages share the ``payload`` | ||
| oneof shape (``inline`` | ``shm`` | ``remote``) via ``common.proto``. | ||
| """ | ||
|
|
||
| import os | ||
|
|
||
| # Unlink each /dev/shm segment right after the worker reads it (default on) so | ||
| # same-host SHM tensors don't accumulate. Disable with | ||
| # TOKENSPEED_UNLINK_MM_SHM_AFTER_READ=0 (e.g. for debugging). | ||
| UNLINK_MM_SHM_AFTER_READ = os.getenv("TOKENSPEED_UNLINK_MM_SHM_AFTER_READ", "1").lower() not in ( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Nit: The env var is |
||
| "0", | ||
| "false", | ||
| "no", | ||
| ) | ||
|
|
||
|
|
||
| def tensor_payload_bytes(tensor_data) -> bytes: | ||
| """Raw bytes of a ``TensorData``, from whichever payload transport it carries.""" | ||
| payload = tensor_data.WhichOneof("payload") | ||
| if payload == "inline": | ||
| return bytes(tensor_data.inline) | ||
| if payload == "shm": | ||
| return tensor_payload_bytes_from_shm(tensor_data.shm) | ||
| if payload == "remote": | ||
| raise ValueError("TensorData.remote payload is not implemented yet") | ||
| raise ValueError("TensorData payload is required") | ||
|
|
||
|
|
||
| def tensor_payload_bytes_from_shm(shm_handle) -> bytes: | ||
| """Read ``nbytes`` from ``/dev/shm/<name>`` at ``offset`` (unlinking after read | ||
| when enabled).""" | ||
| name = validated_shm_name(shm_handle.name) | ||
|
|
||
| path = os.path.join("/dev/shm", name) | ||
| fd = None | ||
| try: | ||
| fd = os.open(path, os.O_RDONLY) | ||
| raw = os.pread(fd, int(shm_handle.nbytes), int(shm_handle.offset)) | ||
| finally: | ||
| if fd is not None: | ||
| os.close(fd) | ||
| if fd is not None and UNLINK_MM_SHM_AFTER_READ: | ||
| try: | ||
| os.unlink(path) | ||
| except FileNotFoundError: | ||
| pass | ||
|
|
||
| if len(raw) != int(shm_handle.nbytes): | ||
| raise ValueError( | ||
| f"TensorData.shm byte length mismatch for name={shm_handle.name!r}: " | ||
| f"expected {int(shm_handle.nbytes)}, got {len(raw)}" | ||
| ) | ||
| return raw | ||
|
|
||
|
|
||
| def validated_shm_name(name: str) -> str: | ||
| """Reject path-traversal / absolute / empty SHM names before opening.""" | ||
| name = name.lstrip("/") | ||
| if not name or "/" in name or name in (".", "..") or "\x00" in name: | ||
| raise ValueError(f"Invalid TensorData.shm name: {name!r}") | ||
| return name | ||
|
|
||
|
|
||
| def shm_namespace_id() -> str: | ||
| """Identity of this process's ``/dev/shm`` tmpfs: ``<boot_id>:<st_dev>``. | ||
|
|
||
| ``boot_id`` (``/proc/sys/kernel/random/boot_id``) is not namespaced, so it | ||
| pins the host; ``st_dev`` is the tmpfs superblock device backing | ||
| ``/dev/shm``. Two processes share ``/dev/shm`` iff both match -- including | ||
| separate containers sharing it via ``--ipc``/bind-mount, where mount | ||
| namespaces differ but the underlying superblock (``st_dev``) is the same. The | ||
| router compares this token to its own to decide the SHM tensor transport. | ||
| Empty string if it can't be determined. | ||
| """ | ||
| try: | ||
| with open("/proc/sys/kernel/random/boot_id", encoding="ascii") as f: | ||
| boot_id = f.read().strip() | ||
| shm_dev = os.stat("/dev/shm").st_dev | ||
| return f"{boot_id}:{shm_dev}" | ||
| except OSError: | ||
| return "" | ||
|
slin1237 marked this conversation as resolved.
Outdated
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |||||||||
| import zmq.asyncio | ||||||||||
| from smg_grpc_proto import vllm_engine_pb2, vllm_engine_pb2_grpc | ||||||||||
| from smg_grpc_proto.generated import common_pb2 | ||||||||||
| from smg_grpc_servicer import mm_shm | ||||||||||
| from transformers import BatchFeature | ||||||||||
| from vllm import PoolingParams, SamplingParams, TokensPrompt | ||||||||||
| from vllm.distributed.kv_events import KVEventBatch | ||||||||||
|
|
@@ -78,7 +79,8 @@ def _tensor_from_proto(td: vllm_engine_pb2.TensorData) -> torch.Tensor: | |||||||||
| torch_dtype = _PROTO_DTYPE_MAP.get(td.dtype) | ||||||||||
| if torch_dtype is None: | ||||||||||
| raise ValueError(f"Unsupported proto tensor dtype: {td.dtype!r}") | ||||||||||
| return torch.frombuffer(bytearray(td.data), dtype=torch_dtype).reshape(*td.shape) | ||||||||||
| payload = mm_shm.tensor_payload_bytes(td) | ||||||||||
| return torch.frombuffer(bytearray(payload), dtype=torch_dtype).reshape(*td.shape) | ||||||||||
|
Comment on lines
+82
to
+83
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using
Suggested change
|
||||||||||
|
|
||||||||||
|
|
||||||||||
| try: | ||||||||||
|
|
@@ -487,6 +489,7 @@ async def GetServerInfo( | |||||||||
| kv_role=kv_role, | ||||||||||
| kv_engine_id=kv_engine_id, | ||||||||||
| data_parallel_size=parallel.data_parallel_size, | ||||||||||
| shm_namespace_id=mm_shm.shm_namespace_id(), | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This servicer now constructs Useful? React with 👍 / 👎. |
||||||||||
| ) | ||||||||||
|
|
||||||||||
| async def GetLoads( | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,9 +14,9 @@ use smg_grpc_client::{ | |
|
|
||
| use crate::routers::grpc::{ | ||
| proto_wrapper::{ | ||
| cleanup_tokenspeed_shm_handles, collect_tokenspeed_generate_request_shm_handles, | ||
| finish_tokenspeed_request, ProtoEmbedComplete, ProtoEmbedRequest, ProtoGenerateRequest, | ||
| ProtoStream, | ||
| cleanup_mm_shm_handles, collect_tokenspeed_generate_request_shm_handles, | ||
| collect_vllm_generate_request_shm_handles, finish_tokenspeed_request, ProtoEmbedComplete, | ||
| ProtoEmbedRequest, ProtoGenerateRequest, ProtoStream, | ||
| }, | ||
| MultimodalData, | ||
| }; | ||
|
|
@@ -397,8 +397,14 @@ impl GrpcClient { | |
| Ok(ProtoStream::Sglang(stream)) | ||
| } | ||
| (Self::Vllm(client), ProtoGenerateRequest::Vllm(boxed_req)) => { | ||
| let stream = client.generate(*boxed_req).await?; | ||
| Ok(ProtoStream::Vllm(stream)) | ||
| let shm_handles = collect_vllm_generate_request_shm_handles(&boxed_req); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Important: SHM cleanup covers the send-failure path here, but the build-failure path is unguarded. When TokenSpeed handles this with |
||
| match client.generate(*boxed_req).await { | ||
| Ok(stream) => Ok(ProtoStream::Vllm(stream)), | ||
| Err(error) => { | ||
| cleanup_mm_shm_handles(&shm_handles); | ||
| Err(error) | ||
| } | ||
| } | ||
| } | ||
| (Self::Trtllm(client), ProtoGenerateRequest::Trtllm(boxed_req)) => { | ||
| let stream = client.generate(*boxed_req).await?; | ||
|
|
@@ -413,7 +419,7 @@ impl GrpcClient { | |
| match client.generate(*boxed_req).await { | ||
| Ok(stream) => Ok(ProtoStream::TokenSpeed(stream)), | ||
| Err(error) => { | ||
| cleanup_tokenspeed_shm_handles(&shm_handles); | ||
| cleanup_mm_shm_handles(&shm_handles); | ||
| Err(error) | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,7 +72,7 @@ async fn assemble_multimodal_data_impl( | |
| } | ||
| GrpcClient::Vllm(_) => { | ||
| ensure_image_only(&precomputed, "vLLM")?; | ||
| Ok(MultimodalData::Vllm(assemble_vllm(precomputed))) | ||
| Ok(MultimodalData::Vllm(assemble_vllm(precomputed, workers))) | ||
| } | ||
| GrpcClient::Trtllm(_) => { | ||
| ensure_image_only(&precomputed, "TRT-LLM")?; | ||
|
|
@@ -168,7 +168,10 @@ fn assemble_sglang(intermediate: PrecomputedMultimodalIntermediate) -> SglangMul | |
| } | ||
| } | ||
|
|
||
| fn assemble_vllm(intermediate: PrecomputedMultimodalIntermediate) -> VllmMultimodalData { | ||
| fn assemble_vllm( | ||
| intermediate: PrecomputedMultimodalIntermediate, | ||
| workers: Option<&WorkerSelection>, | ||
| ) -> VllmMultimodalData { | ||
| let (pixel_values, pixel_values_shape) = serialize_encoder_input(&intermediate.preprocessed); | ||
| let model_specific_tensors = serialize_model_specific(intermediate.preprocessed.model_specific); | ||
| let mm_hashes = intermediate.images.iter().map(|f| f.hash.clone()).collect(); | ||
|
|
@@ -190,6 +193,8 @@ fn assemble_vllm(intermediate: PrecomputedMultimodalIntermediate) -> VllmMultimo | |
| batched_keys, | ||
| flat_keys, | ||
| keep_on_cpu_keys: intermediate.keep_on_cpu_keys, | ||
| shm_enabled: resolve_mm_shm_enabled(workers, false), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In vLLM PD, Useful? React with 👍 / 👎. |
||
| shm_min_bytes: resolve_mm_shm_min_bytes(workers), | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.