@@ -71,8 +71,9 @@ def _load_qdq_helpers() -> None:
7171 _v_qdq_nvfp4 = _v_nvfp4
7272
7373
74- # Maps public P/V QDQ options to kernel constexpr values.
75- _QDQ_MODES = {None : 0 , "fp8" : 1 , "nvfp4" : 2 }
74+ # Maps public QDQ options to kernel constexpr values.
75+ _P_QDQ_MODES = {None : 0 , "fp8" : 1 , "nvfp4" : 2 }
76+ _V_QDQ_MODES = {None : 0 , "nvfp4" : 2 }
7677
7778
7879LOG2E : float = 1.44269504088896
@@ -289,7 +290,7 @@ def _attn_fwd(
289290 SKIP_THRESHOLD_LOG2 : tl .constexpr = 0.0 , # log2(lambda) in the kernel's scaled log2 score space
290291 P_QDQ : tl .constexpr = 0 , # Fake quant-dequant of softmax P: 0=off, 1=FP8 E4M3, 2=NVFP4
291292 p_qdq_scale = 1.0 , # Per-tensor scale for softmax qdq (runtime scalar; amax/448 or amax/(6*448))
292- V_QDQ : tl .constexpr = 0 , # Fake quant-dequant of V: 0=off, 1=FP8 E4M3, 2=NVFP4
293+ V_QDQ : tl .constexpr = 0 , # Fake quant-dequant of V: 0=off, 2=NVFP4
293294 v_qdq_scale = 1.0 ,
294295 V_CACHE_QUANTIZED : tl .constexpr = False , # complete block-16 groups are already QDQ
295296 Sparsity_total = None , # Optional int64 scalar for counting total tiles (atomic)
@@ -476,13 +477,10 @@ def _attn_fwd(
476477 mask = ((kv_start + kv_pos [:, None ]) < seq_len_kv ) & d_mask [None , :],
477478 other = 0.0 ,
478479 )
479- if V_QDQ != 0 and (
480+ if V_QDQ == 2 and (
480481 (not V_CACHE_QUANTIZED ) or (kv_start + BLOCK_N > v_quantized_boundary )
481482 ):
482- if V_QDQ == 1 :
483- v_qdq = _qdq_fp8 (v .to (tl .float32 ), v_qdq_scale )
484- else :
485- v_qdq = _v_qdq_nvfp4 (v .to (tl .float32 ), v_qdq_scale , BLOCK_N , BLOCK_D )
483+ v_qdq = _v_qdq_nvfp4 (v .to (tl .float32 ), v_qdq_scale , BLOCK_N , BLOCK_D )
486484 v_qdq = v_qdq .to (v .dtype )
487485 if V_CACHE_QUANTIZED :
488486 use_qdq = (kv_start + kv_pos ) >= v_quantized_boundary
@@ -1298,12 +1296,11 @@ def attention(
12981296 and the global scale ``amax / (6 * 448)`` for NVFP4. A runtime
12991297 scalar — user-set or calibrated values do not recompile the
13001298 kernel. Values above amax saturate.
1301- v_qdq: Fake quant-dequant of V before ``P @ V``. ``"fp8 "`` uses a
1302- per-tensor E4M3 scale; ``"nvfp4"`` uses signed E2M1 values with
1303- one E4M3 scale per 16 keys. ``None`` disables V QDQ.
1299+ v_qdq: Fake quant-dequant of V before ``P @ V``. ``"nvfp4 "`` uses
1300+ signed E2M1 values with one E4M3 scale per 16 keys. ``None``
1301+ disables V QDQ.
13041302 v_qdq_amax: Optional per-tensor V amax. ``None`` uses global scale 1;
1305- otherwise converts to ``amax / 448`` (FP8) or ``amax / (6 * 448)``
1306- (NVFP4).
1303+ otherwise converts to the NVFP4 global scale ``amax / (6 * 448)``.
13071304 v_cache_quantized: Complete block-16 groups in the paged V cache are
13081305 already QDQ; only the pristine partial group is QDQ on read.
13091306 k_cache: Paged K cache [num_blocks, page_size, num_kv_heads, head_dim].
@@ -1330,11 +1327,11 @@ def attention(
13301327 # silently reuse stale compiled kernels from the on-disk cache.
13311328 _load_sparsity_helpers ()
13321329 _load_qdq_helpers ()
1333- if p_qdq not in _QDQ_MODES :
1330+ if p_qdq not in _P_QDQ_MODES :
13341331 raise ValueError (
1335- f"p_qdq must be one of { sorted (k for k in _QDQ_MODES if k )} or None, got { p_qdq !r} "
1332+ f"p_qdq must be one of { sorted (k for k in _P_QDQ_MODES if k )} or None, got { p_qdq !r} "
13361333 )
1337- p_qdq_mode = _QDQ_MODES [p_qdq ]
1334+ p_qdq_mode = _P_QDQ_MODES [p_qdq ]
13381335 # Convert the per-tensor amax to the kernel's scale convention
13391336 # (``q = cast(p / scale) * scale``): FP8 uses ``amax / 448``; NVFP4 uses the
13401337 # global scale ``amax / (6 * 448)``. amax=1 (the default, the theoretical
@@ -1344,18 +1341,18 @@ def attention(
13441341 if not (math .isfinite (p_qdq_amax ) and p_qdq_amax > 0 ):
13451342 raise ValueError (f"p_qdq_amax must be a finite positive value, got { p_qdq_amax } " )
13461343 p_qdq_scale = p_qdq_amax / 448.0 if p_qdq == "fp8" else p_qdq_amax / (6.0 * 448.0 )
1347- if v_qdq not in _QDQ_MODES :
1344+ if v_qdq not in _V_QDQ_MODES :
13481345 raise ValueError (
1349- f"v_qdq must be one of { sorted (k for k in _QDQ_MODES if k )} or None, got { v_qdq !r} "
1346+ f"v_qdq must be one of { sorted (k for k in _V_QDQ_MODES if k )} or None, got { v_qdq !r} "
13501347 )
1351- v_qdq_mode = _QDQ_MODES [v_qdq ]
1348+ v_qdq_mode = _V_QDQ_MODES [v_qdq ]
13521349 if v_qdq_mode and any (t .requires_grad for t in (q , k , v )):
13531350 raise NotImplementedError ("v_qdq is inference-only and does not support autograd" )
13541351 v_qdq_scale = 1.0
13551352 if v_qdq_mode and v_qdq_amax is not None :
13561353 if not (math .isfinite (v_qdq_amax ) and v_qdq_amax > 0 ):
13571354 raise ValueError (f"v_qdq_amax must be a finite positive value, got { v_qdq_amax } " )
1358- v_qdq_scale = v_qdq_amax / 448.0 if v_qdq == "fp8" else v_qdq_amax / (6.0 * 448.0 )
1355+ v_qdq_scale = v_qdq_amax / (6.0 * 448.0 )
13591356 if v_cache_quantized and v_qdq != "nvfp4" :
13601357 raise ValueError ("v_cache_quantized requires v_qdq='nvfp4'" )
13611358 if v_cache_quantized and any (x is None for x in (k_cache , v_cache , block_table )):
0 commit comments