1use anyhow::{Context, Result, bail};
14use std::ffi::c_void;
15
16use crate::cuda_min::{CudaCtx, CudaModule, DeviceBuffer, launch_kernel};
17
18include!(concat!(env!("OUT_DIR"), "/storage_ptx.rs"));
19
20unsafe extern "C" {
21 fn cuMemsetD32Async(dst: u64, value: u32, count: usize, stream: u64) -> i32;
22}
23
24#[derive(Clone, Copy, Debug)]
25pub struct TiledAttentionDims {
26 pub max_seqs: usize,
27 pub num_q_heads: usize,
28 pub num_kv_heads: usize,
29 pub head_dim: usize,
30 pub block_size: usize,
31 pub tile_capacity: usize,
32}
33
34impl TiledAttentionDims {
35 pub fn validate(&self) -> Result<()> {
36 if !self.num_q_heads.is_multiple_of(self.num_kv_heads) {
37 bail!(
38 "num_q_heads ({}) must divide num_kv_heads ({})",
39 self.num_q_heads,
40 self.num_kv_heads
41 );
42 }
43 if self.head_dim > 256 {
44 bail!("head_dim {} exceeds MAX_HEAD_DIM=256", self.head_dim);
45 }
46 Ok(())
47 }
48 pub fn gqa_ratio(&self) -> i32 {
49 (self.num_q_heads / self.num_kv_heads) as i32
50 }
51 fn n_q_slots(&self) -> usize {
52 self.max_seqs * self.num_q_heads
53 }
54 pub fn m_bytes(&self) -> usize {
55 self.n_q_slots() * 4
56 }
57 pub fn l_bytes(&self) -> usize {
58 self.n_q_slots() * 4
59 }
60 pub fn o_bytes(&self) -> usize {
61 self.n_q_slots() * self.head_dim * 4
62 }
63 pub fn output_bytes(&self) -> usize {
64 self.n_q_slots() * self.head_dim * 2
65 }
66}
67
68pub struct TiledAttention {
69 dims: TiledAttentionDims,
70 _modules: Vec<CudaModule>,
71 f_step: u64,
72 f_finalize: u64,
73 pub m_state: DeviceBuffer,
74 pub l_state: DeviceBuffer,
75 pub o_state: DeviceBuffer,
76}
77
78const NEG_INF_F32_BITS: u32 = 0xFF800000;
79
80impl TiledAttention {
81 pub fn new(dims: TiledAttentionDims) -> Result<Self> {
82 dims.validate()?;
83 let mut modules = Vec::new();
84 let mut f_step = 0u64;
85 let mut f_finalize = 0u64;
86 for entry in STORAGE_PTX.iter() {
87 match entry.name {
88 "paged_decode_attn_tiled" => {
89 let m = CudaModule::from_ptx(entry.ptx)
90 .with_context(|| format!("load {}", entry.name))?;
91 f_step = m.function("paged_decode_attn_tiled")?;
92 modules.push(m);
93 }
94 "attention_finalize" => {
95 let m = CudaModule::from_ptx(entry.ptx)
96 .with_context(|| format!("load {}", entry.name))?;
97 f_finalize = m.function("attention_finalize")?;
98 modules.push(m);
99 }
100 _ => {}
101 }
102 }
103 if f_step == 0 || f_finalize == 0 {
104 bail!("tiled-attention PTX modules missing");
105 }
106 let m_state = DeviceBuffer::new(dims.m_bytes())?;
107 let l_state = DeviceBuffer::new(dims.l_bytes())?;
108 let o_state = DeviceBuffer::new(dims.o_bytes())?;
109 Ok(Self {
110 dims,
111 _modules: modules,
112 f_step,
113 f_finalize,
114 m_state,
115 l_state,
116 o_state,
117 })
118 }
119
120 pub fn begin_step(&self, ctx: &CudaCtx, num_seqs: usize) -> Result<()> {
126 self.begin_step_on_stream(ctx.stream, num_seqs)
127 }
128
129 pub fn begin_step_on_stream(&self, stream: u64, num_seqs: usize) -> Result<()> {
130 if num_seqs > self.dims.max_seqs {
131 bail!(
132 "begin_step num_seqs {} > max_seqs {}",
133 num_seqs,
134 self.dims.max_seqs
135 );
136 }
137 let n_q = num_seqs * self.dims.num_q_heads;
138 let n_o = n_q * self.dims.head_dim;
139 unsafe {
140 let s = cuMemsetD32Async(self.m_state.ptr, NEG_INF_F32_BITS, n_q, stream);
141 if s != 0 {
142 bail!("cuMemsetD32Async m_state failed: {s}");
143 }
144 let s = cuMemsetD32Async(self.l_state.ptr, 0, n_q, stream);
145 if s != 0 {
146 bail!("cuMemsetD32Async l_state failed: {s}");
147 }
148 let s = cuMemsetD32Async(self.o_state.ptr, 0, n_o, stream);
149 if s != 0 {
150 bail!("cuMemsetD32Async o_state failed: {s}");
151 }
152 }
153 Ok(())
154 }
155
156 pub fn paged_strides(&self) -> (i64, i64, i64) {
159 let blk = (self.dims.block_size * self.dims.num_kv_heads * self.dims.head_dim) as i64;
160 let tok = (self.dims.num_kv_heads * self.dims.head_dim) as i64;
161 let kvh = self.dims.head_dim as i64;
162 (blk, tok, kvh)
163 }
164
165 pub fn scratch_pool_strides(&self) -> (i64, i64, i64) {
168 let kv_stripe = (self.dims.num_kv_heads * self.dims.block_size * self.dims.head_dim) as i64;
169 let blk = 2 * kv_stripe;
170 let tok = self.dims.head_dim as i64;
171 let kvh = (self.dims.block_size * self.dims.head_dim) as i64;
172 (blk, tok, kvh)
173 }
174
175 #[allow(clippy::too_many_arguments)]
180 pub fn step_tile(
181 &self,
182 ctx: &CudaCtx,
183 q: u64,
184 k_pool: u64,
185 v_pool: u64,
186 tile_blocks: u64,
187 tile_block_counts: u64,
188 num_seqs: usize,
189 blk_stride: i64,
190 tok_stride: i64,
191 kvh_stride: i64,
192 last_block_valid_slots: i32,
193 ) -> Result<()> {
194 self.step_tile_on_stream(
195 ctx.stream,
196 q,
197 k_pool,
198 v_pool,
199 tile_blocks,
200 tile_block_counts,
201 num_seqs,
202 blk_stride,
203 tok_stride,
204 kvh_stride,
205 last_block_valid_slots,
206 )
207 }
208
209 #[allow(clippy::too_many_arguments)]
210 pub fn step_tile_on_stream(
211 &self,
212 stream: u64,
213 q: u64,
214 k_pool: u64,
215 v_pool: u64,
216 tile_blocks: u64,
217 tile_block_counts: u64,
218 num_seqs: usize,
219 blk_stride: i64,
220 tok_stride: i64,
221 kvh_stride: i64,
222 last_block_valid_slots: i32,
223 ) -> Result<()> {
224 let mut q_v = q;
225 let mut k_v = k_pool;
226 let mut v_v = v_pool;
227 let mut tb = tile_blocks;
228 let mut tc = tile_block_counts;
229 let mut m_v = self.m_state.ptr;
230 let mut l_v = self.l_state.ptr;
231 let mut o_v = self.o_state.ptr;
232 let mut nq = self.dims.num_q_heads as i32;
233 let mut nk = self.dims.num_kv_heads as i32;
234 let mut hd = self.dims.head_dim as i32;
235 let mut bs = self.dims.block_size as i32;
236 let mut tcap = self.dims.tile_capacity as i32;
237 let mut gqa = self.dims.gqa_ratio();
238 let mut blk_s = blk_stride;
239 let mut tok_s = tok_stride;
240 let mut kvh_s = kvh_stride;
241 let mut lbvs = last_block_valid_slots;
242 let mut params = [
243 &mut q_v as *mut _ as *mut c_void,
244 &mut k_v as *mut _ as *mut c_void,
245 &mut v_v as *mut _ as *mut c_void,
246 &mut tb as *mut _ as *mut c_void,
247 &mut tc as *mut _ as *mut c_void,
248 &mut m_v as *mut _ as *mut c_void,
249 &mut l_v as *mut _ as *mut c_void,
250 &mut o_v as *mut _ as *mut c_void,
251 &mut nq as *mut _ as *mut c_void,
252 &mut nk as *mut _ as *mut c_void,
253 &mut hd as *mut _ as *mut c_void,
254 &mut bs as *mut _ as *mut c_void,
255 &mut tcap as *mut _ as *mut c_void,
256 &mut gqa as *mut _ as *mut c_void,
257 &mut blk_s as *mut _ as *mut c_void,
258 &mut tok_s as *mut _ as *mut c_void,
259 &mut kvh_s as *mut _ as *mut c_void,
260 &mut lbvs as *mut _ as *mut c_void,
261 ];
262 launch_kernel(
263 self.f_step,
264 (num_seqs as u32, self.dims.num_q_heads as u32, 1),
265 (self.dims.head_dim as u32, 1, 1),
266 0,
267 stream,
268 &mut params,
269 )
270 }
271
272 pub fn finalize(&self, ctx: &CudaCtx, output: u64, num_seqs: usize) -> Result<()> {
275 self.finalize_on_stream(ctx.stream, output, num_seqs)
276 }
277
278 pub fn finalize_on_stream(&self, stream: u64, output: u64, num_seqs: usize) -> Result<()> {
279 let mut l_v = self.l_state.ptr;
280 let mut o_v = self.o_state.ptr;
281 let mut out_v = output;
282 let mut nq = self.dims.num_q_heads as i32;
283 let mut hd = self.dims.head_dim as i32;
284 let mut params = [
285 &mut l_v as *mut _ as *mut c_void,
286 &mut o_v as *mut _ as *mut c_void,
287 &mut out_v as *mut _ as *mut c_void,
288 &mut nq as *mut _ as *mut c_void,
289 &mut hd as *mut _ as *mut c_void,
290 ];
291 launch_kernel(
292 self.f_finalize,
293 (num_seqs as u32, self.dims.num_q_heads as u32, 1),
294 (self.dims.head_dim as u32, 1, 1),
295 0,
296 stream,
297 &mut params,
298 )
299 }
300
301 pub fn dims(&self) -> TiledAttentionDims {
302 self.dims
303 }
304}