feat(vlm): auto-select cuda vmm on multi-node mnnvl (#33936)

This commit is contained in:
Mick
2026-08-08 16:00:58 +08:00
committed by GitHub
parent db3898fec1
commit d747bd052e
11 changed files with 245 additions and 29 deletions
+18 -2
View File
@@ -431,11 +431,14 @@ class MultimodalDataItem:
def reconstruct(self, target_device: int, ipc_consumer_count: int = 1):
"""materialize cuda ipc proxy tensors in-place on target_device"""
if isinstance(self.feature, CudaIpcTensorTransportProxy):
if ipc_consumer_count == 1:
consumer_count = self._resolve_transport_consumer_count(
self.feature, ipc_consumer_count
)
if consumer_count == 1:
self.feature = self.feature.reconstruct_on_target_device(target_device)
else:
self.feature = self.feature.reconstruct_on_target_device(
target_device, consumer_count=ipc_consumer_count
target_device, consumer_count=consumer_count
)
if isinstance(self.precomputed_embeddings, CudaIpcTensorTransportProxy):
self.precomputed_embeddings = (
@@ -474,8 +477,21 @@ class MultimodalDataItem:
def acknowledge_deferred_cuda_ipc_feature(self, consumer_count: int = 1):
"""Release a lazy IPC feature when an embedding-cache hit skips ViT."""
if isinstance(self.feature, CudaIpcTensorTransportProxy):
consumer_count = self._resolve_transport_consumer_count(
self.feature, consumer_count
)
self.feature.acknowledge_consumption(consumer_count)
@staticmethod
def _resolve_transport_consumer_count(proxy, requested_count: int) -> int:
"""Clamp a group acknowledgement to the proxy's actual consumer set."""
proxy_count = getattr(
proxy,
"total_consumer_count",
getattr(proxy, "consumer_count", requested_count),
)
return min(requested_count, proxy_count)
@dataclasses.dataclass
class MultimodalProcessorOutput:
+5
View File
@@ -235,6 +235,11 @@ def get_model_architecture(model_config: ModelConfig) -> Tuple[Type[nn.Module],
return model_cls, resolved_arch
def supports_cuda_vmm_feature_transport(model_config: ModelConfig) -> bool:
model_cls, _ = get_model_architecture(model_config)
return bool(getattr(model_cls, "supports_cuda_vmm_feature_transport", False))
def get_resolved_model_impl(model_config: ModelConfig) -> ModelImpl:
resolved_model_impl = getattr(model_config, "_resolved_model_impl", None)
if resolved_model_impl is not None:
+2
View File
@@ -637,6 +637,8 @@ def mm_projection_auto(
class KimiK25ForConditionalGeneration(nn.Module):
supports_cuda_vmm_feature_transport = True
# Support nvidia/Kimi-K2.5-NVFP4 naming: language_model.layers.*.
# Ref: HF config.json for nvidia/Kimi-K2.5-NVFP4
# https://huggingface.co/nvidia/Kimi-K2.5-NVFP4/blob/main/config.json
+2
View File
@@ -2933,6 +2933,8 @@ class KimiK3LinearForCausalLM(nn.Module):
class KimiK3ForConditionalGeneration(nn.Module):
"""K3 multimodal wrapper: MoonViT3d tower + KimiK3LinearForCausalLM."""
supports_cuda_vmm_feature_transport = True
# Raw HF checkpoint prefixes, before hf_to_sglang_mapper is applied.
encoder_only_safetensors_weight_prefixes = (
"vision_tower.",
+2
View File
@@ -1210,6 +1210,8 @@ class Qwen3LLMModel(Qwen3Model):
class Qwen3VLForConditionalGeneration(nn.Module):
supports_cuda_vmm_feature_transport = True
# To ensure correct weight loading and mapping.
hf_to_sglang_mapper = WeightsMapper(
orig_to_new_substr={
@@ -592,10 +592,10 @@ class KimiK2_5VLImageProcessor(KimiGridMMDataMixin, SGLangBaseProcessor):
)
# K2.5/K2.7 encoder-DP assigns an image to exactly one TP rank. Keep
# its IPC proxy lazy until that assignment is known, avoiding a full
# its GPU transport proxy lazy until that assignment is known, avoiding a full
# image copy to every rank. The scheduler only honors this marker once
# the processor has already set the item's hash and pad value.
if self.use_cuda_ipc and self.server_args.mm_enable_dp_encoder:
if self.keep_mm_features_on_device and self.server_args.mm_enable_dp_encoder:
for item in mm_items:
item.model_specific_data[DEFER_CUDA_IPC_FEATURE_RECONSTRUCTION_KEY] = (
True
@@ -389,7 +389,7 @@ class KimiK3ImageProcessor(KimiGridMMDataMixin, SGLangBaseProcessor):
# that assignment is known: one tokenizer/scheduler crossing per
# image instead of one per rank. K2.5 gates this on
# --mm-enable-dp-encoder; K3 needs no flag.
if getattr(self, "use_cuda_ipc", False):
if self.keep_mm_features_on_device:
for item in mm_items:
item.model_specific_data[DEFER_CUDA_IPC_FEATURE_RECONSTRUCTION_KEY] = (
True
+62 -22
View File
@@ -2747,13 +2747,13 @@ class ServerArgs:
mm_feature_transport: A[
Optional[Literal["cpu", "cuda_ipc", "cuda_vmm"]],
"Transport multimodal features through CPU memory, a bounded CUDA IPC "
"pool, or a bounded CUDA VMM pool. CUDA VMM must be selected explicitly "
"and is available only to models that opt in. "
"pool, or a bounded CUDA VMM pool. "
"Unset resolves automatically: multimodal models on single-node CUDA "
"deployments (without disaggregation) use cuda_ipc, everything else uses "
"cpu. Both CUDA transports reserve SGLANG_MM_FEATURE_CACHE_MB (default "
"1024 MiB) on the base GPU across tokenizer workers and fall back to CPU "
"transport per tensor when full.",
"deployments (without disaggregation) use cuda_ipc; validated multi-node "
"GB200/GB300 MNNVL models use cuda_vmm when an IMEX channel is available; "
"all other deployments use cpu. GPU transports reserve "
"SGLANG_MM_FEATURE_CACHE_MB (default 1024 MiB) on the base GPU and fall "
"back to CPU transport when the pool is full.",
NS("mm"),
] = None
keep_mm_feature_on_device: A[
@@ -7591,10 +7591,10 @@ class ServerArgs:
def _handle_multimodal_feature_transport(self):
"""Resolve multimodal feature transport before tokenizer workers start.
CUDA IPC is deliberately opt-in: its fixed pool lives on ``base_gpu_id``
and reduces the memory left for model/KV-cache allocations. The legacy
flag and environment variable remain supported so existing deployments
continue to work, but both map to this single policy.
GPU transports use a fixed pool on ``base_gpu_id`` and therefore reduce
the memory left for model/KV-cache allocations. The legacy CUDA IPC flag
and environment variable remain supported so existing deployments map
to this single policy.
"""
requested_transport = self.mm_feature_transport
legacy_ipc_is_set = envs.SGLANG_USE_CUDA_IPC_TRANSPORT.is_set()
@@ -7631,20 +7631,48 @@ class ServerArgs:
elif (
self.get_model_config().is_multimodal
and is_cuda()
and self.nnodes == 1
and self.disaggregation_mode == "null"
):
# Auto policy: single-node CUDA serving defaults to the bounded
# CUDA-IPC pool for multimodal models. Text-only deployments do
# not need feature transport. Multi-node (IPC handles are
# intra-node) and PD-disaggregated deployments keep CPU transport.
# A full pool degrades to CPU transport per tensor.
requested_transport = "cuda_ipc"
logger.info(
"Multimodal feature transport auto-resolved to cuda_ipc "
"(single-node CUDA). Pass --mm-feature-transport=cpu to "
"opt out."
)
# A full GPU pool always degrades to CPU transport per tensor.
# CUDA IPC is intra-node; multi-node auto-selection is limited
# to GB200/GB300 systems where the runtime already enables the
# MNNVL/IMEX communication stack.
if self.nnodes == 1:
requested_transport = "cuda_ipc"
logger.info(
"Multimodal feature transport auto-resolved to cuda_ipc "
"(single-node CUDA). Pass --mm-feature-transport=cpu to "
"opt out."
)
elif is_mnnvl_fabric_device() and os.path.exists(
"/dev/nvidia-caps-imex-channels/channel0"
):
from sglang.srt.model_loader.utils import (
supports_cuda_vmm_feature_transport,
)
if supports_cuda_vmm_feature_transport(self.get_model_config()):
requested_transport = "cuda_vmm"
logger.info(
"Multimodal feature transport auto-resolved to "
"cuda_vmm (multi-node GB200/GB300 MNNVL). Pass "
"--mm-feature-transport=cpu to opt out."
)
else:
requested_transport = "cpu"
logger.info(
"Multimodal feature transport auto-resolved to cpu: "
"the model has not opted into CUDA VMM transport."
)
else:
requested_transport = "cpu"
if is_mnnvl_fabric_device():
logger.info(
"Multimodal feature transport auto-resolved to cpu: "
"GB200/GB300 was detected but no IMEX channel is "
"mounted. Configure the MNNVL compute domain or pass "
"--mm-feature-transport=cuda_vmm after doing so."
)
else:
requested_transport = "cpu"
elif legacy_ipc_is_set and legacy_ipc_enabled != (
@@ -7681,6 +7709,18 @@ class ServerArgs:
"--mm-feature-transport=cuda_vmm is not supported with "
"SGLANG_RUST_SERVER."
)
pool_budget_mb = envs.SGLANG_MM_FEATURE_CACHE_MB.get()
handle_kind = "CUDA FABRIC" if self.nnodes > 1 else "POSIX FD"
logger.info(
"Using CUDA VMM for multimodal features with %s sharing: "
"reserving up to %d MiB on base GPU %d across %d tokenizer "
"worker(s). This reduces KV cache headroom; a full pool falls "
"back to inline CPU transport.",
handle_kind,
pool_budget_mb,
self.base_gpu_id,
self.tokenizer_worker_num,
)
if requested_transport == "cuda_ipc":
if not is_cuda():