Skip to content

Commit 15382d1

Browse files
justinchubyCopilot
andcommitted
fix(kv-cache): gate FP8 KV cache on EP FP8-kernel capability
Address PR review: fp8_kv_cache was applied whenever GQA fusion was active (dtype in caps.gqa_dtypes), which includes non-CUDA EPs (CPU gqa_dtypes={FLOAT}, WebGPU float/float16) that lack the FP8 GroupQueryAttention kernel. Such builds emitted FLOAT8E4M3FN KV-cache I/O for EPs that cannot load/run it. - Add EpCapabilities.supports_fp8_kv_cache (default False); set True only for CUDA (SM89+ Ada/Hopper/Blackwell ship the FP8 GQA kernel). - Gate Fp8KvCachePass on caps.supports_fp8_kv_cache in addition to GQA activity; otherwise warn and ignore the request. - Add regression test: CPU/float32 (GQA active, no FP8 kernel) keeps FLOAT KV I/O. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: justinchuby <justinchuby@users.noreply.github.com>
1 parent 91fa683 commit 15382d1

3 files changed

Lines changed: 40 additions & 8 deletions

File tree

src/mobius/_execution_providers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ class EpCapabilities:
9393
default) leaves ``Range`` unchanged. Set ``False`` only when
9494
static cache is used on runtimes that lack a ``Range`` kernel (QNN
9595
HTP), where the ``Range`` node would otherwise be forced onto CPU.
96+
supports_fp8_kv_cache: ``True`` allows :class:`~mobius._passes.
97+
Fp8KvCachePass` to retype the ``GroupQueryAttention`` KV cache to
98+
``FLOAT8E4M3FN``. Only CUDA (SM89+ Ada/Hopper/Blackwell) ships the
99+
FP8 GQA kernel, so this defaults to ``False`` for every other EP —
100+
``--features fp8-kv-cache`` is ignored (with a warning) on EPs that
101+
cannot run an FP8 KV cache, preventing invalid/unloadable models.
96102
supports_matmul_nbits: ``False`` converts ``com.microsoft::MatMulNBits``
97103
(blockwise-INT4 weight) into a standard ``DequantizeLinear`` +
98104
``MatMul`` (QDQ) pair via MatMulNBitsToQDQ. ``True`` leaves the
@@ -153,6 +159,7 @@ class EpCapabilities:
153159
supports_rotary_embedding: bool = True
154160
supports_tensor_scatter: bool = True
155161
supports_range: bool = True
162+
supports_fp8_kv_cache: bool = False
156163
default_int4_accuracy_level: int = 0
157164
provider_options: dict[str, str] = dataclasses.field(default_factory=dict)
158165
enable_graph_capture: bool = False
@@ -309,6 +316,9 @@ def _register_builtins() -> None:
309316
"enable_skip_layer_norm_strict_mode": "1",
310317
},
311318
supports_past_present_share_buffer=True,
319+
# Only CUDA ships the FP8 (E4M3) GroupQueryAttention KV-cache kernel
320+
# (SM89+ Ada/Hopper/Blackwell).
321+
supports_fp8_kv_cache=True,
312322
),
313323
EpCapabilities(
314324
name="dml",

src/mobius/_optimizations.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,10 @@ def optimize_model(
398398
fp8_kv_cache: When ``True``, convert decoder
399399
``GroupQueryAttention`` KV caches to ``FLOAT8E4M3FN`` (per-tensor
400400
E4M3) via :class:`~mobius._passes.Fp8KvCachePass`. Only applied
401-
when GQA fusion is active for ``(ep, dtype)`` and
402-
``model_role == "decoder"``; otherwise a warning is emitted and the
403-
request is ignored (no GQA nodes to convert).
401+
when GQA fusion is active for ``(ep, dtype)``,
402+
``model_role == "decoder"``, and the EP ships the FP8 GQA kernel
403+
(``caps.supports_fp8_kv_cache`` — currently CUDA only); otherwise a
404+
warning is emitted and the request is ignored.
404405
kv_cache_scales: Optional ``layer_id -> (k_scale, v_scale)`` map of
405406
per-tensor FP8 scales (from offline calibration). Only used when
406407
``fp8_kv_cache`` is ``True``; layers absent from the map use a unit
@@ -572,12 +573,13 @@ def _should_inline(func: ir.Function) -> bool:
572573
# GQA-capable EP/dtype); otherwise there is no KV-cache op to convert.
573574
if fp8_kv_cache:
574575
gqa_active = model_role == "decoder" and dtype in caps.gqa_dtypes
575-
if not gqa_active:
576+
if not gqa_active or not caps.supports_fp8_kv_cache:
576577
warnings.warn(
577-
f"fp8_kv_cache=True was requested but GQA fusion is not active "
578-
f"for ep={ep!r}/dtype={dtype}/role={model_role!r}. FP8 KV cache "
579-
f"requires a GroupQueryAttention decoder (e.g. --execution-provider "
580-
f"cuda with an fp16/bf16 dtype). Ignoring the request.",
578+
f"fp8_kv_cache=True was requested but the FP8 GQA KV-cache kernel "
579+
f"is not available for ep={ep!r}/dtype={dtype}/role={model_role!r}. "
580+
f"FP8 KV cache requires a GroupQueryAttention decoder on an EP with "
581+
f"the FP8 kernel (currently only --execution-provider cuda with an "
582+
f"fp16/bf16 dtype). Ignoring the request.",
581583
stacklevel=4,
582584
)
583585
else:

src/mobius/_passes/_fp8_kv_cache_test.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,26 @@ def test_ignored_and_warns_on_non_gqa_ep(self):
145145
ins = {v.name: v for v in pkg["model"].graph.inputs}
146146
assert ins["past_key_values.0.key"].dtype == ir.DataType.FLOAT
147147

148+
def test_ignored_and_warns_on_non_fp8_ep_with_gqa(self):
149+
"""GQA-active but non-FP8 EP (CPU/float32) must NOT emit FP8 KV I/O.
150+
151+
Regression: fp8_kv_cache was previously applied whenever GQA fusion was
152+
active, which includes CPU (gqa_dtypes={FLOAT}) — an EP without the FP8
153+
GQA kernel — producing models that can't load/run.
154+
"""
155+
config = _base_config(dtype=ir.DataType.FLOAT)
156+
module = registry.get("qwen2")(config)
157+
with pytest.warns(UserWarning, match="fp8_kv_cache=True"):
158+
pkg = build_from_module(
159+
module,
160+
config,
161+
"text-generation",
162+
execution_provider="cpu",
163+
fp8_kv_cache=True,
164+
)
165+
ins = {v.name: v for v in pkg["model"].graph.inputs}
166+
assert ins["past_key_values.0.key"].dtype == ir.DataType.FLOAT
167+
148168
def test_pass_is_idempotent(self):
149169
model, _config = _build_fp8_decoder()
150170
# Re-running the pass must not add extra inputs or re-type anything.

0 commit comments

Comments
 (0)