_fa_common.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. """
  2. Copyright (c) 2026 by FlashInfer team.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. """
  6. import functools
  7. import math
  8. from typing import Callable, ClassVar, Optional, Protocol, Tuple, TypeVar, Union, cast
  9. import torch
  10. from ....jit import gen_batch_mla_module
  11. from ....utils import MaskMode, check_shape_dtype_device, get_compute_capability
  12. from ._capabilities import MLAPlanCapabilities, plan_capability_rejection_reason
  13. from .._planning import _MLAPlanArguments
  14. class _GeneratedBatchMLAModule(Protocol):
  15. def plan(self, *args: object) -> object: ...
  16. def plan_with_staged_workspace_bytes(self, *args: object) -> tuple[object, int]: ...
  17. def run(self, *args: object) -> object: ...
  18. @functools.cache
  19. def get_batch_mla_module(
  20. backend: str,
  21. dtype_q: torch.dtype,
  22. dtype_kv: torch.dtype,
  23. dtype_o: torch.dtype,
  24. dtype_idx: torch.dtype,
  25. head_dim_ckv: int,
  26. head_dim_kpe: int,
  27. use_profiler: bool,
  28. ) -> _GeneratedBatchMLAModule:
  29. return gen_batch_mla_module(
  30. backend,
  31. dtype_q,
  32. dtype_kv,
  33. dtype_o,
  34. dtype_idx,
  35. head_dim_ckv,
  36. head_dim_kpe,
  37. use_profiler,
  38. ).build_and_load()
  39. def _validate_generated_fa_plan(
  40. *,
  41. backend: str,
  42. device: torch.device,
  43. qo_indptr: torch.Tensor,
  44. kv_indptr: torch.Tensor,
  45. kv_indices: torch.Tensor,
  46. kv_len_arr: torch.Tensor,
  47. head_dim_ckv: int,
  48. head_dim_kpe: int,
  49. q_data_type: torch.dtype,
  50. kv_data_type: torch.dtype,
  51. output_dtype: torch.dtype,
  52. scale_mode: str,
  53. ) -> None:
  54. for name, tensor in (
  55. ("qo_indptr", qo_indptr),
  56. ("kv_indptr", kv_indptr),
  57. ("kv_indices", kv_indices),
  58. ("kv_len_arr", kv_len_arr),
  59. ):
  60. if tensor.dtype != torch.int32:
  61. raise ValueError(f"{name} must have dtype torch.int32, got {tensor.dtype}.")
  62. if q_data_type not in (torch.float16, torch.bfloat16):
  63. raise ValueError(
  64. f"MLA q_data_type {q_data_type} is not supported by the {backend} backend."
  65. )
  66. supported_kv_dtypes = (torch.float16, torch.bfloat16, torch.float8_e4m3fn)
  67. if kv_data_type not in supported_kv_dtypes:
  68. raise ValueError(
  69. f"MLA kv_data_type {kv_data_type} is not supported by the {backend} "
  70. f"backend. Supported dtypes: {list(supported_kv_dtypes)}."
  71. )
  72. if output_dtype != q_data_type:
  73. raise ValueError(
  74. f"{backend} backend output_dtype must match q_data_type, got "
  75. f"{output_dtype} and {q_data_type}."
  76. )
  77. if head_dim_kpe < 0:
  78. raise ValueError(f"head_dim_kpe must be >= 0, got {head_dim_kpe}.")
  79. if kv_data_type == torch.float8_e4m3fn:
  80. major, minor = get_compute_capability(device)
  81. if major not in (9, 12):
  82. raise ValueError(
  83. "FP8 kv_data_type for MLA requires an SM90 (Hopper) device, "
  84. f"got SM{major}{minor}."
  85. )
  86. if q_data_type != torch.bfloat16:
  87. raise ValueError(
  88. "FP8 kv_data_type for MLA currently only supports "
  89. f"q_data_type=torch.bfloat16, got {q_data_type}."
  90. )
  91. if head_dim_ckv != 512 or head_dim_kpe not in (0, 64):
  92. raise ValueError(
  93. "FP8 kv_data_type for MLA currently only supports "
  94. "head_dim_ckv=512 and head_dim_kpe in (0, 64), got "
  95. f"head_dim_ckv={head_dim_ckv}, head_dim_kpe={head_dim_kpe}."
  96. )
  97. if scale_mode != "kv-per-tensor":
  98. raise ValueError("FP8 MLA plans require scale_mode='kv-per-tensor'.")
  99. elif scale_mode != "default":
  100. raise ValueError("non-FP8 MLA plans require scale_mode='default'.")
  101. class _BatchMLAGeneratedFaMechanics:
  102. def __init__(
  103. self,
  104. *,
  105. backend: Optional[str] = None,
  106. float_workspace_buffer: torch.Tensor,
  107. use_cuda_graph: bool,
  108. qo_indptr_buf: Optional[torch.Tensor],
  109. kv_indptr_buf: Optional[torch.Tensor],
  110. kv_indices_buf: Optional[torch.Tensor],
  111. kv_len_arr_buf: Optional[torch.Tensor],
  112. int_workspace_buffer: Optional[torch.Tensor] = None,
  113. pin_memory_int_workspace_buffer: Optional[torch.Tensor] = None,
  114. ) -> None:
  115. self._backend = backend
  116. self._float_workspace_buffer = float_workspace_buffer
  117. self.device = float_workspace_buffer.device
  118. if int_workspace_buffer is None:
  119. int_workspace_buffer = torch.empty(
  120. (8 * 1024 * 1024,), dtype=torch.uint8, device=self.device
  121. )
  122. if pin_memory_int_workspace_buffer is None:
  123. pin_memory_int_workspace_buffer = torch.empty(
  124. int_workspace_buffer.shape,
  125. dtype=int_workspace_buffer.dtype,
  126. pin_memory=True,
  127. device="cpu",
  128. )
  129. self._int_workspace_buffer = int_workspace_buffer
  130. self._pin_memory_int_workspace_buffer = pin_memory_int_workspace_buffer
  131. self._use_cuda_graph = use_cuda_graph
  132. self._qo_indptr_buf = qo_indptr_buf
  133. self._kv_indptr_buf = kv_indptr_buf
  134. self._kv_indices_buf = kv_indices_buf
  135. self._kv_len_arr_buf = kv_len_arr_buf
  136. @staticmethod
  137. def _storage_interval(tensor: torch.Tensor) -> tuple[int, int, int]:
  138. start = tensor.storage_offset() * tensor.element_size()
  139. return (
  140. tensor.untyped_storage().data_ptr(),
  141. start,
  142. start + tensor.numel() * tensor.element_size(),
  143. )
  144. def _preflight_graph_metadata_buffers(
  145. self,
  146. *,
  147. qo_indptr: torch.Tensor,
  148. kv_indptr: torch.Tensor,
  149. kv_indices: torch.Tensor,
  150. kv_len_arr: torch.Tensor,
  151. ) -> None:
  152. named = (
  153. ("qo_indptr", self._qo_indptr_buf, qo_indptr, False),
  154. ("kv_indptr", self._kv_indptr_buf, kv_indptr, False),
  155. ("kv_indices", self._kv_indices_buf, kv_indices, True),
  156. ("kv_len_arr", self._kv_len_arr_buf, kv_len_arr, False),
  157. )
  158. for name, reserved, source, allow_larger in named:
  159. if reserved is None:
  160. raise ValueError(
  161. "CUDA graph mode requires reserved qo_indptr, kv_indptr, "
  162. "kv_indices, and kv_len_arr buffers."
  163. )
  164. if reserved.dtype != torch.int32:
  165. raise ValueError(
  166. f"CUDA graph reserved {name} buffer must have dtype torch.int32."
  167. )
  168. if reserved.device != self.device:
  169. raise ValueError(
  170. f"CUDA graph reserved {name} buffer must be on {self.device}."
  171. )
  172. if not reserved.is_contiguous():
  173. raise ValueError(
  174. f"CUDA graph reserved {name} buffer must be contiguous."
  175. )
  176. if name == "kv_indices" and reserved.ndim != 1:
  177. raise ValueError(
  178. "CUDA graph reserved kv_indices buffer must have rank 1."
  179. )
  180. if (allow_larger and reserved.shape[0] < source.shape[0]) or (
  181. not allow_larger and reserved.shape != source.shape
  182. ):
  183. raise ValueError(
  184. f"CUDA graph reserved {name} buffer has insufficient or "
  185. "incompatible capacity."
  186. )
  187. intervals = [
  188. (name, self._storage_interval(reserved))
  189. for name, reserved, _, _ in named
  190. if reserved is not None
  191. ]
  192. for index, (left_name, (left_ptr, left_start, left_end)) in enumerate(
  193. intervals
  194. ):
  195. for right_name, (right_ptr, right_start, right_end) in intervals[
  196. index + 1 :
  197. ]:
  198. if left_ptr == right_ptr and max(left_start, right_start) < min(
  199. left_end, right_end
  200. ):
  201. raise ValueError(
  202. "CUDA graph reserved metadata buffers must not overlap: "
  203. f"{left_name} overlaps {right_name}."
  204. )
  205. copy_targets = {
  206. name: reserved[: source.shape[0]] if allow_larger else reserved
  207. for name, reserved, source, allow_larger in named
  208. if reserved is not None
  209. }
  210. for source_name, _, source, _ in named:
  211. source_interval = self._storage_interval(source)
  212. for target_name, target in copy_targets.items():
  213. target_interval = self._storage_interval(target)
  214. overlaps = source_interval[0] == target_interval[0] and max(
  215. source_interval[1], target_interval[1]
  216. ) < min(source_interval[2], target_interval[2])
  217. exact_corresponding_reuse = (
  218. source_name == target_name and source_interval == target_interval
  219. )
  220. if overlaps and not exact_corresponding_reuse:
  221. raise ValueError(
  222. f"CUDA graph {source_name} source overlaps reserved "
  223. f"{target_name} target; only exact same-source/same-target "
  224. "reuse is allowed."
  225. )
  226. def _stage_metadata(
  227. self,
  228. *,
  229. qo_indptr: torch.Tensor,
  230. kv_indptr: torch.Tensor,
  231. kv_indices: torch.Tensor,
  232. kv_len_arr: torch.Tensor,
  233. ) -> None:
  234. if self._use_cuda_graph:
  235. self._preflight_graph_metadata_buffers(
  236. qo_indptr=qo_indptr,
  237. kv_indptr=kv_indptr,
  238. kv_indices=kv_indices,
  239. kv_len_arr=kv_len_arr,
  240. )
  241. assert self._qo_indptr_buf is not None
  242. assert self._kv_indptr_buf is not None
  243. assert self._kv_indices_buf is not None
  244. assert self._kv_len_arr_buf is not None
  245. targets = (
  246. (self._qo_indptr_buf, qo_indptr),
  247. (self._kv_indptr_buf, kv_indptr),
  248. (self._kv_indices_buf[: len(kv_indices)], kv_indices),
  249. (self._kv_len_arr_buf, kv_len_arr),
  250. )
  251. # GB10 fix: no rollback snapshot. It cloned kv_indices_buf on every
  252. # plan(), 136 MB per decode step for GLM's sparse MLA, to undo a
  253. # copy that the preflight above has already made safe.
  254. for target, source in targets:
  255. target.copy_(source, non_blocking=True)
  256. else:
  257. self._qo_indptr_buf = qo_indptr.to(self.device, non_blocking=True)
  258. self._kv_indptr_buf = kv_indptr.to(self.device, non_blocking=True)
  259. self._kv_indices_buf = kv_indices.to(self.device, non_blocking=True)
  260. self._kv_len_arr_buf = kv_len_arr.to(self.device, non_blocking=True)
  261. def _plan_generated_fa(
  262. self,
  263. *,
  264. module_loader: Callable[[], _GeneratedBatchMLAModule],
  265. qo_indptr: torch.Tensor,
  266. kv_indptr: torch.Tensor,
  267. kv_indices: torch.Tensor,
  268. kv_len_arr: torch.Tensor,
  269. num_heads: int,
  270. head_dim_ckv: int,
  271. page_size: int,
  272. causal: bool,
  273. sm_scale: float,
  274. q_data_type: torch.dtype,
  275. kv_data_type: torch.dtype,
  276. use_profiler: bool,
  277. ) -> None:
  278. # ---------------------------------------------------------------------------
  279. # Build the generated backend plan
  280. # ---------------------------------------------------------------------------
  281. cached_module = module_loader()
  282. qo_indptr_host = qo_indptr.to("cpu")
  283. kv_indptr_host = kv_indptr.to("cpu")
  284. kv_len_arr_host = kv_len_arr.to("cpu")
  285. plan_args = (
  286. self._float_workspace_buffer,
  287. self._int_workspace_buffer,
  288. self._pin_memory_int_workspace_buffer,
  289. qo_indptr_host,
  290. kv_indptr_host,
  291. kv_len_arr_host,
  292. num_heads,
  293. head_dim_ckv,
  294. causal,
  295. )
  296. if hasattr(cached_module, "plan_with_staged_workspace_bytes"):
  297. plan_info, staged_int_workspace_bytes = (
  298. cached_module.plan_with_staged_workspace_bytes(*plan_args)
  299. )
  300. else:
  301. # Compatibility for externally cached legacy modules; generated modules
  302. # are always adapted at load time.
  303. plan_info = cached_module.plan(*plan_args)
  304. staged_int_workspace_bytes = 0
  305. # ---------------------------------------------------------------------------
  306. # Stage metadata and publish backend state
  307. # ---------------------------------------------------------------------------
  308. self._stage_metadata(
  309. qo_indptr=qo_indptr,
  310. kv_indptr=kv_indptr,
  311. kv_indices=kv_indices,
  312. kv_len_arr=kv_len_arr,
  313. )
  314. self._cached_module = cached_module
  315. self._causal = causal
  316. self._page_size = page_size
  317. self._sm_scale = sm_scale
  318. self._head_dim_ckv = head_dim_ckv
  319. self._q_data_type = q_data_type
  320. self._kv_data_type = kv_data_type
  321. self._use_profiler = use_profiler
  322. self._plan_info = plan_info
  323. self._staged_int_workspace_bytes = staged_int_workspace_bytes
  324. def _validate_run_input_dtypes(
  325. self,
  326. *,
  327. q_nope: torch.Tensor,
  328. q_pe: torch.Tensor,
  329. ckv_cache: torch.Tensor,
  330. kpe_cache: torch.Tensor,
  331. ) -> None:
  332. if q_nope.dtype != self._q_data_type:
  333. raise ValueError(
  334. f"q_nope.dtype={q_nope.dtype} does not match the planned "
  335. f"q_data_type={self._q_data_type}."
  336. )
  337. if q_pe.dtype != self._q_data_type:
  338. raise ValueError(
  339. f"q_pe.dtype={q_pe.dtype} does not match the planned "
  340. f"q_data_type={self._q_data_type}."
  341. )
  342. if ckv_cache.dtype != self._kv_data_type:
  343. raise ValueError(
  344. f"ckv_cache.dtype={ckv_cache.dtype} does not match the planned "
  345. f"kv_data_type={self._kv_data_type}."
  346. )
  347. if kpe_cache.dtype != self._kv_data_type:
  348. raise ValueError(
  349. f"kpe_cache.dtype={kpe_cache.dtype} does not match the planned "
  350. f"kv_data_type={self._kv_data_type}."
  351. )
  352. def _run_generated_fa(
  353. self,
  354. *,
  355. q_nope: torch.Tensor,
  356. q_pe: torch.Tensor,
  357. ckv_cache: torch.Tensor,
  358. kpe_cache: torch.Tensor,
  359. out: Optional[torch.Tensor],
  360. lse: Optional[torch.Tensor],
  361. return_lse: bool,
  362. profiler_buffer: Optional[torch.Tensor],
  363. return_lse_base_on_e: bool,
  364. ckv_scale: Optional[float],
  365. ckv_scale_arr: Optional[torch.Tensor],
  366. kpe_scale: Optional[float],
  367. ) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
  368. # ---------------------------------------------------------------------------
  369. # Validate inputs and scale arguments
  370. # ---------------------------------------------------------------------------
  371. self._validate_run_input_dtypes(
  372. q_nope=q_nope,
  373. q_pe=q_pe,
  374. ckv_cache=ckv_cache,
  375. kpe_cache=kpe_cache,
  376. )
  377. kv_is_fp8 = self._kv_data_type == torch.float8_e4m3fn
  378. if kv_is_fp8:
  379. if (ckv_scale is None) == (ckv_scale_arr is None):
  380. raise ValueError(
  381. "Exactly one of ckv_scale or ckv_scale_arr is required when "
  382. "kv_data_type is FP8."
  383. )
  384. if kpe_scale is None:
  385. raise ValueError("kpe_scale is required when kv_data_type is FP8.")
  386. ckv_scale_f = 1.0 if ckv_scale is None else float(ckv_scale)
  387. kpe_scale_f = float(kpe_scale)
  388. if ckv_scale is not None and (
  389. not math.isfinite(ckv_scale_f) or ckv_scale_f <= 0.0
  390. ):
  391. raise ValueError(
  392. f"ckv_scale must be a finite positive value, got {ckv_scale}"
  393. )
  394. if not math.isfinite(kpe_scale_f) or kpe_scale_f <= 0.0:
  395. raise ValueError(
  396. f"kpe_scale must be a finite positive value, got {kpe_scale}"
  397. )
  398. else:
  399. if (
  400. ckv_scale is not None
  401. or ckv_scale_arr is not None
  402. or kpe_scale is not None
  403. ):
  404. raise ValueError(
  405. "ckv_scale / ckv_scale_arr / kpe_scale are only valid when "
  406. "kv_data_type is FP8."
  407. )
  408. ckv_scale_f = 1.0
  409. kpe_scale_f = 1.0
  410. # ---------------------------------------------------------------------------
  411. # Prepare output and auxiliary buffers
  412. # ---------------------------------------------------------------------------
  413. if profiler_buffer is None and self._use_profiler:
  414. raise ValueError("Profiler is enabled, profiler_buffer must be provided")
  415. if out is None:
  416. out = torch.empty_like(q_nope)
  417. else:
  418. check_shape_dtype_device(
  419. out, q_nope.shape, q_nope.dtype, q_nope.device, "out"
  420. )
  421. if return_lse:
  422. if lse is None:
  423. lse = torch.empty(
  424. q_nope.shape[:2], dtype=torch.float32, device=self.device
  425. )
  426. else:
  427. check_shape_dtype_device(
  428. lse, q_nope.shape[:2], torch.float32, q_nope.device, "lse"
  429. )
  430. if ckv_scale_arr is not None:
  431. expected_scale_shape = (*ckv_cache.shape[:-1], self._head_dim_ckv // 128)
  432. check_shape_dtype_device(
  433. ckv_scale_arr,
  434. expected_scale_shape,
  435. torch.float32,
  436. ckv_cache.device,
  437. "ckv_scale_arr",
  438. )
  439. if not ckv_scale_arr.is_contiguous():
  440. raise ValueError("ckv_scale_arr must be contiguous.")
  441. # ---------------------------------------------------------------------------
  442. # Launch the generated backend
  443. # ---------------------------------------------------------------------------
  444. mask_mode = MaskMode.CAUSAL.value if self._causal else MaskMode.NON_CAUSAL.value
  445. profiler_args = (profiler_buffer,) if self._use_profiler else ()
  446. self._cached_module.run(
  447. self._float_workspace_buffer,
  448. self._int_workspace_buffer,
  449. self._plan_info,
  450. q_nope,
  451. q_pe,
  452. ckv_cache,
  453. kpe_cache,
  454. self._kv_indices_buf,
  455. out,
  456. lse,
  457. mask_mode,
  458. q_nope.shape[1],
  459. self._page_size,
  460. self._sm_scale,
  461. return_lse_base_on_e,
  462. ckv_scale_f,
  463. kpe_scale_f,
  464. ckv_scale_arr,
  465. *profiler_args,
  466. )
  467. return (out, lse) if return_lse else out
  468. _FaBackendT = TypeVar("_FaBackendT", bound="_BatchMLAPagedAttentionFaBackendBase")
  469. class _BatchMLAPagedAttentionFaBackendBase(_BatchMLAGeneratedFaMechanics):
  470. _plan_capabilities: ClassVar[Optional[MLAPlanCapabilities]] = None
  471. def __init__(
  472. self,
  473. *,
  474. backend: Optional[str] = None,
  475. float_workspace_buffer: torch.Tensor,
  476. use_cuda_graph: bool,
  477. qo_indptr_buf: Optional[torch.Tensor],
  478. kv_indptr_buf: Optional[torch.Tensor],
  479. kv_indices_buf: Optional[torch.Tensor],
  480. kv_len_arr_buf: Optional[torch.Tensor],
  481. query_split_widths: tuple[int, int],
  482. kv_split_widths: tuple[int, int],
  483. int_workspace_buffer: Optional[torch.Tensor] = None,
  484. pin_memory_int_workspace_buffer: Optional[torch.Tensor] = None,
  485. ) -> None:
  486. if backend is None:
  487. if self._plan_capabilities is None:
  488. raise TypeError("generated-FA backend capabilities are required.")
  489. backend = self._plan_capabilities.backend_name
  490. super().__init__(
  491. backend=backend,
  492. float_workspace_buffer=float_workspace_buffer,
  493. use_cuda_graph=use_cuda_graph,
  494. qo_indptr_buf=qo_indptr_buf,
  495. kv_indptr_buf=kv_indptr_buf,
  496. kv_indices_buf=kv_indices_buf,
  497. kv_len_arr_buf=kv_len_arr_buf,
  498. int_workspace_buffer=int_workspace_buffer,
  499. pin_memory_int_workspace_buffer=pin_memory_int_workspace_buffer,
  500. )
  501. self._query_split_widths = query_split_widths
  502. self._kv_split_widths = kv_split_widths
  503. def plan(
  504. self,
  505. *,
  506. qo_indptr: torch.Tensor,
  507. kv_indptr: torch.Tensor,
  508. kv_indices: torch.Tensor,
  509. kv_len_arr: torch.Tensor,
  510. num_heads: int,
  511. head_dim_ckv: int,
  512. head_dim_kpe: int,
  513. page_size: int,
  514. causal: bool,
  515. sm_scale: float,
  516. q_data_type: torch.dtype,
  517. kv_data_type: torch.dtype,
  518. output_dtype: torch.dtype,
  519. scale_mode: str,
  520. use_profiler: bool,
  521. ) -> None:
  522. raise NotImplementedError
  523. @classmethod
  524. def plan_from_wrapper(
  525. cls: type[_FaBackendT], args: _MLAPlanArguments
  526. ) -> _FaBackendT:
  527. assert cls._plan_capabilities is not None
  528. if reason := plan_capability_rejection_reason(args, cls._plan_capabilities):
  529. raise ValueError(reason)
  530. csr = args.csr()
  531. backend = cls(
  532. float_workspace_buffer=args._float_workspace_buffer,
  533. use_cuda_graph=args._use_cuda_graph,
  534. qo_indptr_buf=args._qo_indptr_buf,
  535. kv_indptr_buf=args._kv_indptr_buf,
  536. kv_indices_buf=args._kv_indices_buf,
  537. kv_len_arr_buf=args._kv_len_arr_buf,
  538. query_split_widths=(args.head_dim_ckv, args.head_dim_kpe),
  539. kv_split_widths=(args.head_dim_ckv, args.head_dim_kpe),
  540. int_workspace_buffer=args._graph_plan_int_workspace_buffer,
  541. )
  542. if args._use_cuda_graph:
  543. backend._preflight_graph_metadata_buffers(
  544. qo_indptr=csr.qo_indptr,
  545. kv_indptr=csr.kv_indptr,
  546. kv_indices=csr.kv_indices,
  547. kv_len_arr=csr.kv_len_arr,
  548. )
  549. backend.plan(
  550. qo_indptr=csr.qo_indptr,
  551. kv_indptr=csr.kv_indptr,
  552. kv_indices=csr.kv_indices,
  553. kv_len_arr=csr.kv_len_arr,
  554. num_heads=args.num_heads,
  555. head_dim_ckv=args.head_dim_ckv,
  556. head_dim_kpe=args.head_dim_kpe,
  557. page_size=args.page_size,
  558. causal=args.causal,
  559. sm_scale=args.sm_scale,
  560. q_data_type=args.q_data_type,
  561. kv_data_type=args.kv_data_type,
  562. output_dtype=args.output_dtype,
  563. scale_mode=args.scale_mode,
  564. use_profiler=args.use_profiler,
  565. )
  566. return backend
  567. def run_from_wrapper(
  568. self,
  569. *,
  570. query: object,
  571. kv_cache: object,
  572. out: Optional[torch.Tensor],
  573. lse: Optional[torch.Tensor],
  574. return_lse: bool,
  575. profiler_buffer: Optional[torch.Tensor],
  576. kv_len: Optional[torch.Tensor],
  577. page_table: Optional[torch.Tensor],
  578. return_lse_base_on_e: bool,
  579. o_scale: Optional[float],
  580. ckv_scale: Optional[float],
  581. ckv_scale_arr: Optional[torch.Tensor],
  582. kpe_scale: Optional[float],
  583. ) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
  584. if kv_len is not None:
  585. raise ValueError("kv_len is only supported with cutlass backend.")
  586. if page_table is not None:
  587. raise ValueError("page_table is only supported with cutlass backend.")
  588. if o_scale is not None:
  589. raise ValueError(
  590. "o_scale is only supported with the cutlass backend for now."
  591. )
  592. q_nope, q_pe = cast(tuple[torch.Tensor, torch.Tensor], query)
  593. ckv_cache, kpe_cache = cast(tuple[torch.Tensor, torch.Tensor], kv_cache)
  594. return self._run_generated_fa(
  595. q_nope=q_nope,
  596. q_pe=q_pe,
  597. ckv_cache=ckv_cache,
  598. kpe_cache=kpe_cache,
  599. out=out,
  600. lse=lse,
  601. return_lse=return_lse,
  602. profiler_buffer=profiler_buffer,
  603. return_lse_base_on_e=return_lse_base_on_e,
  604. ckv_scale=ckv_scale,
  605. ckv_scale_arr=ckv_scale_arr,
  606. kpe_scale=kpe_scale,
  607. )