A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 6, 09:30 UTC
[torch.compile][BE][Multimodal] Remove requirement to set_model_tag to avoid cache conflict (#37345)
Signed-off-by: Lucas Kabela <lucaskabela@meta.com>
Lucas Kabela committed
Mar 19, 2026 at 10:26 UTC
7769b58307c9604ac833ba790f511cea3989c0e6
7 files changed
+86
-69
docs/design/torch_compile_multimodal.md
+5
-6
@@ -29,10 +29,9 @@ To compile a multimodal component such as an encoder, we follow the same mechani
29
1. The `@support_torch_compile` decorator should include `enable_if=should_torch_compile_mm_encoder`. This will gate the compilation behind our
30
`compile_mm_encoder` configuration
31
32
-2. `with set_model_tag("<component_name>", is_encoder=True)` context manager should be used around the nn.Module's instantiation. Since torch.compile
33
-relies on caching artifacts to reduce start time, we must properly propagate the `<component_name>` information to the cache in order to avoid collisions
34
-with the LLM text-backbone, or other instances of the same artifact (as is the case with vision block). `is_encoder=True` is also needed for encoder
35
-components (see Compile Range Integration).
32
+2. The `@support_torch_compile` decorator should include `is_encoder=True` for encoder components. This is needed for compile range integration
33
+(see Compile Range Integration). The decorator automatically uses the class name as the cache directory prefix, avoiding collisions between
34
+independently compiled sub-modules (e.g. vision encoder components vs the text backbone).
35
36
### CompilationConfig
37
@@ -57,8 +56,8 @@ tradeoff
56
### Compile ranges
57
58
The torch.compile integration will try to rely on max_batch_size to infer compilation ranges for dynamic shapes; however, for modules used in the encoder, this
60
-shape can be difficult to infer due to the unspecified range of shapes the encoder may see as input. Therefore, we rely on `is_encoder=True` in the `set_model_tag`
61
-to alert torch.compile to the fact that this range cannot be inferred, and we default to the range (1, MAX_INT).
59
+shape can be difficult to infer due to the unspecified range of shapes the encoder may see as input. Therefore, we rely on `is_encoder=True` in the
60
+`@support_torch_compile` decorator to alert torch.compile to the fact that this range cannot be inferred, and we default to the range (1, MAX_INT).
61
62
!!! note
63
We may seek to tighten this range for better performance in the future
vllm/compilation/decorators.py
+14
-3
@@ -118,6 +118,7 @@ def support_torch_compile(
118
dynamic_arg_dims: dict[str, int | list[int]] | None = None,
119
mark_unbacked_dims: dict[str, int | list[int]] | None = None,
120
enable_if: Callable[[VllmConfig], bool] | None = None,
121
+ is_encoder: bool = False,
122
shape_invariants: Callable[..., None] = lambda *args, **kwargs: None,
123
) -> Callable[[type[_T]], type[_T]] | type[_T]:
124
"""
@@ -177,6 +178,11 @@ def support_torch_compile(
178
enforce that dynamo does not specialize on 0/1 values in the case of dummy input
179
such as for vision model compilation
180
181
+ `is_encoder` marks this module as a portion of an multimodal encoder.
182
+ When True, the compile range upper bound is set to MAX_INT32 instead of
183
+ max_num_batched_tokens, since encoder input shapes are unpredictable.
184
+ This is typically used for vision encoder sub-modules in multimodal models.
185
+
186
`shape_invariants` is a function that gets compiled right before forward.
187
The function should have the torch._check calls that are needed to set
188
the relationships between different input sizes. For example:
@@ -226,6 +232,7 @@ def support_torch_compile(
232
inferred_dynamic_arg_dims,
233
mark_unbacked_dims,
234
enable_if,
235
+ is_encoder,
236
shape_invariants,
237
)
238
@@ -316,6 +323,7 @@ def _support_torch_compile(
323
dynamic_arg_dims: dict[str, int | list[int]],
324
mark_unbacked_dims: dict[str, int | list[int]] | None = None,
325
enable_if: Callable[[VllmConfig], bool] | None = None,
326
+ is_encoder: bool = False,
327
shape_invariants: Callable[..., None] = lambda *args, **kwargs: None,
328
) -> type[_T]:
329
"""
@@ -345,8 +353,7 @@ def _support_torch_compile(
353
vllm_config = get_current_vllm_config()
354
355
# NOTE: to support multimodal models (such as encoder),
348
- # we may not have vllm_config so we may need to patch
349
- # it
356
+ # we may not have vllm_config so we may need to patch it
357
sig = inspect.signature(old_init)
358
if "vllm_config" in sig.parameters:
359
kwargs["vllm_config"] = vllm_config
@@ -374,7 +381,11 @@ def _support_torch_compile(
381
self.compiled = False
382
383
# Handled by monkeypatching `TorchCompileWithNoGuardsWrapper` into base class
377
- TorchCompileWithNoGuardsWrapper.__init__(self)
384
+ TorchCompileWithNoGuardsWrapper.__init__(
385
+ self,
386
+ compile_prefix=cls.__name__ if is_encoder else "",
387
+ is_encoder=is_encoder,
388
+ )
389
390
cls.__init__ = __init__
391
vllm/compilation/wrapper.py
+15
-3
@@ -75,8 +75,14 @@ class TorchCompileWithNoGuardsWrapper:
75
return ctx.result
76
return callable_fn(*args, **kwargs)
77
78
- def __init__(self) -> None:
78
+ def __init__(
79
+ self,
80
+ compile_prefix: str = "",
81
+ is_encoder: bool = False,
82
+ ) -> None:
83
self.compiled = False
84
+ self._compile_prefix = compile_prefix
85
+ self._is_encoder = is_encoder
86
87
vllm_config = get_current_vllm_config()
88
self.vllm_config = vllm_config
@@ -87,7 +93,9 @@ class TorchCompileWithNoGuardsWrapper:
93
if mode is None:
94
raise RuntimeError("Compilation mode cannot be NO_COMPILATION")
95
90
- backend = vllm_config.compilation_config.init_backend(vllm_config)
96
+ backend = vllm_config.compilation_config.init_backend(
97
+ vllm_config, prefix=compile_prefix, is_encoder=is_encoder
98
+ )
99
options = {}
100
101
if isinstance(backend, str) and backend == "inductor":
@@ -332,4 +340,8 @@ def reset_compile_wrapper(model: torch.nn.Module) -> None:
340
compilation_config.local_cache_dir = ""
341
342
model.__class__.forward.__code__ = model.original_code_object()
335
- TorchCompileWithNoGuardsWrapper.__init__(model)
343
+ TorchCompileWithNoGuardsWrapper.__init__(
344
+ model,
345
+ compile_prefix=model._compile_prefix,
346
+ is_encoder=model._is_encoder,
347
+ )
vllm/config/compilation.py
+10
-4
@@ -909,11 +909,19 @@ class CompilationConfig:
909
if self.backend == "":
910
self.backend = current_platform.get_compile_backend()
911
912
- def init_backend(self, vllm_config: "VllmConfig") -> str | Callable:
912
+ def init_backend(
913
+ self,
914
+ vllm_config: "VllmConfig",
915
+ prefix: str = "",
916
+ is_encoder: bool = False,
917
+ ) -> str | Callable:
918
"""
919
Initialize the backend for the compilation config from a vllm config.
920
Arguments:
921
vllm_config: The vllm config to initialize the backend from.
922
+ prefix: Cache directory prefix for this compiled module.
923
+ is_encoder: Whether this module is used in an encoder (as
924
+ opposed to a text backbone).
925
Returns:
926
The backend for the compilation config.
927
"""
@@ -943,9 +951,7 @@ class CompilationConfig:
951
952
from vllm.compilation.backends import VllmBackend
953
946
- # TODO[@lucaskabela]: See if we can forward prefix
947
- # https://github.com/vllm-project/vllm/issues/27045
948
- return VllmBackend(vllm_config)
954
+ return VllmBackend(vllm_config, prefix=prefix, is_encoder=is_encoder)
955
956
def post_init_cudagraph_sizes(self) -> None:
957
"""To complete the initialization after cudagraph related
vllm/model_executor/models/lfm2_siglip2.py
+7
-10
@@ -272,6 +272,7 @@ class Siglip2MLP(nn.Module):
272
@support_torch_compile(
273
dynamic_arg_dims={"hidden_states": [0, 1], "cu_seqlens": 0},
274
enable_if=should_torch_compile_mm_encoder,
275
+ is_encoder=True,
276
)
277
class Siglip2EncoderLayer(nn.Module):
278
def __init__(
@@ -395,16 +396,12 @@ class Siglip2VisionTransformer(nn.Module):
396
embed_dim = config.hidden_size
397
self.config = config
398
self.embeddings = Siglip2VisionEmbeddings(config)
398
- # Keep the import local to avoid circular dependencies during model init.
399
- from vllm.compilation.backends import set_model_tag
400
-
401
- with set_model_tag("Siglip2Encoder", is_encoder=True):
402
- self.encoder = Siglip2Encoder(
403
- config,
404
- quant_config=quant_config,
405
- num_hidden_layers_override=num_hidden_layers_override,
406
- prefix=f"{prefix}.encoder",
407
- )
399
+ self.encoder = Siglip2Encoder(
400
+ config,
401
+ quant_config=quant_config,
402
+ num_hidden_layers_override=num_hidden_layers_override,
403
+ prefix=f"{prefix}.encoder",
404
+ )
405
num_hidden_layers = config.num_hidden_layers
406
if len(self.encoder.layers) > config.num_hidden_layers:
407
raise ValueError(
vllm/model_executor/models/mllama4.py
+4
-7
@@ -453,7 +453,9 @@ class Llama4UnfoldConvolution(nn.Module):
453
454
455
@support_torch_compile(
456
- dynamic_arg_dims={"images_flattened": 0}, enable_if=should_torch_compile_mm_encoder
456
+ dynamic_arg_dims={"images_flattened": 0},
457
+ enable_if=should_torch_compile_mm_encoder,
458
+ is_encoder=True,
459
)
460
class Llama4VisionModel(nn.Module):
461
def __init__(
@@ -754,12 +756,7 @@ class Llama4ForConditionalGeneration(
756
self.multimodal_config = multimodal_config
757
758
with self._mark_tower_model(vllm_config, "image"):
757
- from vllm.compilation.backends import set_model_tag
758
-
759
- with (
760
- set_current_vllm_config(vllm_config),
761
- set_model_tag("Llama4VisionModel", is_encoder=True),
762
- ):
759
+ with set_current_vllm_config(vllm_config):
760
self.vision_model = Llama4VisionModel(
761
config=config.vision_config,
762
quant_config=None,
vllm/model_executor/models/qwen2_5_vl.py
+31
-36
@@ -427,6 +427,7 @@ class Qwen2_5_VisionAttention(nn.Module):
427
"rotary_pos_emb_sin": 0,
428
},
429
enable_if=should_torch_compile_mm_encoder,
430
+ is_encoder=True,
431
)
432
class Qwen2_5_VisionBlock(nn.Module):
433
def __init__(
@@ -486,6 +487,7 @@ class Qwen2_5_VisionBlock(nn.Module):
487
"x": 0,
488
},
489
enable_if=should_torch_compile_mm_encoder,
490
+ is_encoder=True,
491
)
492
class Qwen2_5_VisionPatchEmbed(nn.Module):
493
def __init__(
@@ -521,6 +523,7 @@ class Qwen2_5_VisionPatchEmbed(nn.Module):
523
"x": 0,
524
},
525
enable_if=should_torch_compile_mm_encoder,
526
+ is_encoder=True,
527
)
528
class Qwen2_5_VisionPatchMerger(nn.Module):
529
def __init__(
@@ -592,18 +595,12 @@ class Qwen2_5_VisionTransformer(nn.Module):
595
self.spatial_merge_size = vision_config.spatial_merge_size
596
self.fullatt_block_indexes = vision_config.fullatt_block_indexes
597
self.spatial_merge_unit = self.spatial_merge_size**2
595
- # TODO[@lucaskabela]: Investigate fixing this usage
596
- # see https://github.com/vllm-project/vllm/issues/27044
597
- # DO NOT MOVE THIS IMPORT
598
- from vllm.compilation.backends import set_model_tag
599
-
600
- with set_model_tag("Qwen2_5_VisionPatchEmbed", is_encoder=True):
601
- self.patch_embed = Qwen2_5_VisionPatchEmbed(
602
- patch_size=patch_size,
603
- temporal_patch_size=temporal_patch_size,
604
- in_channels=in_channels,
605
- hidden_size=self.hidden_size,
606
- )
598
+ self.patch_embed = Qwen2_5_VisionPatchEmbed(
599
+ patch_size=patch_size,
600
+ temporal_patch_size=temporal_patch_size,
601
+ in_channels=in_channels,
602
+ hidden_size=self.hidden_size,
603
+ )
604
605
norm_layer = partial(RMSNorm, eps=norm_eps)
606
head_dim = self.hidden_size // self.num_heads
@@ -619,31 +616,29 @@ class Qwen2_5_VisionTransformer(nn.Module):
616
dtype=torch.get_default_dtype(),
617
)
618
622
- with set_model_tag("Qwen2_5_VisionBlock", is_encoder=True):
623
- self.blocks = nn.ModuleList(
624
- [
625
- Qwen2_5_VisionBlock(
626
- dim=self.hidden_size,
627
- num_heads=self.num_heads,
628
- mlp_hidden_dim=vision_config.intermediate_size,
629
- act_fn=get_act_and_mul_fn(vision_config.hidden_act),
630
- norm_layer=norm_layer,
631
- quant_config=quant_config,
632
- prefix=f"{prefix}.blocks.{layer_idx}",
633
- )
634
- for layer_idx in range(depth)
635
- ]
636
- )
619
+ self.blocks = nn.ModuleList(
620
+ [
621
+ Qwen2_5_VisionBlock(
622
+ dim=self.hidden_size,
623
+ num_heads=self.num_heads,
624
+ mlp_hidden_dim=vision_config.intermediate_size,
625
+ act_fn=get_act_and_mul_fn(vision_config.hidden_act),
626
+ norm_layer=norm_layer,
627
+ quant_config=quant_config,
628
+ prefix=f"{prefix}.blocks.{layer_idx}",
629
+ )
630
+ for layer_idx in range(depth)
631
+ ]
632
+ )
633
638
- with set_model_tag("Qwen2_5_VisionPatchMerger", is_encoder=True):
639
- self.merger = Qwen2_5_VisionPatchMerger(
640
- d_model=vision_config.out_hidden_size,
641
- context_dim=self.hidden_size,
642
- norm_layer=norm_layer,
643
- spatial_merge_size=self.spatial_merge_size,
644
- quant_config=quant_config,
645
- prefix=f"{prefix}.merger",
646
- )
634
+ self.merger = Qwen2_5_VisionPatchMerger(
635
+ d_model=vision_config.out_hidden_size,
636
+ context_dim=self.hidden_size,
637
+ norm_layer=norm_layer,
638
+ spatial_merge_size=self.spatial_merge_size,
639
+ quant_config=quant_config,
640
+ prefix=f"{prefix}.merger",
641
+ )
642
643
@property
644
def dtype(self) -> torch.dtype: