@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 6d729c43fb
Fork 0

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 14, 05:30 UTC

[Bugfix] Fix ModernBert load & Enable sliding window attention for bidirectional attention. (#22637)

Signed-off-by: wang.yuqi <noooop@126.com> Signed-off-by: Max de Bayser <mbayser@br.ibm.com> Co-authored-by: Max de Bayser <mbayser@br.ibm.com>

wang.yuqi committed Aug 12, 2025 at 15:23 UTC 6d729c43fbaf63d534e71c0b8aa61f0a82dd2018
4 files changed +101 -59
tests/models/language/pooling/test_gte.py
+18 -3
@@ -4,10 +4,11 @@ from typing import Any
4
5 import pytest
6
7 -from ...utils import (CLSPoolingEmbedModelInfo, EmbedModelInfo,
8 - LASTPoolingEmbedModelInfo, check_transformers_version)
7 +from ...utils import (CLSPoolingEmbedModelInfo, CLSPoolingRerankModelInfo,
8 + EmbedModelInfo, LASTPoolingEmbedModelInfo,
9 + RerankModelInfo, check_transformers_version)
10 from .embed_utils import correctness_test_embed_models
10 -from .mteb_utils import mteb_test_embed_models
11 +from .mteb_utils import mteb_test_embed_models, mteb_test_rerank_models
12
13 MODELS = [
14 ########## BertModel
@@ -58,6 +59,14 @@ MODELS = [
59 enable_test=False),
60 ]
61
62 +RERANK_MODELS = [
63 + # classifier_pooling: mean
64 + CLSPoolingRerankModelInfo(
65 + "Alibaba-NLP/gte-reranker-modernbert-base",
66 + architecture="ModernBertForSequenceClassification",
67 + enable_test=True),
68 +]
69 +
70
71 @pytest.mark.parametrize("model_info", MODELS)
72 def test_embed_models_mteb(hf_runner, vllm_runner,
@@ -88,3 +97,9 @@ def test_embed_models_correctness(hf_runner, vllm_runner,
97
98 correctness_test_embed_models(hf_runner, vllm_runner, model_info,
99 example_prompts, vllm_extra_kwargs)
100 +
101 +
102 +@pytest.mark.parametrize("model_info", RERANK_MODELS)
103 +def test_rerank_models_mteb(hf_runner, vllm_runner,
104 + model_info: RerankModelInfo) -> None:
105 + mteb_test_rerank_models(hf_runner, vllm_runner, model_info)
vllm/model_executor/models/modernbert.py
+15 -16
@@ -26,8 +26,7 @@ from vllm.model_executor.pooling_metadata import PoolingMetadata
26 from vllm.sequence import IntermediateTensors
27 from vllm.tasks import PoolingTask
28
29 -from .interfaces import (SupportsCrossEncoding, SupportsV0Only,
30 - default_pooling_type)
29 +from .interfaces import SupportsCrossEncoding, default_pooling_type
30 from .utils import WeightsMapper, maybe_prefix
31
32
@@ -93,16 +92,14 @@ class ModernBertAttention(nn.Module):
92 bias=config.attention_bias,
93 )
94
95 + sliding_window = None
96 if layer_id % config.global_attn_every_n_layers != 0:
97 - self.local_attention = (config.local_attention // 2,
98 - config.local_attention // 2)
97 + sliding_window = config.local_attention // 2
98 + rope_theta = config.local_rope_theta if config.local_rope_theta \
99 + is not None else config.global_rope_theta
100 else:
100 - self.local_attention = (-1, -1)
101 + rope_theta = config.global_rope_theta
102
102 - rope_theta = config.global_rope_theta
103 - if self.local_attention != (
104 - -1, -1) and config.local_rope_theta is not None:
105 - rope_theta = config.local_rope_theta
103 self.rotary_emb = ModernBertRotaryEmbedding(config=config,
104 head_size=self.head_dim,
105 dim=self.head_dim,
@@ -111,7 +108,8 @@ class ModernBertAttention(nn.Module):
108 self.head_dim,
109 self.scaling,
110 prefix=f"{layer_id}.attn",
114 - attn_type=AttentionType.ENCODER_ONLY)
111 + attn_type=AttentionType.ENCODER_ONLY,
112 + per_layer_sliding_window=sliding_window)
113 self.Wo = RowParallelLinear(config.hidden_size,
114 config.hidden_size,
115 bias=config.attention_bias)
@@ -278,6 +276,7 @@ class ModernBertPooler(Pooler):
276 return self.pooling.get_pooling_updates(task)
277
278 def _head(self, pooled_output: torch.Tensor):
279 + pooled_output = pooled_output.to(self.dense.weight.dtype)
280 return self.norm(self.act(self.dense(pooled_output)))
281
282 def forward(
@@ -296,8 +295,7 @@ class ModernBertPooler(Pooler):
295
296
297 @default_pooling_type("CLS")
299 -class ModernBertForSequenceClassification(nn.Module, SupportsV0Only,
300 - SupportsCrossEncoding):
298 +class ModernBertForSequenceClassification(nn.Module, SupportsCrossEncoding):
299
300 is_pooling_model = True
301
@@ -308,6 +306,7 @@ class ModernBertForSequenceClassification(nn.Module, SupportsV0Only,
306 self.model = ModernBertModel(vllm_config=vllm_config,
307 prefix=maybe_prefix(prefix, "modernbert"))
308 self.classifier = nn.Linear(config.hidden_size, config.num_labels)
309 + self.pooling = ModernBertPooler(config)
310
311 pooler_config = vllm_config.model_config.pooler_config
312 assert pooler_config is not None
@@ -317,14 +316,14 @@ class ModernBertForSequenceClassification(nn.Module, SupportsV0Only,
316 Pooler.for_encode(pooler_config),
317 "classify":
318 ClassifierPooler(
320 - pooling=ModernBertPooler(config),
319 + pooling=self.pooling,
320 classifier=self.classifier,
321 act_fn=ClassifierPooler.act_fn_for_seq_cls(
322 vllm_config.model_config),
323 ),
324 "score":
325 ClassifierPooler(
327 - pooling=ModernBertPooler(config),
326 + pooling=self.pooling,
327 classifier=self.classifier,
328 act_fn=ClassifierPooler.act_fn_for_cross_encoder(
329 vllm_config.model_config),
@@ -353,7 +352,7 @@ class ModernBertForSequenceClassification(nn.Module, SupportsV0Only,
352 default_weight_loader)
353 weight_loader(param, loaded_weight)
354 if name.startswith("head"):
356 - param = params_dict["_pooler.pooler." + name[len("head") + 1:]]
355 + param = params_dict["pooling." + name[len("head") + 1:]]
356 weight_loader = getattr(param, "weight_loader",
357 default_weight_loader)
358 weight_loader(param, loaded_weight)
@@ -368,5 +367,5 @@ class ModernBertForSequenceClassification(nn.Module, SupportsV0Only,
367 return self.model(
368 input_ids=input_ids,
369 inputs_embeds=inputs_embeds,
371 - position_ids=positions,
370 + positions=positions,
371 )
vllm/v1/attention/backends/flash_attn.py
+2
@@ -384,6 +384,8 @@ class FlashAttentionImpl(AttentionImpl):
384 self.alibi_slopes = alibi_slopes
385 if sliding_window is None:
386 self.sliding_window = (-1, -1)
387 + elif attn_type == AttentionType.ENCODER_ONLY:
388 + self.sliding_window = (sliding_window - 1, sliding_window - 1)
389 else:
390 self.sliding_window = (sliding_window - 1, 0)
391 self.kv_cache_dtype = kv_cache_dtype
vllm/v1/worker/gpu_model_runner.py
+66 -40
@@ -826,7 +826,8 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
826 # Prepare encoder attention metadata separately
827 # (encoder layers are not in KV cache groups)
828 if self.is_encoder_only_model:
829 - common_attn_metadata, encoder_attn_metadata = \
829 +
830 + per_layer_metadata = \
831 self._build_encoder_only_attn_metadata(
832 scheduler_output)
833
@@ -835,6 +836,8 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
836 self.vllm_config, Attention)
837 for layer_name, attn_module in attention_layers.items():
838 if attn_module.attn_type == AttentionType.ENCODER_ONLY:
839 + common_attn_metadata, encoder_attn_metadata =\
840 + per_layer_metadata[layer_name]
841 attn_metadata[layer_name] = encoder_attn_metadata
842
843 # Prepare the attention metadata for each KV cache group and make layers
@@ -2683,30 +2686,41 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2686 # Check if model is encoder-only
2687 block_size = self.vllm_config.cache_config.block_size
2688 use_mla = self.vllm_config.model_config.use_mla
2686 - attn_specs = list[AttentionSpec]()
2687 - for attn_module in attn_layers.values():
2689 + attn_specs: dict[AttentionSpec, list[str]] = defaultdict(list)
2690 + for layer_name, attn_module in attn_layers.items():
2691
2692 if attn_module.attn_type == AttentionType.ENCODER_ONLY:
2690 - assert attn_module.sliding_window is None, "Sliding "
2691 - "window attention is not supported for encoder-only models"
2692 -
2693 - attn_specs.append(
2694 - FullAttentionSpec(block_size=block_size,
2695 - num_kv_heads=attn_module.num_kv_heads,
2696 - head_size=attn_module.head_size,
2697 - dtype=self.kv_cache_dtype,
2698 - use_mla=use_mla))
2693 + if attn_module.sliding_window is None:
2694 + attn_spec: AttentionSpec = FullAttentionSpec(
2695 + block_size=block_size,
2696 + num_kv_heads=attn_module.num_kv_heads,
2697 + head_size=attn_module.head_size,
2698 + dtype=self.kv_cache_dtype,
2699 + use_mla=use_mla)
2700 + else:
2701 + attn_spec = SlidingWindowSpec(
2702 + block_size=block_size,
2703 + num_kv_heads=attn_module.num_kv_heads,
2704 + head_size=attn_module.head_size,
2705 + dtype=self.kv_cache_dtype,
2706 + sliding_window=attn_module.sliding_window,
2707 + use_mla=use_mla)
2708 + attn_specs[attn_spec].append(layer_name)
2709 +
2710 else:
2711 raise ValueError("Expected only encoder-only layers")
2712
2713 if len(attn_specs) > 0:
2703 - assert len(attn_specs) == len(attn_layers), \
2704 - "All or none of the layers are expected to be encoder-only"
2714 + total_layers = 0
2715 + for attn_spec, layer_names in attn_specs.items():
2716
2706 - attn_backends = get_attn_backends_for_layers(attn_layers.keys())
2717 + attn_backends = get_attn_backends_for_layers(layer_names)
2718 + total_layers += len(layer_names)
2719
2708 - self.attn_groups.append(
2709 - create_attn_groups(attn_backends, attn_specs[0]))
2720 + self.attn_groups.append(
2721 + create_attn_groups(attn_backends, attn_spec))
2722 + assert total_layers == len(attn_layers), \
2723 + "All or none of the layers are expected to be encoder-only"
2724 self.is_encoder_only_model = True
2725
2726 def calculate_reorder_batch_threshold(self) -> None:
@@ -3071,7 +3085,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3085
3086 def _build_encoder_only_attn_metadata(
3087 self, scheduler_output: "SchedulerOutput") -> \
3074 - tuple[CommonAttentionMetadata, Any]:
3088 + dict[str, tuple[CommonAttentionMetadata, Any]]:
3089 """Prepare encoder attention metadata for encoder-only models.
3090
3091 Args:
@@ -3088,10 +3102,6 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3102 tokens = [scheduler_output.num_scheduled_tokens[i] for i in req_ids]
3103 max_num_scheduled_tokens = max(tokens)
3104
3091 - # Use the first attention metadata builder
3092 - # to create encoder attention metadata
3093 - builder = self.attn_groups[0][0].metadata_builder
3094 -
3105 dummy_block_table = torch.zeros((num_reqs, 1),
3106 dtype=torch.int32,
3107 device=self.device)
@@ -3099,22 +3109,38 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3109 dtype=torch.int32,
3110 device=self.device)
3111
3102 - common_metadata = CommonAttentionMetadata(
3103 - query_start_loc=self.query_start_loc[:num_reqs + 1],
3104 - query_start_loc_cpu=self.query_start_loc_cpu[:num_reqs + 1],
3105 - seq_lens=self.seq_lens[:num_reqs],
3106 - seq_lens_cpu=self.seq_lens_cpu[:num_reqs],
3107 - num_computed_tokens_cpu=self.input_batch.
3108 - num_computed_tokens_cpu_tensor[:num_reqs],
3109 - num_reqs=num_reqs,
3110 - num_actual_tokens=total_num_scheduled_tokens,
3111 - max_query_len=max_num_scheduled_tokens,
3112 - block_table_tensor=dummy_block_table,
3113 - slot_mapping=dummy_slot_mapping,
3114 - causal=False,
3115 - )
3112 + group_metadata = dict[str, tuple[CommonAttentionMetadata, Any]]()
3113
3117 - return common_metadata, builder.build(
3118 - common_prefix_len=0, # No cascade for encoder
3119 - common_attn_metadata=common_metadata,
3120 - )
3114 + for attn_group_list in self.attn_groups:
3115 +
3116 + assert len(attn_group_list) == 1
3117 + attn_group = attn_group_list[0]
3118 +
3119 + # Use the first attention metadata builder
3120 + # to create encoder attention metadata
3121 + builder = attn_group.metadata_builder
3122 +
3123 + common_metadata = CommonAttentionMetadata(
3124 + query_start_loc=self.query_start_loc[:num_reqs + 1],
3125 + query_start_loc_cpu=self.query_start_loc_cpu[:num_reqs + 1],
3126 + seq_lens=self.seq_lens[:num_reqs],
3127 + seq_lens_cpu=self.seq_lens_cpu[:num_reqs],
3128 + num_computed_tokens_cpu=self.input_batch.
3129 + num_computed_tokens_cpu_tensor[:num_reqs],
3130 + num_reqs=num_reqs,
3131 + num_actual_tokens=total_num_scheduled_tokens,
3132 + max_query_len=max_num_scheduled_tokens,
3133 + block_table_tensor=dummy_block_table,
3134 + slot_mapping=dummy_slot_mapping,
3135 + causal=False,
3136 + )
3137 +
3138 + metadata = builder.build(
3139 + common_prefix_len=0, # No cascade for encoder
3140 + common_attn_metadata=common_metadata,
3141 + )
3142 +
3143 + for layer_name in attn_group.layer_names:
3144 + group_metadata[layer_name] = (common_metadata, metadata)
3145 +
3146 + return group_metadata