flashinfer.py 20.5 KB
Newer Older
1
2
3
4
5
6
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Compatibility wrapper for FlashInfer API changes.

Users of vLLM should always import **only** these wrappers.
"""
7

8
9
10
11
import contextlib
import functools
import importlib
import importlib.util
12
import os
13
import shutil
14
15
from collections.abc import Callable
from typing import Any, NoReturn
16

17
import requests
18
import torch
19
20

import vllm.envs as envs
21
from vllm.logger import init_logger
22
23
24
from vllm.model_executor.layers.batch_invariant import (
    vllm_is_batch_invariant,
)
25
from vllm.platforms import current_platform
26
27
28

logger = init_logger(__name__)

29
30
31
32
33
34
35
36
# This is the storage path for the cubins, it can be replaced
# with a local path for testing.
# Referenced from https://github.com/flashinfer-ai/flashinfer/blob/0c9a92c3d9a7e043ab6f3f7b2273269caf6ab044/flashinfer/jit/cubin_loader.py#L35  # noqa: E501
FLASHINFER_CUBINS_REPOSITORY = os.environ.get(
    "FLASHINFER_CUBINS_REPOSITORY",
    "https://edge.urm.nvidia.com/artifactory/sw-kernelinferencelibrary-public-generic-local/",  # noqa: E501
)

37

38
39
40
41
42
43
44
45
46
47
48
@functools.cache
def has_flashinfer_cubin() -> bool:
    """Return `True` if flashinfer-cubin package is available."""
    if envs.VLLM_HAS_FLASHINFER_CUBIN:
        return True
    if importlib.util.find_spec("flashinfer_cubin") is not None:
        return True
    logger.debug_once("flashinfer-cubin package was not found")
    return False


49
50
@functools.cache
def has_flashinfer() -> bool:
51
    """Return `True` if flashinfer-python package is available."""
52
53
    # Use find_spec to check if the module exists without importing it
    # This avoids potential CUDA initialization side effects
54
55
56
    if importlib.util.find_spec("flashinfer") is None:
        logger.debug_once("FlashInfer unavailable since package was not found")
        return False
57
    # When not using flashinfer cubin,
58
    # Also check if nvcc is available since it's required to JIT compile flashinfer
59
    if not has_flashinfer_cubin() and shutil.which("nvcc") is None:
60
61
62
63
        logger.debug_once(
            "FlashInfer unavailable since nvcc was not found "
            "and not using pre-downloaded cubins"
        )
64
65
        return False
    return True
66
67
68
69
70
71
72


def _missing(*_: Any, **__: Any) -> NoReturn:
    """Placeholder for unavailable FlashInfer backend."""
    raise RuntimeError(
        "FlashInfer backend is not available. Please install the package "
        "to enable FlashInfer kernels: "
73
74
        "https://github.com/flashinfer-ai/flashinfer"
    )
75
76
77
78
79
80
81
82
83
84
85


def _get_submodule(module_name: str) -> Any | None:
    """Safely import a submodule and return it, or None if not available."""
    try:
        return importlib.import_module(module_name)
    except (ImportError, ModuleNotFoundError):
        return None


# General lazy import wrapper
86
87
88
def _lazy_import_wrapper(
    module_name: str, attr_name: str, fallback_fn: Callable[..., Any] = _missing
):
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
    """Create a lazy import wrapper for a specific function."""

    @functools.cache
    def _get_impl():
        if not has_flashinfer():
            return None
        mod = _get_submodule(module_name)
        return getattr(mod, attr_name, None) if mod else None

    def wrapper(*args, **kwargs):
        impl = _get_impl()
        if impl is None:
            return fallback_fn(*args, **kwargs)
        return impl(*args, **kwargs)

    return wrapper


# Create lazy wrappers for each function
108
109
110
flashinfer_trtllm_bf16_moe = _lazy_import_wrapper(
    "flashinfer.fused_moe", "trtllm_bf16_moe"
)
111
flashinfer_trtllm_fp8_block_scale_moe = _lazy_import_wrapper(
112
113
    "flashinfer.fused_moe", "trtllm_fp8_block_scale_moe"
)
114
flashinfer_trtllm_fp8_per_tensor_scale_moe = _lazy_import_wrapper(
115
116
117
118
119
    "flashinfer.fused_moe", "trtllm_fp8_per_tensor_scale_moe"
)
flashinfer_cutlass_fused_moe = _lazy_import_wrapper(
    "flashinfer.fused_moe", "cutlass_fused_moe"
)
120
121
122
flashinfer_cutedsl_grouped_gemm_nt_masked = _lazy_import_wrapper(
    "flashinfer.cute_dsl.blockscaled_gemm", "grouped_gemm_nt_masked"
)
123
flashinfer_fp4_quantize = _lazy_import_wrapper("flashinfer", "fp4_quantize")
124
125
126
127
128
129
130
nvfp4_batched_quantize = _lazy_import_wrapper("flashinfer", "nvfp4_batched_quantize")
silu_and_mul_scaled_nvfp4_experts_quantize = _lazy_import_wrapper(
    "flashinfer", "silu_and_mul_scaled_nvfp4_experts_quantize"
)
scaled_fp4_grouped_quantize = _lazy_import_wrapper(
    "flashinfer", "scaled_fp4_grouped_quantize"
)
131
nvfp4_block_scale_interleave = _lazy_import_wrapper(
132
    "flashinfer.fp4_quantization", "block_scale_interleave"
133
)
134
trtllm_fp4_block_scale_moe = _lazy_import_wrapper(
135
136
    "flashinfer", "trtllm_fp4_block_scale_moe"
)
137
138
139
140
# Special case for autotune since it returns a context manager
autotune = _lazy_import_wrapper(
    "flashinfer.autotuner",
    "autotune",
141
142
    fallback_fn=lambda *args, **kwargs: contextlib.nullcontext(),
)
143
144


145
146
@functools.cache
def has_flashinfer_comm() -> bool:
147
    """Return `True` if FlashInfer comm module is available."""
148
    return has_flashinfer() and importlib.util.find_spec("flashinfer.comm") is not None
149
150
151
152


@functools.cache
def has_flashinfer_all2all() -> bool:
153
    """Return `True` if FlashInfer mnnvl all2all is available."""
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
    if not has_flashinfer_comm():
        return False

    # Check if all required functions are available
    required_functions = [
        ("flashinfer.comm", "Mapping"),
        ("flashinfer.comm.mnnvl", "MnnvlMemory"),
        ("flashinfer.comm.trtllm_alltoall", "MnnvlMoe"),
        ("flashinfer.comm.trtllm_alltoall", "MoEAlltoallInfo"),
    ]

    for module_name, attr_name in required_functions:
        mod = _get_submodule(module_name)
        if not mod or not hasattr(mod, attr_name):
            return False
    return True


172
173
@functools.cache
def has_flashinfer_moe() -> bool:
174
    """Return `True` if FlashInfer MoE module is available."""
175
176
177
178
    return (
        has_flashinfer()
        and importlib.util.find_spec("flashinfer.fused_moe") is not None
    )
179
180


181
182
183
184
185
186
187
188
@functools.cache
def has_flashinfer_cutedsl() -> bool:
    """Return ``True`` if FlashInfer cutedsl module is available."""
    return (
        has_flashinfer() and importlib.util.find_spec("flashinfer.cute_dsl") is not None
    )


189
190
191
192
193
194
195
196
197
@functools.cache
def has_flashinfer_trtllm_fused_moe() -> bool:
    """Return `True` if FlashInfer TRTLLM fused MoE is available."""
    if not has_flashinfer_moe():
        return False
    required_functions = [
        ("flashinfer.fused_moe", "trtllm_fp8_block_scale_moe"),
        ("flashinfer.fused_moe", "trtllm_fp8_per_tensor_scale_moe"),
        ("flashinfer.fused_moe", "trtllm_fp4_block_scale_moe"),
198
        ("flashinfer.fused_moe", "trtllm_mxint4_block_scale_moe"),
199
200
201
202
203
204
205
206
    ]
    for module_name, attr_name in required_functions:
        mod = _get_submodule(module_name)
        if not mod or not hasattr(mod, attr_name):
            return False
    return True


207
208
@functools.cache
def has_flashinfer_cutlass_fused_moe() -> bool:
209
    """Return `True` if FlashInfer CUTLASS fused MoE is available."""
210
    if not has_flashinfer_moe():
211
212
213
214
215
216
        return False

    # Check if all required functions are available
    required_functions = [
        ("flashinfer.fused_moe", "cutlass_fused_moe"),
        ("flashinfer", "fp4_quantize"),
217
        ("flashinfer", "nvfp4_block_scale_interleave"),
218
        ("flashinfer.fused_moe", "trtllm_fp4_block_scale_moe"),
219
220
221
222
223
224
225
226
227
    ]

    for module_name, attr_name in required_functions:
        mod = _get_submodule(module_name)
        if not mod or not hasattr(mod, attr_name):
            return False
    return True


228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
@functools.cache
def has_flashinfer_cutedsl_grouped_gemm_nt_masked() -> bool:
    """Return ``True`` if FlashInfer CUTLASS fused MoE is available."""
    if not has_flashinfer_cutedsl():
        return False

    # Check if all required functions are available
    required_functions = [
        ("flashinfer.cute_dsl.blockscaled_gemm", "grouped_gemm_nt_masked"),
        ("flashinfer", "scaled_fp4_grouped_quantize"),
        ("flashinfer", "silu_and_scaled_nvfp4_experts_quantize"),
    ]

    for module_name, attr_name in required_functions:
        mod = _get_submodule(module_name)
        if not mod or not hasattr(mod, attr_name):
            return False
    return True


248
249
@functools.cache
def has_nvidia_artifactory() -> bool:
250
    """Return `True` if NVIDIA's artifactory is accessible.
251

252
253
254
    This checks connectivity to the kernel inference library artifactory
    which is required for downloading certain cubin kernels like TRTLLM FHMA.
    """
255
256
    # If we have pre-downloaded cubins, we can assume the cubins are available.
    if has_flashinfer_cubin():
257
258
        return True

259
260
261
262
263
264
265
266
267
    try:
        # Use a short timeout to avoid blocking for too long
        response = requests.get(FLASHINFER_CUBINS_REPOSITORY, timeout=5)
        accessible = response.status_code == 200
        if accessible:
            logger.debug_once("NVIDIA artifactory is accessible")
        else:
            logger.warning_once(
                "NVIDIA artifactory returned failed status code: %d",
268
269
                response.status_code,
            )
270
271
272
273
274
275
        return accessible
    except Exception as e:
        logger.warning_once("Failed to connect to NVIDIA artifactory: %s", e)
        return False


276
@functools.cache
277
278
def supports_trtllm_attention() -> bool:
    """
279
280
    TRTLLM attention is supported if the platform is SM100,
    NVIDIA artifactory is accessible, and batch-invariant mode is not enabled.
281
    """
282
283
284
285
    # Batch-invariant mode disables TRTLLM attention
    if vllm_is_batch_invariant():
        return False

286
    # Requires SM100 and NVIDIA artifactory to be accessible to download cubins
287
288
289
    return (
        current_platform.is_device_capability_family(100) and has_nvidia_artifactory()
    )
290

291

292
def force_use_trtllm_attention() -> bool | None:
293
    """
294
295
    This function should only be called during initialization stage when vllm config
    is set.
296
    Return `None` if --attention-config.use_trtllm_attention is not set,
297
298
    return `True` if TRTLLM attention is forced to be used,
    return `False` if TRTLLM attention is forced to be not used.
299
    """
300
301
302
303
    from vllm.config import get_current_vllm_config

    vllm_config = get_current_vllm_config()
    return vllm_config.attention_config.use_trtllm_attention
304
305


306
307
def can_use_trtllm_attention(num_qo_heads: int, num_kv_heads: int) -> bool:
    """Check if the current configuration supports TRTLLM attention."""
308
309
    if force_use_trtllm_attention() is False:
        return False
310
    has_trtllm = supports_trtllm_attention()
311
    return has_trtllm and (num_qo_heads % num_kv_heads == 0)
312
313


314
def use_trtllm_attention(
315
316
    num_qo_heads: int,
    num_kv_heads: int,
317
318
    num_tokens: int,
    max_seq_len: int,
319
    dcp_world_size: int,
320
    kv_cache_dtype: str,
321
    q_dtype: torch.dtype,
322
    is_prefill: bool,
323
324
    # None means auto-detection, True means force on, False means force off
    force_use_trtllm: bool | None = None,
325
    has_sinks: bool = False,
326
    has_spec: bool = False,
327
) -> bool:
328
    """Return `True` if TRTLLM attention is used."""
329

330
    # CLI argument is set to 0 - respect it
331
    if force_use_trtllm is not None and not force_use_trtllm:
332
333
        return False

334
335
336
337
338
339
340
341
    # Decode context parallel is not supported
    if dcp_world_size > 1:
        logger.warning_once(
            "Trtllm does not support returning LSE and as a result "
            "does not support DCP, reverting to FlashInfer"
        )
        return False

342
343
344
345
346
    # The platform is not supported
    if not supports_trtllm_attention():
        if force_use_trtllm:
            logger.warning_once(
                "TRTLLM attention is not supported on this platform, "
347
                "but --attention-config.use_trtllm_attention is set to 1"
348
            )
349
350
351
        return False

    # The combination of query and key heads is not supported
352
    if num_qo_heads % num_kv_heads != 0:
353
354
355
        if force_use_trtllm:
            logger.warning_once(
                "TRTLLM attention is not supported for this combination of "
356
357
                "query and key heads, but --attention-config.use_trtllm_attention is "
                "set to 1"
358
            )
359
360
        return False

361
362
    if has_spec and not is_prefill:
        # Speculative decoding requires TRTLLM attention for decodes
363
        logger.info_once("Using TRTLLM attention (enabled for speculative decoding).")
364
365
        return True

366
367
368
369
370
    # Must use TRTLLM attention if query is FP8 quantized
    if q_dtype == current_platform.fp8_dtype():
        logger.info_once("Using TRTLLM attention (query is quantized).")
        return True

371
372
373
    # If sinks are being used, we must use TRTLLM attention as it's
    # the only backend that supports them
    if has_sinks:
374
        logger.info_once("Using TRTLLM attention (required for attention sinks).")
375
376
        return True

377
    if force_use_trtllm is None:
378
        # CLI argument not set - use auto-detection
379
380
        if is_prefill:
            # Prefill auto-detection
381
            use_trtllm = kv_cache_dtype == "auto"
382
383
384
385
            if use_trtllm:
                logger.warning_once("Using TRTLLM prefill attention (auto-detected).")
        else:
            # Decode auto-detection
386
            use_trtllm = num_tokens <= 256 and kv_cache_dtype == "auto"
387
388
            if use_trtllm:
                logger.warning_once("Using TRTLLM decode attention (auto-detected).")
389
390
        return use_trtllm

391
392
393
394
    # CLI argument is set to 1 - respect it
    logger.info_once(
        "Using TRTLLM attention (--attention-config.use_trtllm_attention is set to 1)"
    )
395
396
    return True

397

398
399
400
401
402
403
404
405
406
407
408
409
410
411
if has_flashinfer():

    @torch.library.custom_op(
        "vllm::flashinfer_mm_fp4",
        mutates_args=[],
        device_types="cuda",
    )
    def flashinfer_mm_fp4(
        A: torch.Tensor,
        B: torch.Tensor,
        A_scale: torch.Tensor,
        B_scale: torch.Tensor,
        g_scale: torch.Tensor,
        dtype: torch.dtype,
412
        use_8x4_sf_layout: bool,
413
414
415
        backend: str,
    ) -> torch.Tensor:
        from flashinfer import mm_fp4 as flashinfer_mm_fp4_
416
417

        return flashinfer_mm_fp4_(
418
419
420
421
422
423
424
425
426
            A,
            B,
            A_scale,
            B_scale,
            g_scale,
            dtype,
            block_size=16,
            use_8x4_sf_layout=use_8x4_sf_layout,
            backend=backend,
427
428
429
430
431
        )

    @torch.library.register_fake(
        "vllm::flashinfer_mm_fp4",
    )
432
433
434
435
436
437
438
    def flashinfer_mm_fp4_fake(
        A: torch.Tensor,
        B: torch.Tensor,
        A_scale: torch.Tensor,
        B_scale: torch.Tensor,
        g_scale: torch.Tensor,
        dtype: torch.dtype,
439
        use_8x4_sf_layout: bool,
440
441
        backend: str,
    ) -> torch.Tensor:
442
        return torch.empty(A.shape[0], B.shape[1], dtype=dtype, device=A.device)
443

444
445
446
447
448
449
450
451
452
453
454
455
456
457
    @torch.library.custom_op(
        "vllm::bmm_fp8",
        mutates_args=[],
        device_types="cuda",
    )
    def bmm_fp8(
        A: torch.Tensor,
        B: torch.Tensor,
        A_scale: torch.Tensor,
        B_scale: torch.Tensor,
        dtype: torch.dtype,
        backend: str,
    ) -> torch.Tensor:
        from flashinfer import bmm_fp8 as bmm_fp8_
458

459
460
        return bmm_fp8_(A, B, A_scale, B_scale, dtype, None, backend)

461
462
463
    @torch.library.register_fake(
        "vllm::bmm_fp8",
    )
464
465
466
467
468
469
470
471
    def bmm_fp8_fake(
        A: torch.Tensor,
        B: torch.Tensor,
        A_scale: torch.Tensor,
        B_scale: torch.Tensor,
        dtype: torch.dtype,
        backend: str,
    ) -> torch.Tensor:
472
473
474
475
        return torch.empty(
            A.shape[0], A.shape[1], B.shape[2], dtype=dtype, device=A.device
        )

476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
    @torch.library.custom_op(
        "vllm::flashinfer_nvfp4_quantize",
        mutates_args=[],
        device_types="cuda",
    )
    def flashinfer_nvfp4_quantize(
        a: torch.Tensor, a_global_sf: torch.Tensor
    ) -> tuple[torch.Tensor, torch.Tensor]:
        from flashinfer import SfLayout
        from flashinfer import nvfp4_quantize as nvfp4_quantize_

        return nvfp4_quantize_(
            a, a_global_sf, sfLayout=SfLayout.layout_8x4, do_shuffle=False
        )

    @torch.library.register_fake(
        "vllm::flashinfer_nvfp4_quantize",
    )
    def flashinfer_nvfp4_quantize_fake(
        a: torch.Tensor, a_global_sf: torch.Tensor
    ) -> tuple[torch.Tensor, torch.Tensor]:
        m, n = a.shape

        round_up = lambda x, y: (x + y - 1) // y * y

        rounded_m = round_up(m, 8)
        scale_n = n // 16
        rounded_n = round_up(scale_n, 4)

        return torch.empty(m, n // 2, dtype=torch.uint8, device=a.device), torch.empty(
            rounded_m, rounded_n, dtype=torch.uint8, device=a.device
        )

509
510
511
512
513
514
515
516
517
518

def flashinfer_scaled_fp4_mm(
    a: torch.Tensor,
    b: torch.Tensor,
    block_scale_a: torch.Tensor,
    block_scale_b: torch.Tensor,
    alpha: torch.Tensor,
    out_dtype: torch.dtype,
    backend: str,
) -> torch.Tensor:
519
520
521
522
523
    assert a.ndim == 2 and b.ndim == 2
    assert block_scale_a.ndim == 2 and block_scale_b.ndim == 2
    assert a.stride(-1) == 1 and b.stride(-1) == 1
    assert a.shape[1] == b.shape[1]

524
    if backend in ("cutlass", "cudnn"):
525
526
527
        block_scale_a = block_scale_a.view(torch.uint8)
        block_scale_b = block_scale_b.view(torch.uint8)

528
529
    use_8x4_sf_layout = True if backend == "trtllm" and a.shape[0] <= 32 else False  # noqa: SIM210

530
531
532
533
534
535
536
    return flashinfer_mm_fp4(
        a,
        b.t(),
        block_scale_a,
        block_scale_b.t(),
        alpha,
        out_dtype,
537
        use_8x4_sf_layout=use_8x4_sf_layout,
538
539
540
541
        backend=backend,
    )


542
def flashinfer_scaled_fp8_mm(
543
544
545
546
547
    a: torch.Tensor,
    b: torch.Tensor,
    scale_a: torch.Tensor,
    scale_b: torch.Tensor,
    out_dtype: torch.dtype,
548
    bias: torch.Tensor | None = None,
549
) -> torch.Tensor:
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
    assert a.ndim == 2 and b.ndim == 2
    assert a.shape[1] == b.shape[0]
    assert scale_a.numel() == 1 and scale_b.numel() == 1
    assert a.dtype == torch.float8_e4m3fn and b.dtype == torch.float8_e4m3fn
    assert a.device.type == "cuda" and b.device.type == "cuda"
    assert scale_a.dtype == torch.float32 and scale_b.dtype == torch.float32
    assert scale_a.device.type == "cuda" and scale_b.device.type == "cuda"

    output = bmm_fp8(
        a.unsqueeze(0),
        b.unsqueeze(0),
        scale_a,
        scale_b,
        out_dtype,
        "auto",
    ).view(a.shape[0], b.shape[1])

    if bias is not None:
        output = output + bias
    return output


572
573
574
575
576
577
def flashinfer_quant_nvfp4_8x4_sf_layout(
    a: torch.Tensor, a_global_sf: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]:
    return flashinfer_nvfp4_quantize(a, a_global_sf)


578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
flashinfer_fp8_blockscale_gemm = _lazy_import_wrapper(
    "flashinfer.gemm", "fp8_blockscale_gemm_sm90"
)


@functools.cache
def has_flashinfer_fp8_blockscale_gemm() -> bool:
    """Return `True` if FlashInfer block-scale FP8 GEMM is available."""
    return (
        has_flashinfer()
        and current_platform.is_device_capability(90)
        and hasattr(_get_submodule("flashinfer.gemm"), "fp8_blockscale_gemm_sm90")
    )


@functools.cache
def is_flashinfer_fp8_blockscale_gemm_supported() -> bool:
    """Return `True` if FlashInfer block-scale FP8 GEMM is supported."""
    return (
        envs.VLLM_BLOCKSCALE_FP8_GEMM_FLASHINFER
        and has_flashinfer_fp8_blockscale_gemm()
    )


def should_use_flashinfer_for_blockscale_fp8_gemm(
    is_flashinfer_supported: bool,
    output_dtype: torch.dtype,
    input: torch.Tensor,
    weight: torch.Tensor,
):
    if not is_flashinfer_supported:
        return False

    # Verify DeepGEMM N/K dims requirements
    # NOTE: Also synchronized with test_w8a8_block_fp8_deep_gemm_matmul
    # test inside kernels/quatization/test_block_fp8.py
    N_MULTIPLE = 64
    K_MULTIPLE = 128

    weight_dtype = weight.dtype
    input_dtype = input.dtype

    should_use_flashinfer = (
        output_dtype == torch.bfloat16
        and input_dtype == torch.bfloat16
        and weight_dtype == torch.float8_e4m3fn
        and weight.shape[0] % N_MULTIPLE == 0
        and weight.shape[1] % K_MULTIPLE == 0
    )

    return should_use_flashinfer


631
632
__all__ = [
    "has_flashinfer",
633
    "flashinfer_trtllm_fp8_block_scale_moe",
634
    "flashinfer_cutlass_fused_moe",
635
    "flashinfer_cutedsl_grouped_gemm_nt_masked",
636
    "flashinfer_fp4_quantize",
637
638
    "silu_and_mul_scaled_nvfp4_experts_quantize",
    "scaled_fp4_grouped_quantize",
639
    "nvfp4_block_scale_interleave",
640
    "trtllm_fp4_block_scale_moe",
641
    "autotune",
642
    "has_flashinfer_moe",
643
644
    "has_flashinfer_comm",
    "has_flashinfer_all2all",
645
    "has_flashinfer_cutlass_fused_moe",
646
    "has_flashinfer_cutedsl_grouped_gemm_nt_masked",
647
    "has_flashinfer_fp8_blockscale_gemm",
648
    "has_nvidia_artifactory",
649
    "supports_trtllm_attention",
650
    "can_use_trtllm_attention",
651
    "use_trtllm_attention",
652
    "flashinfer_scaled_fp4_mm",
653
    "flashinfer_scaled_fp8_mm",
654
    "flashinfer_quant_nvfp4_8x4_sf_layout",
655
656
657
    "flashinfer_fp8_blockscale_gemm",
    "should_use_flashinfer_for_blockscale_fp8_gemm",
    "is_flashinfer_fp8_blockscale_gemm_supported",
658
]