feat(vlm): auto-select cuda vmm on multi-node mnnvl (#33936)
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user