@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / a597a57595
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 15, 18:30 UTC

[Attention] Flash Attention 3 - fp8 (#14570)

Signed-off-by: Mickael Seznec <mickael@mistral.ai>

Mickaël Seznec committed Mar 20, 2025 at 06:14 UTC a597a57595b5b6bb5eca63ed510468a21e683cbd
15 files changed +272 -76
cmake/external_projects/vllm_flash_attn.cmake
+1 -1
@@ -38,7 +38,7 @@ else()
38 FetchContent_Declare(
39 vllm-flash-attn
40 GIT_REPOSITORY https://github.com/vllm-project/flash-attention.git
41 - GIT_TAG 9bfa9869829d8c593527eb34c5271d0090f7ccc9
41 + GIT_TAG dc9d410b3e2d6534a4c70724c2515f4def670a22
42 GIT_PROGRESS TRUE
43 # Don't share the vllm-flash-attn build between build types
44 BINARY_DIR ${CMAKE_BINARY_DIR}/vllm-flash-attn
tests/kernels/test_flash_attn.py
+68 -8
@@ -15,6 +15,7 @@ NUM_HEADS = [(4, 4), (8, 2), (16, 2)]
15 HEAD_SIZES = [128, 256]
16 BLOCK_SIZES = [16, 32]
17 DTYPES = [torch.float16, torch.bfloat16]
18 +QDTYPES = [None, torch.float8_e4m3fn]
19 # one value large enough to test overflow in index calculation.
20 # one value small enough to test the schema op check
21 NUM_BLOCKS = [32768, 2048]
@@ -85,6 +86,7 @@ def ref_paged_attn(
86 @pytest.mark.parametrize("num_blocks", NUM_BLOCKS)
87 @pytest.mark.parametrize("sliding_window", [None, 256])
88 @pytest.mark.parametrize("fa_version", [2, 3])
89 +@pytest.mark.parametrize("q_dtype", QDTYPES)
90 @torch.inference_mode()
91 def test_flash_attn_with_paged_kv(
92 use_out: bool,
@@ -97,11 +99,15 @@ def test_flash_attn_with_paged_kv(
99 num_blocks: int,
100 sliding_window: Optional[int],
101 fa_version: int,
102 + q_dtype: Optional[torch.dtype],
103 ) -> None:
104 torch.set_default_device("cuda")
105 if not is_fa_version_supported(fa_version):
106 pytest.skip(f"Flash attention version {fa_version} not supported due "
107 f"to: \"{fa_version_unsupported_reason(fa_version)}\"")
108 + if q_dtype is not None and (dtype != torch.bfloat16 or fa_version == 2):
109 + pytest.skip("Flash attention with quantized inputs is only "
110 + "supported on version 3 with bfloat16 base type")
111
112 current_platform.seed_everything(0)
113 num_seqs = len(kv_lens)
@@ -130,10 +136,28 @@ def test_flash_attn_with_paged_kv(
136
137 q = query.unsqueeze(1)
138 out = torch.empty_like(q) if use_out else None
139 +
140 + maybe_quantized_query = q
141 + maybe_quantized_key_cache = key_cache
142 + maybe_quantized_value_cache = value_cache
143 + q_descale = None
144 + k_descale = None
145 + v_descale = None
146 + if q_dtype is not None:
147 + # QKV are drawn from N(0, 1): no need for a fp8 scaling factor
148 + maybe_quantized_query = query.to(q_dtype)
149 + maybe_quantized_key_cache = key_cache.to(q_dtype)
150 + maybe_quantized_value_cache = value_cache.to(q_dtype)
151 +
152 + scale_shape = (num_seqs, num_kv_heads)
153 + q_descale = torch.ones(scale_shape, dtype=torch.float32)
154 + k_descale = torch.ones(scale_shape, dtype=torch.float32)
155 + v_descale = torch.ones(scale_shape, dtype=torch.float32)
156 +
157 output = flash_attn_with_kvcache(
134 - q=q,
135 - k_cache=key_cache,
136 - v_cache=value_cache,
158 + q=maybe_quantized_query,
159 + k_cache=maybe_quantized_key_cache,
160 + v_cache=maybe_quantized_value_cache,
161 out=out,
162 softmax_scale=scale,
163 causal=True,
@@ -142,10 +166,17 @@ def test_flash_attn_with_paged_kv(
166 softcap=soft_cap if soft_cap is not None else 0,
167 window_size=window_size,
168 fa_version=fa_version,
169 + q_descale=q_descale,
170 + k_descale=k_descale,
171 + v_descale=v_descale,
172 )
173 output = output if not use_out else out
174 output = output.squeeze(1)
175
176 + atol, rtol = 1.5e-2, 1e-2
177 + if q_dtype is not None:
178 + atol, rtol = 1.5e-1, 1.5e-1
179 +
180 ref_output = ref_paged_attn(query=query,
181 key_cache=key_cache,
182 value_cache=value_cache,
@@ -155,7 +186,7 @@ def test_flash_attn_with_paged_kv(
186 scale=scale,
187 soft_cap=soft_cap,
188 sliding_window=sliding_window)
158 - torch.testing.assert_close(output, ref_output, atol=2e-2, rtol=1e-2), \
189 + torch.testing.assert_close(output, ref_output, atol=atol, rtol=rtol), \
190 f"{torch.max(torch.abs(output - ref_output))}"
191
192
@@ -171,6 +202,7 @@ def test_flash_attn_with_paged_kv(
202 @pytest.mark.parametrize("soft_cap", [None, 10.0, 50.0])
203 @pytest.mark.parametrize("num_blocks", NUM_BLOCKS)
204 @pytest.mark.parametrize("fa_version", [2, 3])
205 +@pytest.mark.parametrize("q_dtype", QDTYPES)
206 @torch.inference_mode()
207 def test_varlen_with_paged_kv(
208 use_out: bool,
@@ -183,11 +215,15 @@ def test_varlen_with_paged_kv(
215 soft_cap: Optional[float],
216 num_blocks: int,
217 fa_version: int,
218 + q_dtype: Optional[torch.dtype],
219 ) -> None:
220 torch.set_default_device("cuda")
221 if not is_fa_version_supported(fa_version):
222 pytest.skip(f"Flash attention version {fa_version} not supported due "
223 f"to: \"{fa_version_unsupported_reason(fa_version)}\"")
224 + if q_dtype is not None and (dtype != torch.bfloat16 or fa_version == 2):
225 + pytest.skip("Flash attention with quantized inputs is only "
226 + "supported on version 3 with bfloat16 base type")
227 current_platform.seed_everything(0)
228 num_seqs = len(seq_lens)
229 query_lens = [x[0] for x in seq_lens]
@@ -223,10 +259,28 @@ def test_varlen_with_paged_kv(
259 dtype=torch.int32)
260
261 out = torch.empty_like(query) if use_out else None
262 +
263 + maybe_quantized_query = query
264 + maybe_quantized_key_cache = key_cache
265 + maybe_quantized_value_cache = value_cache
266 + q_descale = None
267 + k_descale = None
268 + v_descale = None
269 + if q_dtype is not None:
270 + # QKV are drawn from N(0, 1): no need for a fp8 scaling factor
271 + maybe_quantized_query = query.to(q_dtype)
272 + maybe_quantized_key_cache = key_cache.to(q_dtype)
273 + maybe_quantized_value_cache = value_cache.to(q_dtype)
274 +
275 + scale_shape = (num_seqs, num_kv_heads)
276 + q_descale = torch.ones(scale_shape, dtype=torch.float32)
277 + k_descale = torch.ones(scale_shape, dtype=torch.float32)
278 + v_descale = torch.ones(scale_shape, dtype=torch.float32)
279 +
280 output = flash_attn_varlen_func(
227 - q=query,
228 - k=key_cache,
229 - v=value_cache,
281 + q=maybe_quantized_query,
282 + k=maybe_quantized_key_cache,
283 + v=maybe_quantized_value_cache,
284 out=out,
285 cu_seqlens_q=cu_query_lens,
286 seqused_k=kv_lens,
@@ -238,6 +292,9 @@ def test_varlen_with_paged_kv(
292 block_table=block_tables,
293 softcap=soft_cap if soft_cap is not None else 0,
294 fa_version=fa_version,
295 + q_descale=q_descale,
296 + k_descale=k_descale,
297 + v_descale=v_descale,
298 )
299 output = output if not use_out else out
300
@@ -252,5 +309,8 @@ def test_varlen_with_paged_kv(
309 sliding_window=sliding_window,
310 soft_cap=soft_cap,
311 )
255 - torch.testing.assert_close(output, ref_output, atol=2e-2, rtol=1e-2), \
312 + atol, rtol = 1.5e-2, 1e-2
313 + if q_dtype is not None:
314 + atol, rtol = 1.5e-1, 1.5e-1
315 + torch.testing.assert_close(output, ref_output, atol=atol, rtol=rtol), \
316 f"{torch.max(torch.abs(output - ref_output))}"
vllm/attention/__init__.py
+8 -4
@@ -4,12 +4,16 @@ from vllm.attention.backends.abstract import (AttentionBackend,
4 AttentionMetadata,
5 AttentionMetadataBuilder,
6 AttentionState, AttentionType)
7 -from vllm.attention.backends.utils import get_flash_attn_version
7 from vllm.attention.layer import Attention
8 from vllm.attention.selector import get_attn_backend
9
10 __all__ = [
12 - "Attention", "AttentionBackend", "AttentionMetadata", "AttentionType",
13 - "AttentionMetadataBuilder", "Attention", "AttentionState",
14 - "get_attn_backend", "get_flash_attn_version"
11 + "Attention",
12 + "AttentionBackend",
13 + "AttentionMetadata",
14 + "AttentionType",
15 + "AttentionMetadataBuilder",
16 + "Attention",
17 + "AttentionState",
18 + "get_attn_backend",
19 ]
vllm/attention/backends/abstract.py
+1
@@ -232,6 +232,7 @@ class AttentionMetadataBuilder(ABC, Generic[T]):
232
233 class AttentionLayer(Protocol):
234
235 + _q_scale: torch.Tensor
236 _k_scale: torch.Tensor
237 _v_scale: torch.Tensor
238 _k_scale_float: float
vllm/attention/backends/flash_attn.py
+68 -11
@@ -19,10 +19,10 @@ from vllm.attention.backends.abstract import (AttentionBackend, AttentionImpl,
19 # yapf: enable
20 from vllm.attention.backends.utils import (
21 PAD_SLOT_ID, CommonAttentionState, compute_slot_mapping,
22 - compute_slot_mapping_start_idx, get_flash_attn_version,
23 - get_num_prefill_decode_query_kv_tokens, get_seq_len_block_table_args,
24 - is_all_cross_attn_metadata_set, is_all_encoder_attn_metadata_set,
25 - is_block_tables_empty)
22 + compute_slot_mapping_start_idx, get_num_prefill_decode_query_kv_tokens,
23 + get_seq_len_block_table_args, is_all_cross_attn_metadata_set,
24 + is_all_encoder_attn_metadata_set, is_block_tables_empty)
25 +from vllm.fa_utils import get_flash_attn_version
26 from vllm.logger import init_logger
27 from vllm.multimodal import MultiModalPlaceholderMap
28 from vllm.utils import async_tensor_h2d, make_tensor_with_pad
@@ -630,9 +630,11 @@ class FlashAttentionImpl(AttentionImpl):
630 self.sliding_window = ((sliding_window - 1,
631 0) if sliding_window is not None else (-1, -1))
632 self.kv_cache_dtype = kv_cache_dtype
633 - if is_quantized_kv_cache(self.kv_cache_dtype):
633 + self.vllm_flash_attn_version = get_flash_attn_version()
634 + if (is_quantized_kv_cache(self.kv_cache_dtype)
635 + and self.vllm_flash_attn_version != 3):
636 raise NotImplementedError(
635 - "FlashAttention with FP8 KV cache not yet supported")
637 + "Only FlashAttention3 supports FP8 KV cache")
638 if logits_soft_cap is None:
639 # In flash-attn, setting logits_soft_cap as 0 means no soft cap.
640 logits_soft_cap = 0
@@ -647,7 +649,6 @@ class FlashAttentionImpl(AttentionImpl):
649 f"Head size {head_size} is not supported by FlashAttention. "
650 f"Supported head sizes are: {support_head_sizes}.")
651 self.attn_type = attn_type
650 - self.vllm_flash_attn_version = get_flash_attn_version()
652
653 def forward(
654 self,
@@ -671,13 +672,19 @@ class FlashAttentionImpl(AttentionImpl):
672 for profiling run.
673 attn_metadata: Metadata for attention.
674 NOTE: It in-place updates the output tensor.
675 + NOTE: FP8 quantization, flash-attn expect the size of
676 + {q,k,v}_descale to be (num_sequences, num_kv_heads).
677 + We use torch's .expand() to avoid duplicating values
678 """
675 - # NOTE(woosuk): FlashAttention does not support FP8 KV cache.
676 - assert layer._k_scale_float == 1.0 and layer._v_scale_float == 1.0, (
677 - "key/v_scale is not supported in FlashAttention.")
678 -
679 assert output is not None, "Output tensor must be provided."
680
681 + # NOTE(woosuk): FlashAttention2 does not support FP8 KV cache.
682 + if self.vllm_flash_attn_version < 3 or output.dtype != torch.bfloat16:
683 + assert (
684 + layer._k_scale_float == 1.0 and layer._v_scale_float == 1.0), (
685 + "key/v_scale is only supported in FlashAttention 3 with "
686 + "base dtype bfloat16")
687 +
688 attn_type = self.attn_type
689 if (attn_type == AttentionType.ENCODER
690 and (not attn_metadata.is_all_encoder_attn_metadata_set)):
@@ -694,6 +701,7 @@ class FlashAttentionImpl(AttentionImpl):
701 window_size = self.sliding_window
702 alibi_slopes: Optional[torch.Tensor] = self.alibi_slopes
703 logits_soft_cap: Optional[float] = self.logits_soft_cap
704 + fp8_attention = kv_cache_dtype.startswith("fp8")
705
706 if kv_cache.numel() > 0:
707 key_cache = kv_cache[0]
@@ -729,6 +737,19 @@ class FlashAttentionImpl(AttentionImpl):
737 layer._v_scale,
738 )
739
740 + if fp8_attention:
741 + kv_cache = kv_cache.view(torch.float8_e4m3fn)
742 + key_cache = key_cache.view(torch.float8_e4m3fn)
743 + value_cache = value_cache.view(torch.float8_e4m3fn)
744 +
745 + if fp8_attention:
746 + num_tokens, num_heads, head_size = query.shape
747 + query, _ = ops.scaled_fp8_quant(
748 + query.reshape(
749 + (num_tokens, num_heads * head_size)).contiguous(),
750 + layer._q_scale)
751 + query = query.reshape((num_tokens, num_heads, head_size))
752 +
753 (num_prefill_query_tokens, num_prefill_kv_tokens,
754 num_decode_query_tokens) = \
755 get_num_prefill_decode_query_kv_tokens(attn_metadata, attn_type)
@@ -753,6 +774,23 @@ class FlashAttentionImpl(AttentionImpl):
774 key = key[:num_prefill_kv_tokens]
775 value = value[:num_prefill_kv_tokens]
776
777 + if fp8_attention:
778 + num_kv_tokens, num_kv_heads, head_size = key.shape
779 +
780 + key, _ = ops.scaled_fp8_quant(
781 + key.reshape((num_kv_tokens,
782 + num_kv_heads * head_size)).contiguous(),
783 + layer._k_scale)
784 + key = key.reshape((num_kv_tokens, num_kv_heads, head_size))
785 +
786 + value, _ = ops.scaled_fp8_quant(
787 + value.reshape((num_kv_tokens,
788 + num_kv_heads * head_size)).contiguous(),
789 + layer._v_scale)
790 + value = value.reshape(
791 + (num_kv_tokens, num_kv_heads, head_size))
792 +
793 + descale_shape = (q_seq_start_loc.shape[0] - 1, key.shape[1])
794 flash_attn_varlen_func(
795 q=query,
796 k=key,
@@ -768,13 +806,19 @@ class FlashAttentionImpl(AttentionImpl):
806 softcap=logits_soft_cap,
807 out=prefill_output,
808 fa_version=self.vllm_flash_attn_version,
809 + q_descale=layer._q_scale.expand(descale_shape),
810 + k_descale=layer._k_scale.expand(descale_shape),
811 + v_descale=layer._v_scale.expand(descale_shape),
812 )
813 else:
814 # prefix-enabled attention
815 assert attn_type == AttentionType.DECODER, (
816 "Only decoder-only models support prefix caching")
817 assert prefill_meta.seq_lens is not None
818 + assert prefill_meta.query_start_loc is not None
819 max_seq_len = max(prefill_meta.seq_lens)
820 + descale_shape = (prefill_meta.query_start_loc.shape[0] - 1,
821 + key.shape[1])
822 flash_attn_varlen_func( # noqa
823 q=query,
824 k=key_cache,
@@ -791,6 +835,9 @@ class FlashAttentionImpl(AttentionImpl):
835 softcap=logits_soft_cap,
836 out=prefill_output,
837 fa_version=self.vllm_flash_attn_version,
838 + q_descale=layer._q_scale.expand(descale_shape),
839 + k_descale=layer._k_scale.expand(descale_shape),
840 + v_descale=layer._v_scale.expand(descale_shape),
841 )
842
843 if decode_meta := attn_metadata.decode_metadata:
@@ -804,6 +851,9 @@ class FlashAttentionImpl(AttentionImpl):
851 assert attn_type == AttentionType.DECODER, (
852 "Only decoder-only models support max_decode_query_len > 1"
853 )
854 + assert decode_meta.query_start_loc is not None
855 + descale_shape = (decode_meta.query_start_loc.shape[0] - 1,
856 + key.shape[1])
857 flash_attn_varlen_func(
858 q=decode_query,
859 k=key_cache,
@@ -820,6 +870,9 @@ class FlashAttentionImpl(AttentionImpl):
870 block_table=decode_meta.block_tables,
871 out=decode_output,
872 fa_version=self.vllm_flash_attn_version,
873 + q_descale=layer._q_scale.expand(descale_shape),
874 + k_descale=layer._k_scale.expand(descale_shape),
875 + v_descale=layer._v_scale.expand(descale_shape),
876 )
877 else:
878 # Use flash_attn_with_kvcache for normal decoding.
@@ -828,6 +881,7 @@ class FlashAttentionImpl(AttentionImpl):
881 _,
882 block_tables_arg,
883 ) = get_seq_len_block_table_args(decode_meta, False, attn_type)
884 + descale_shape = (seq_lens_arg.shape[0], key_cache.shape[-2])
885 flash_attn_with_kvcache(
886 q=decode_query.unsqueeze(1),
887 k_cache=key_cache,
@@ -841,6 +895,9 @@ class FlashAttentionImpl(AttentionImpl):
895 softcap=logits_soft_cap,
896 out=decode_output.unsqueeze(1),
897 fa_version=self.vllm_flash_attn_version,
898 + q_descale=layer._q_scale.expand(descale_shape),
899 + k_descale=layer._k_scale.expand(descale_shape),
900 + v_descale=layer._v_scale.expand(descale_shape),
901 )
902 return output
903
vllm/attention/backends/mla/common.py
+1 -1
@@ -203,9 +203,9 @@ from vllm.attention.backends.abstract import (AttentionBackend, AttentionLayer,
203 AttentionState, MLAAttentionImpl)
204 from vllm.attention.backends.utils import (PAD_SLOT_ID, compute_slot_mapping,
205 compute_slot_mapping_start_idx,
206 - get_flash_attn_version,
206 is_block_tables_empty)
207 from vllm.attention.ops.triton_merge_attn_states import merge_attn_states
208 +from vllm.fa_utils import get_flash_attn_version
209 from vllm.model_executor.layers.linear import (ColumnParallelLinear,
210 LinearBase, RowParallelLinear,
211 UnquantizedLinearMethod)
vllm/attention/backends/utils.py
-34
@@ -8,13 +8,11 @@ from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, TypeVar, Union
8 import numpy as np
9 import torch
10
11 -from vllm import envs
11 from vllm.attention import (AttentionMetadata, AttentionMetadataBuilder,
12 AttentionState)
13 from vllm.attention.backends.abstract import AttentionType
14 from vllm.logger import init_logger
15 from vllm.multimodal import MultiModalPlaceholderMap
17 -from vllm.platforms import current_platform
16 from vllm.utils import async_tensor_h2d, make_tensor_with_pad
17
18 logger = init_logger(__name__)
@@ -585,35 +583,3 @@ def get_num_prefill_decode_query_kv_tokens(
583
584 return (num_prefill_query_tokens, num_prefill_kv_tokens,
585 num_decode_query_tokens)
588 -
589 -
590 -def get_flash_attn_version():
591 - try:
592 - from vllm.vllm_flash_attn.flash_attn_interface import (
593 - fa_version_unsupported_reason, is_fa_version_supported)
594 -
595 - # if hopper default to FA3, otherwise stick to FA2 for now
596 - # TODO(lucas): profile FA3 on ampere to see if it makes sense to
597 - # use FA3 as default for both
598 - if current_platform.get_device_capability()[0] == 9:
599 - fa_version = 3 if is_fa_version_supported(3) else 2
600 - else:
601 - fa_version = 2
602 -
603 - if envs.VLLM_FLASH_ATTN_VERSION is not None:
604 - assert envs.VLLM_FLASH_ATTN_VERSION in [2, 3]
605 - fa_version = envs.VLLM_FLASH_ATTN_VERSION
606 - if (current_platform.get_device_capability()[0] == 10
607 - and envs.VLLM_FLASH_ATTN_VERSION == 3):
608 - logger.warning("Cannot use FA version 3 on Blackwell platform",
609 - "defaulting to FA version 2.")
610 - fa_version = 2
611 -
612 - if not is_fa_version_supported(fa_version):
613 - logger.error("Cannot use FA version %d is not supported due to %s",
614 - fa_version, fa_version_unsupported_reason(fa_version))
615 -
616 - assert is_fa_version_supported(fa_version)
617 - return fa_version
618 - except (ImportError, AssertionError):
619 - return None
vllm/attention/layer.py
+7 -2
@@ -84,6 +84,9 @@ class Attention(nn.Module):
84 self.calculate_kv_scales = calculate_kv_scales
85 self._k_scale = torch.tensor(1.0, dtype=torch.float32)
86 self._v_scale = torch.tensor(1.0, dtype=torch.float32)
87 + # FlashAttn doesn't support quantizing the kv-cache only
88 + # but requires q to be quantized as well.
89 + self._q_scale = torch.tensor(1.0, dtype=torch.float32)
90
91 # We also keep the float32 versions of k/v_scale for attention
92 # backends that don't support tensors (Flashinfer)
@@ -153,6 +156,7 @@ class Attention(nn.Module):
156 ).parallel_config.pipeline_parallel_size)
157 ]
158
159 + self.q_range = torch.tensor(envs.Q_SCALE_CONSTANT, dtype=torch.float32)
160 self.k_range = torch.tensor(envs.K_SCALE_CONSTANT, dtype=torch.float32)
161 self.v_range = torch.tensor(envs.V_SCALE_CONSTANT, dtype=torch.float32)
162
@@ -178,7 +182,7 @@ class Attention(nn.Module):
182 if self.calculate_kv_scales:
183 attn_metadata = get_forward_context().attn_metadata
184 if attn_metadata.enable_kv_scales_calculation:
181 - self.calc_kv_scales(key, value)
185 + self.calc_kv_scales(query, key, value)
186 if self.use_output:
187 output_shape = (output_shape
188 if output_shape is not None else query.shape)
@@ -225,7 +229,8 @@ class Attention(nn.Module):
229 return torch.ops.vllm.unified_attention(
230 query, key, value, self.layer_name)
231
228 - def calc_kv_scales(self, key, value):
232 + def calc_kv_scales(self, query, key, value):
233 + self._q_scale.copy_(torch.abs(query).max() / self.q_range)
234 self._k_scale.copy_(torch.abs(key).max() / self.k_range)
235 self._v_scale.copy_(torch.abs(value).max() / self.v_range)
236 self._k_scale_float = self._k_scale.item()
vllm/envs.py
+6 -1
@@ -78,6 +78,7 @@ if TYPE_CHECKING:
78 VLLM_ENABLE_V1_MULTIPROCESSING: bool = True
79 VLLM_LOG_BATCHSIZE_INTERVAL: float = -1
80 VLLM_DISABLE_COMPILE_CACHE: bool = False
81 + Q_SCALE_CONSTANT: int = 200
82 K_SCALE_CONSTANT: int = 200
83 V_SCALE_CONSTANT: int = 100
84 VLLM_SERVER_DEV_MODE: bool = False
@@ -524,13 +525,17 @@ environment_variables: dict[str, Callable[[], Any]] = {
525 # Pad the fp8 weights to 256 bytes for ROCm
526 "VLLM_ROCM_FP8_PADDING":
527 lambda: bool(int(os.getenv("VLLM_ROCM_FP8_PADDING", "1"))),
528 +
529 + # Divisor for dynamic query scale factor calculation for FP8 KV Cache
530 + "Q_SCALE_CONSTANT":
531 + lambda: int(os.getenv("Q_SCALE_CONSTANT", "200")),
532 # Divisor for dynamic key scale factor calculation for FP8 KV Cache
533 "K_SCALE_CONSTANT":
534 lambda: int(os.getenv("K_SCALE_CONSTANT", "200")),
530 -
535 # Divisor for dynamic value scale factor calculation for FP8 KV Cache
536 "V_SCALE_CONSTANT":
537 lambda: int(os.getenv("V_SCALE_CONSTANT", "100")),
538 +
539 # If set, enable multiprocessing in LLM for the V1 code path.
540 "VLLM_ENABLE_V1_MULTIPROCESSING":
541 lambda: bool(int(os.getenv("VLLM_ENABLE_V1_MULTIPROCESSING", "1"))),
vllm/fa_utils.py new
+42
@@ -0,0 +1,42 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +from typing import Optional
3 +
4 +from vllm import envs
5 +from vllm.logger import init_logger
6 +
7 +logger = init_logger(__name__)
8 +
9 +
10 +def get_flash_attn_version() -> Optional[int]:
11 + # import here to avoid circular dependencies
12 + from vllm.platforms import current_platform
13 + try:
14 + from vllm.vllm_flash_attn.flash_attn_interface import (
15 + fa_version_unsupported_reason, is_fa_version_supported)
16 + device_capability = current_platform.get_device_capability()
17 +
18 + assert device_capability is not None
19 +
20 + # 1. default version depending on platform
21 + fa_version = 3 if (device_capability.major == 9
22 + and is_fa_version_supported(3)) else 2
23 +
24 + # 2. override if passed by environment
25 + if envs.VLLM_FLASH_ATTN_VERSION is not None:
26 + assert envs.VLLM_FLASH_ATTN_VERSION in [2, 3]
27 + fa_version = envs.VLLM_FLASH_ATTN_VERSION
28 +
29 + # 3. fallback for unsupported combinations
30 + if device_capability.major == 10 and fa_version == 3:
31 + logger.warning("Cannot use FA version 3 on Blackwell platform",
32 + "defaulting to FA version 2.")
33 + fa_version = 2
34 +
35 + if not is_fa_version_supported(fa_version):
36 + logger.error("Cannot use FA version %d is not supported due to %s",
37 + fa_version, fa_version_unsupported_reason(fa_version))
38 +
39 + assert is_fa_version_supported(fa_version)
40 + return fa_version
41 + except (ImportError, AssertionError):
42 + return None
vllm/model_executor/layers/quantization/kv_cache.py
+13 -3
@@ -26,11 +26,14 @@ class BaseKVCacheMethod(QuantizeMethodBase):
26
27 def create_weights(self, layer: torch.nn.Module):
28 """
29 - Create "weight" (aka k_scale and v_scale) for an attention layer.
29 + Create "weight" (aka q_scale, k_scale and v_scale)
30 + for an attention layer.
31 """
31 - # Initialize the KV cache scales to -1.0, which is an invalid value.
32 - # If the k/v_scale appears in the checkpoint, it will be
32 + # Initialize the Q and KV cache scales to -1.0, an invalid value.
33 + # If the q and k/v_scales appear in the checkpoint, it will be
34 # overwritten when loading weights.
35 + layer.q_scale = torch.nn.Parameter(torch.tensor(-1.0),
36 + requires_grad=False)
37 layer.k_scale = torch.nn.Parameter(torch.tensor(-1.0),
38 requires_grad=False)
39 layer.v_scale = torch.nn.Parameter(torch.tensor(-1.0),
@@ -75,6 +78,13 @@ class BaseKVCacheMethod(QuantizeMethodBase):
78 raise ValueError("Only support per-tensor scaling factor "
79 "for fp8 KV cache")
80
81 + if layer.q_scale < 0.0:
82 + logger.warning_once(
83 + "Checkpoint does not provide a q scaling factor. "
84 + "Setting it to k_scale. This only matters for "
85 + "the flash-attn backend.")
86 + layer._q_scale.copy_(k_scale)
87 +
88 # These are used in the final Attention.forward()
89 layer._k_scale.copy_(k_scale)
90 layer._v_scale.copy_(v_scale)
vllm/platforms/cuda.py
+12 -9
@@ -14,6 +14,7 @@ from typing_extensions import ParamSpec
14 # import custom ops, trigger op registration
15 import vllm._C # noqa
16 import vllm.envs as envs
17 +from vllm.fa_utils import get_flash_attn_version
18 from vllm.logger import init_logger
19 from vllm.utils import import_pynvml
20
@@ -240,15 +241,6 @@ class CudaPlatformBase(Platform):
241 "Cannot use FlashAttention-2 backend for dtype other than "
242 "torch.float16 or torch.bfloat16.")
243 target_backend = _Backend.XFORMERS
243 - elif kv_cache_dtype is not None and \
244 - kv_cache_dtype.startswith("fp8"):
245 - logger.info(
246 - "Cannot use FlashAttention-2 backend for FP8 KV cache.")
247 - logger.warning(
248 - "Please use FlashInfer backend with FP8 KV Cache for "
249 - "better performance by setting environment variable "
250 - "VLLM_ATTENTION_BACKEND=FLASHINFER")
251 - target_backend = _Backend.XFORMERS
244 elif block_size % 16 != 0:
245 logger.info(
246 "Cannot use FlashAttention-2 backend for block size not "
@@ -270,6 +262,17 @@ class CudaPlatformBase(Platform):
262 "Cannot use FlashAttention-2 backend for head size %d.",
263 head_size)
264 target_backend = _Backend.XFORMERS
265 + fp8_kv_cache = (kv_cache_dtype is not None
266 + and kv_cache_dtype.startswith("fp8"))
267 + if (fp8_kv_cache and get_flash_attn_version() != 3):
268 + logger.info(
269 + "Cannot use FlashAttention-2 backend for FP8 KV cache."
270 + )
271 + logger.warning(
272 + "Please use FlashInfer backend with FP8 KV Cache for "
273 + "better performance by setting environment variable "
274 + "VLLM_ATTENTION_BACKEND=FLASHINFER")
275 + target_backend = _Backend.XFORMERS
276 except ImportError:
277 logger.info(
278 "Cannot use FlashAttention-2 backend because the "
vllm/v1/attention/backends/flash_attn.py
+40 -1
@@ -6,11 +6,12 @@ from typing import TYPE_CHECKING, Any, Optional
6 import numpy as np
7 import torch
8
9 +from vllm import _custom_ops as ops
10 from vllm.attention.backends.abstract import (AttentionBackend, AttentionImpl,
11 AttentionMetadata, AttentionType,
12 is_quantized_kv_cache)
12 -from vllm.attention.backends.utils import get_flash_attn_version
13 from vllm.attention.ops.triton_merge_attn_states import merge_attn_states
14 +from vllm.fa_utils import get_flash_attn_version
15 from vllm.logger import init_logger
16 from vllm.platforms import current_platform
17 from vllm.utils import cdiv
@@ -226,6 +227,9 @@ class FlashAttentionImpl(AttentionImpl):
227 attn_metadata: Metadata for attention.
228 Returns:
229 shape = [num_tokens, num_heads * head_size]
230 + NOTE: FP8 quantization, flash-attn expect the size of
231 + {q,k,v}_descale to be (num_sequences, num_kv_heads).
232 + We use torch's .expand() to avoid duplicating values
233 """
234 assert output is not None, "Output tensor must be provided."
235
@@ -259,6 +263,17 @@ class FlashAttentionImpl(AttentionImpl):
263 layer._k_scale,
264 layer._v_scale,
265 )
266 + descale_shape = (attn_metadata.query_start_loc.shape[0] - 1,
267 + key.shape[1])
268 + if self.kv_cache_dtype.startswith("fp8"):
269 + key_cache = key_cache.view(torch.float8_e4m3fn)
270 + value_cache = value_cache.view(torch.float8_e4m3fn)
271 + num_tokens, num_heads, head_size = query.shape
272 + query, _ = ops.scaled_fp8_quant(
273 + query.reshape(
274 + (num_tokens, num_heads * head_size)).contiguous(),
275 + layer._q_scale)
276 + query = query.reshape((num_tokens, num_heads, head_size))
277
278 # Compute attention and update output up to `num_actual_tokens`.
279 if not attn_metadata.use_cascade:
@@ -279,6 +294,9 @@ class FlashAttentionImpl(AttentionImpl):
294 block_table=attn_metadata.block_table,
295 softcap=self.logits_soft_cap,
296 fa_version=self.vllm_flash_attn_version,
297 + q_descale=layer._q_scale.expand(descale_shape),
298 + k_descale=layer._k_scale.expand(descale_shape),
299 + v_descale=layer._v_scale.expand(descale_shape),
300 )
301 return output
302
@@ -301,6 +319,9 @@ class FlashAttentionImpl(AttentionImpl):
319 block_table=attn_metadata.block_table,
320 common_prefix_len=attn_metadata.common_prefix_len,
321 fa_version=self.vllm_flash_attn_version,
322 + q_descale=layer._q_scale,
323 + k_descale=layer._k_scale,
324 + v_descale=layer._v_scale,
325 )
326 return output
327
@@ -391,6 +412,9 @@ def cascade_attention(
412 block_table: torch.Tensor,
413 common_prefix_len: int,
414 fa_version: int,
415 + q_descale: Optional[torch.Tensor] = None,
416 + k_descale: Optional[torch.Tensor] = None,
417 + v_descale: Optional[torch.Tensor] = None,
418 ) -> torch.Tensor:
419 assert alibi_slopes is None, ("Cascade attention does not support ALiBi.")
420 # TODO: Support sliding window.
@@ -402,6 +426,7 @@ def cascade_attention(
426 assert common_prefix_len % block_size == 0
427 num_common_kv_blocks = common_prefix_len // block_size
428 assert num_common_kv_blocks > 0
429 + descale_shape = (cu_prefix_query_lens.shape[0] - 1, key_cache.shape[-2])
430
431 # Process shared prefix.
432 prefix_output, prefix_lse = flash_attn_varlen_func(
@@ -419,8 +444,16 @@ def cascade_attention(
444 softcap=logits_soft_cap,
445 return_softmax_lse=True,
446 fa_version=fa_version,
447 + q_descale=q_descale.expand(descale_shape)
448 + if q_descale is not None else None,
449 + k_descale=k_descale.expand(descale_shape)
450 + if k_descale is not None else None,
451 + v_descale=v_descale.expand(descale_shape)
452 + if v_descale is not None else None,
453 )
454
455 + descale_shape = (cu_query_lens.shape[0] - 1, key_cache.shape[-2])
456 +
457 # Process suffix per query.
458 suffix_output, suffix_lse = flash_attn_varlen_func(
459 q=query,
@@ -437,6 +470,12 @@ def cascade_attention(
470 softcap=logits_soft_cap,
471 return_softmax_lse=True,
472 fa_version=fa_version,
473 + q_descale=q_descale.expand(descale_shape)
474 + if q_descale is not None else None,
475 + k_descale=k_descale.expand(descale_shape)
476 + if k_descale is not None else None,
477 + v_descale=v_descale.expand(descale_shape)
478 + if v_descale is not None else None,
479 )
480
481 # Merge prefix and suffix outputs, and store the result in output.
vllm/v1/executor/multiproc_executor.py
+4
@@ -5,6 +5,7 @@ import pickle
5 import signal
6 import sys
7 import time
8 +import traceback
9 import weakref
10 from dataclasses import dataclass
11 from enum import Enum, auto
@@ -370,6 +371,9 @@ class WorkerProc:
371 func = partial(cloudpickle.loads(method), self.worker)
372 output = func(*args, **kwargs)
373 except Exception as e:
374 + # Notes have been introduced in python 3.11
375 + if hasattr(e, "add_note"):
376 + e.add_note(traceback.format_exc())
377 self.worker_response_mq.enqueue(
378 (WorkerProc.ResponseStatus.FAILURE, e))
379 logger.exception("WorkerProc hit an exception: %s", exc_info=e)
vllm/v1/worker/gpu_model_runner.py
+1 -1
@@ -1558,7 +1558,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1558 block_size=block_size,
1559 num_kv_heads=attn_module.num_kv_heads,
1560 head_size=attn_module.head_size,
1561 - dtype=attn_module.dtype,
1561 + dtype=self.kv_cache_dtype,
1562 use_mla=use_mla)
1563 elif attn_module.attn_type in (AttentionType.ENCODER,
1564 AttentionType.ENCODER_ONLY):