main
hpp 2,189 lines 91 KB
Raw
1 #ifndef CPU_ATTN_HPP
2 #define CPU_ATTN_HPP
3
4 #include <type_traits>
5 #include <cstddef>
6
7 #if defined(__APPLE__)
8 #include <sys/sysctl.h>
9 #endif
10
11 #include "cpu/cpu_arch_macros.h"
12 #include "cpu/utils.hpp"
13
14 namespace cpu_attention {
15 enum class ISA { AMX, VEC, VEC16, NEON, VXE, RVV, VSX, AMX_FP8 };
16
17 // Mirrors csrc/attention/dtype_fp8.cuh Fp8KVCacheDataType exactly.
18 enum class Fp8KVCacheDataType {
19 kAuto = 0,
20 kFp8E4M3 = 1,
21 kFp8E5M2 = 2,
22 };
23
24 struct AttentionInput;
25
26 template <ISA isa, typename scalar_t, int64_t head_dim,
27 typename kv_cache_scalar_t = scalar_t>
28 class AttentionImpl {
29 public:
30 void init_from_input(const AttentionInput*) {}
31 float get_output_v_scale() const noexcept { return 1.0f; }
32 };
33
34 struct AttentionWorkItemGroup {
35 int32_t req_id;
36 int32_t q_token_id_start;
37 int32_t q_token_num;
38 int32_t kv_split_pos_start;
39 int32_t kv_split_pos_end;
40
41 int64_t total_kv_len;
42 int32_t split_id;
43 int32_t local_split_id;
44
45 AttentionWorkItemGroup(const int32_t req_id, const int32_t q_token_id_start,
46 const int32_t kv_split_pos_start,
47 const int32_t kv_split_pos_end)
48 : req_id(req_id),
49 q_token_id_start(q_token_id_start),
50 q_token_num(0),
51 kv_split_pos_start(kv_split_pos_start),
52 kv_split_pos_end(kv_split_pos_end),
53 total_kv_len(0),
54 split_id(-1),
55 local_split_id(0) {}
56
57 std::string to_string() const {
58 std::stringstream ss;
59 ss << '[' << "req_id: " << req_id << ",\n";
60 ss << "q_token_id_start: " << q_token_id_start << ",\n";
61 ss << "q_token_num: " << q_token_num << ",\n";
62 ss << "kv_split_pos_start: " << kv_split_pos_start << ",\n";
63 ss << "kv_split_pos_end: " << kv_split_pos_end << ",\n";
64 ss << "total_kv_len: " << total_kv_len << ",\n";
65 ss << "split_id: " << split_id << ",\n";
66 ss << "local_split_id: " << local_split_id << ",\n";
67 ss << ']';
68
69 return ss.str();
70 }
71 };
72
73 struct ReductionWorkItemGroup {
74 int32_t req_id;
75 int32_t q_token_id_start;
76 int32_t q_token_id_num;
77 int32_t split_start_id;
78 int32_t split_num;
79
80 ReductionWorkItemGroup(const int32_t req_id, const int32_t q_token_id_start,
81 const int32_t q_token_id_num,
82 const int32_t split_start_id)
83 : req_id(req_id),
84 q_token_id_start(q_token_id_start),
85 q_token_id_num(q_token_id_num),
86 split_start_id(split_start_id),
87 split_num(0) {}
88
89 std::string to_string() const {
90 std::stringstream ss;
91 ss << '[' << "req_id: " << req_id << ",\n";
92 ss << "q_token_id_start: " << q_token_id_start << ",\n";
93 ss << "q_token_id_num: " << q_token_id_num << ",\n";
94 ss << "split_start_id: " << split_start_id << ",\n";
95 ss << "split_num: " << split_num << ",\n";
96 ss << ']';
97
98 return ss.str();
99 }
100 };
101
102 struct AttentionMetadata {
103 std::atomic_int64_t counter;
104 char _padding1[56];
105 ISA isa;
106 int32_t workitem_group_num;
107 int32_t reduction_item_num;
108 int32_t reduction_split_num;
109 int32_t thread_num;
110 int32_t effective_thread_num; // non-zero item num in workitem_num_per_thread
111 int32_t split_kv_q_token_num_threshold;
112 int64_t attention_scratchpad_size_per_thread;
113 int64_t reduction_scratchpad_size_per_kv_head;
114 AttentionWorkItemGroup* workitem_groups_ptr;
115 ReductionWorkItemGroup* reduction_items_ptr;
116 int32_t cu_workitem_num_per_thread[1025] = {
117 0}; // prefix sum of workitem_num_per_thread
118 char _padding2[56];
119
120 AttentionMetadata(ISA isa, int32_t workitem_group_num,
121 int32_t reduction_item_num, int32_t reduction_split_num,
122 int32_t split_kv_q_token_num_threshold)
123 : isa(isa),
124 workitem_group_num(workitem_group_num),
125 reduction_item_num(reduction_item_num),
126 reduction_split_num(reduction_split_num),
127 thread_num(cpu_utils::get_max_threads()),
128 effective_thread_num(thread_num),
129 split_kv_q_token_num_threshold(split_kv_q_token_num_threshold),
130 attention_scratchpad_size_per_thread(0),
131 reduction_scratchpad_size_per_kv_head(0),
132 workitem_groups_ptr(
133 (AttentionWorkItemGroup*)((char*)this + sizeof(AttentionMetadata))),
134 reduction_items_ptr(
135 (ReductionWorkItemGroup*)((char*)this + sizeof(AttentionMetadata) +
136 workitem_group_num *
137 sizeof(AttentionWorkItemGroup))),
138 counter(0) {
139 TORCH_CHECK_LE(thread_num, 1024);
140 static_assert(sizeof(AttentionMetadata) % 64 == 0);
141 TORCH_CHECK(reinterpret_cast<size_t>(this) % 64 == 0);
142 }
143
144 void reset_counter() { counter.store(0); }
145
146 int64_t acquire_counter() { return counter++; }
147
148 void print() const {
149 std::stringstream ss;
150 ss << "ISA: ";
151 switch (isa) {
152 case ISA::AMX:
153 ss << "AMX, ";
154 break;
155 case ISA::AMX_FP8:
156 ss << "AMX_FP8, ";
157 break;
158 case ISA::VEC:
159 ss << "VEC, ";
160 break;
161 case ISA::VEC16:
162 ss << "VEC16, ";
163 break;
164 case ISA::NEON:
165 ss << "NEON, ";
166 break;
167 case ISA::VXE:
168 ss << "VXE, ";
169 break;
170 case ISA::RVV:
171 ss << "RVV, ";
172 break;
173 case ISA::VSX:
174 ss << "VSX, ";
175 break;
176 }
177 ss << "workitem_group_num: " << workitem_group_num
178 << ", reduction_item_num: " << reduction_item_num
179 << ", reduction_split_num: " << reduction_split_num
180 << ", thread_num: " << thread_num
181 << ", effective_thread_num: " << effective_thread_num
182 << ", attention_scratchpad_size_per_thread: "
183 << attention_scratchpad_size_per_thread
184 << ", reduction_scratchpad_size_per_kv_head: "
185 << reduction_scratchpad_size_per_kv_head << ", workitem groups:\n";
186 for (int32_t i = 0; i < workitem_group_num; ++i) {
187 ss << (workitem_groups_ptr + i)->to_string() << ",\n";
188 }
189
190 ss << "cu_workitem_num_per_thread: [";
191 for (int32_t i = 0; i < thread_num + 1; ++i) {
192 ss << cu_workitem_num_per_thread[i] << ", ";
193 }
194 ss << "]\n";
195
196 ss << "reduction items: \n";
197
198 for (int32_t i = 0; i < reduction_item_num; ++i) {
199 ss << (reduction_items_ptr + i)->to_string() << ",\n";
200 }
201
202 std::printf("%s", ss.str().c_str());
203 }
204 };
205
206 // Thread attention scratchpad contains:
207 // - Q: q_tile_size * head_dim * q_buffer_elem_size, gather Q heads, especially
208 // for GQA
209 // - Q@K^T: max_num_q_per_iter * k_tile_size * logits_buffer_elem_size, logits
210 // - Intermediate outputs: q_tile_size * head_dim * output_buffer_elem_size + 2
211 // * q_tile_size * 4, partial output, max + sum (float)
212 // Reduction scratchpad contains:
213 // - flags: bool array to indicate whether the split is finished
214 // - outputs: split_num * q_tile_size * head_dim * output_buffer_elem_size
215 // - max, sum: 2 * split_num * q_tile_size * 4
216 class AttentionScratchPad {
217 public:
218 AttentionScratchPad(int64_t thread_id,
219 const AttentionMetadata& attention_metadata,
220 void* scratchpad_ptr)
221 : thread_scratchpad_ptr(
222 static_cast<int8_t*>(scratchpad_ptr) +
223 thread_id *
224 attention_metadata.attention_scratchpad_size_per_thread),
225 reduction_scratchpad_ptr(
226 static_cast<int8_t*>(scratchpad_ptr) +
227 attention_metadata.thread_num *
228 attention_metadata.attention_scratchpad_size_per_thread),
229 reduction_scratchpad_size_per_kv_head(
230 attention_metadata.reduction_scratchpad_size_per_kv_head) {}
231
232 // for attention
233 void update(const int64_t head_dim, const int64_t q_buffer_elem_size,
234 const int64_t logits_buffer_elem_size,
235 const int64_t output_buffer_elem_size,
236 const int64_t max_num_q_per_iter, const int64_t q_head_tile_size,
237 const int64_t kv_tile_size) {
238 int64_t buffer_offset = 0;
239 q_buffer_offset_ = buffer_offset;
240 buffer_offset +=
241 calcu_q_buffer_size(q_head_tile_size, head_dim, q_buffer_elem_size);
242 logits_buffer_offset_ = buffer_offset;
243 buffer_offset += calcu_logits_buffer_size(max_num_q_per_iter, kv_tile_size,
244 logits_buffer_elem_size);
245 output_buffer_offset_ = buffer_offset;
246 buffer_offset += calcu_partial_output_buffer_size(
247 q_head_tile_size, head_dim, output_buffer_elem_size);
248 max_buffer_offset_ = buffer_offset;
249 buffer_offset += calcu_partial_output_max_sum_buffer_size(q_head_tile_size);
250 sum_buffer_offset_ = buffer_offset;
251 }
252
253 // for reduction
254 void update(const int32_t kv_head_idx, const int32_t total_split_num,
255 const int64_t head_dim, const int64_t q_head_tile_size,
256 const int64_t output_buffer_elem_size) {
257 int64_t buffer_offset = kv_head_idx * reduction_scratchpad_size_per_kv_head;
258 reduce_flag_buffer_offset_ = buffer_offset;
259 buffer_offset += calcu_reduce_flag_buffer_size(total_split_num);
260 reduce_output_buffer_offset_ = buffer_offset;
261 buffer_offset += calcu_reduce_output_buffer_size(
262 total_split_num, q_head_tile_size, head_dim, output_buffer_elem_size);
263 reduce_max_buffer_offset_ = buffer_offset;
264 buffer_offset +=
265 calcu_reduce_max_sum_buffer_size(total_split_num, q_head_tile_size);
266 reduce_sum_buffer_offset_ = buffer_offset;
267 }
268
269 template <typename T>
270 T* get_q_buffer() {
271 return reinterpret_cast<T*>(thread_scratchpad_ptr + q_buffer_offset_);
272 }
273
274 float* get_logits_buffer() {
275 return reinterpret_cast<float*>(thread_scratchpad_ptr +
276 logits_buffer_offset_);
277 }
278
279 float* get_output_buffer() {
280 return reinterpret_cast<float*>(thread_scratchpad_ptr +
281 output_buffer_offset_);
282 }
283
284 float* get_max_buffer() {
285 return reinterpret_cast<float*>(thread_scratchpad_ptr + max_buffer_offset_);
286 }
287
288 float* get_sum_buffer() {
289 return reinterpret_cast<float*>(thread_scratchpad_ptr + sum_buffer_offset_);
290 }
291
292 volatile bool* get_reduce_flag_buffer() {
293 return reinterpret_cast<volatile bool*>(reduction_scratchpad_ptr +
294 reduce_flag_buffer_offset_);
295 }
296
297 float* get_reduce_output_buffer() {
298 return reinterpret_cast<float*>(reduction_scratchpad_ptr +
299 reduce_output_buffer_offset_);
300 }
301
302 float* get_reduce_max_buffer() {
303 return reinterpret_cast<float*>(reduction_scratchpad_ptr +
304 reduce_max_buffer_offset_);
305 }
306
307 float* get_reduce_sum_buffer() {
308 return reinterpret_cast<float*>(reduction_scratchpad_ptr +
309 reduce_sum_buffer_offset_);
310 }
311
312 int64_t get_thread_scratchpad_size() const {
313 return 2 * sum_buffer_offset_ - max_buffer_offset_;
314 }
315
316 int64_t get_reduction_scratchpad_size() const {
317 return 2 * reduce_sum_buffer_offset_ - reduce_max_buffer_offset_;
318 }
319
320 private:
321 static int64_t round_to_64(const int64_t num) {
322 return ((num + 63) >> 6) << 6;
323 }
324
325 static int64_t calcu_q_buffer_size(const int64_t q_tile_size,
326 const int64_t head_dim,
327 const int64_t elem_size) {
328 return round_to_64(q_tile_size * head_dim * elem_size);
329 }
330
331 static int64_t calcu_logits_buffer_size(const int64_t max_num_q_per_iter,
332 const int64_t k_tile_size,
333 const int64_t elem_size) {
334 return round_to_64(elem_size * max_num_q_per_iter * k_tile_size);
335 }
336
337 static int64_t calcu_partial_output_buffer_size(const int64_t q_tile_size,
338 const int64_t head_dim,
339 const int64_t elem_size) {
340 return round_to_64(q_tile_size * head_dim * elem_size);
341 }
342
343 static int64_t calcu_partial_output_max_sum_buffer_size(
344 const int64_t q_tile_size) {
345 return round_to_64(q_tile_size * sizeof(float));
346 }
347
348 static int64_t calcu_reduce_flag_buffer_size(const int64_t total_split_num) {
349 return round_to_64(total_split_num * sizeof(bool));
350 }
351
352 static int64_t calcu_reduce_max_sum_buffer_size(
353 const int64_t total_split_num, const int32_t q_head_tile_size) {
354 return round_to_64(total_split_num * q_head_tile_size * sizeof(float));
355 }
356
357 static int64_t calcu_reduce_output_buffer_size(
358 const int64_t total_split_num, const int64_t q_head_tile_size,
359 const int64_t head_dim, const int64_t output_buffer_elem_size) {
360 return round_to_64(total_split_num * q_head_tile_size * head_dim *
361 output_buffer_elem_size);
362 }
363
364 private:
365 int8_t* thread_scratchpad_ptr;
366 int8_t* reduction_scratchpad_ptr;
367 int64_t reduction_scratchpad_size_per_kv_head;
368 // attention buffers
369 int64_t q_buffer_offset_;
370 int64_t logits_buffer_offset_;
371 int64_t output_buffer_offset_;
372 int64_t max_buffer_offset_;
373 int64_t sum_buffer_offset_;
374 // reduction buffers
375 int64_t reduce_flag_buffer_offset_;
376 int64_t reduce_output_buffer_offset_;
377 int64_t reduce_max_buffer_offset_;
378 int64_t reduce_sum_buffer_offset_;
379 };
380
381 class AttentionScheduler {
382 public:
383 struct ScheduleInput {
384 int32_t num_reqs;
385 int32_t elem_size;
386 int32_t q_buffer_elem_size;
387 int32_t logits_buffer_elem_size;
388 int32_t output_buffer_elem_size;
389 int32_t num_heads_q;
390 int32_t num_heads_kv;
391 int32_t head_dim;
392 int32_t* query_start_loc;
393 int32_t* seq_lens;
394 int32_t sliding_window_size;
395 bool causal;
396 cpu_attention::ISA isa;
397 int32_t max_num_q_per_iter; // max Q head num can be hold in registers
398 int32_t kv_block_alignment; // context length alignment requirement
399 bool enable_kv_split;
400 bool* dynamic_causal;
401 };
402
403 static constexpr int32_t MaxQTileIterNum = 128;
404
405 AttentionScheduler()
406 : available_cache_size_(cpu_utils::get_available_l2_size()) {}
407
408 torch::Tensor schedule(const ScheduleInput& input) const {
409 const bool causal = input.causal;
410 const bool is_dynamic_causal = input.dynamic_causal != nullptr;
411 const int32_t thread_num = cpu_utils::get_max_threads();
412 const int64_t cache_size = cpu_utils::get_available_l2_size();
413 const int32_t max_num_q_per_iter = input.max_num_q_per_iter;
414 const int32_t kv_len_alignment = input.kv_block_alignment;
415 bool has_decode_request = false;
416 bool decode_only_batch = true;
417 for (int32_t req_id = 0; req_id < input.num_reqs; ++req_id) {
418 const int32_t q_token_num =
419 input.query_start_loc[req_id + 1] - input.query_start_loc[req_id];
420 has_decode_request = has_decode_request || (q_token_num == 1);
421 decode_only_batch = decode_only_batch && (q_token_num == 1);
422 }
423 const int32_t original_q_head_per_kv =
424 input.num_heads_q / input.num_heads_kv;
425 int32_t q_head_per_kv = original_q_head_per_kv;
426 const bool supports_gqa = original_q_head_per_kv <= max_num_q_per_iter;
427 const bool use_gqa_fast_path = supports_gqa && decode_only_batch;
428 const bool use_gqa_scratchpad = supports_gqa && has_decode_request;
429 if (!use_gqa_scratchpad) {
430 q_head_per_kv = 1; // fallback to MHA
431 }
432 const int32_t min_split_kv_len =
433 ((max_num_q_per_iter * 4 + kv_len_alignment - 1) / kv_len_alignment) *
434 kv_len_alignment;
435 const int32_t max_num_q_token_per_iter = max_num_q_per_iter / q_head_per_kv;
436 const int64_t default_tile_size = calcu_default_tile_size(
437 cache_size, input.head_dim, input.elem_size, input.q_buffer_elem_size,
438 input.logits_buffer_elem_size, input.output_buffer_elem_size,
439 max_num_q_per_iter, max_num_q_per_iter);
440 const int32_t default_tile_token_num = default_tile_size / q_head_per_kv;
441 const int32_t split_kv_q_token_num_threshold =
442 input.enable_kv_split ? 1 : 0;
443 const int32_t sliding_window_size = input.sliding_window_size;
444 TORCH_CHECK_LE(split_kv_q_token_num_threshold * q_head_per_kv, 16);
445
446 // get total kv len
447 int64_t total_kv_len = 0;
448 for (int32_t req_id = 0; req_id < input.num_reqs; ++req_id) {
449 const int32_t seq_len = input.seq_lens[req_id];
450 const int32_t q_token_num =
451 input.query_start_loc[req_id + 1] - input.query_start_loc[req_id];
452 const bool req_causal =
453 is_dynamic_causal ? input.dynamic_causal[req_id] : causal;
454 const int32_t q_start_pos = seq_len - q_token_num;
455 const int32_t kv_start_pos = 0;
456 const int32_t kv_end_pos = seq_len;
457
458 for (int32_t token_id = 0; token_id < q_token_num;
459 token_id += max_num_q_token_per_iter) {
460 const int32_t q_tile_token_num =
461 std::min(max_num_q_token_per_iter, q_token_num - token_id);
462 const int32_t q_tile_pos_left = q_start_pos + token_id;
463 const int32_t q_tile_pos_right = q_tile_pos_left + q_tile_token_num;
464 const auto [kv_tile_pos_left, kv_tile_pos_right] = calcu_kv_tile_pos(
465 kv_start_pos, kv_end_pos, q_tile_pos_left, q_tile_pos_right,
466 sliding_window_size, req_causal);
467 const auto [aligned_kv_tile_pos_left, aligned_kv_tile_pos_right] =
468 align_kv_tile_pos(kv_tile_pos_left, kv_tile_pos_right,
469 kv_len_alignment);
470
471 int32_t curr_kv_len =
472 aligned_kv_tile_pos_right - aligned_kv_tile_pos_left;
473 total_kv_len += curr_kv_len;
474 }
475 }
476 const int64_t kv_len_per_thread =
477 (((total_kv_len / thread_num) + kv_len_alignment - 1) /
478 kv_len_alignment) *
479 kv_len_alignment;
480 std::vector<AttentionWorkItemGroup> workitems;
481 std::vector<ReductionWorkItemGroup> reduce_workitems;
482 workitems.reserve(1024);
483 reduce_workitems.reserve(1024);
484 std::vector<int32_t> workitem_num_per_thread(thread_num, 0);
485
486 // split tasks
487 int32_t curr_thread_id = 0;
488 int64_t remaining_kv_len = kv_len_per_thread;
489 int32_t cum_split_num = 0;
490 for (int32_t req_id = 0; req_id < input.num_reqs; ++req_id) {
491 const int32_t seq_len = input.seq_lens[req_id];
492 const int32_t q_token_num =
493 input.query_start_loc[req_id + 1] - input.query_start_loc[req_id];
494 const bool req_causal =
495 is_dynamic_causal ? input.dynamic_causal[req_id] : causal;
496 const int32_t q_start_pos = seq_len - q_token_num;
497 const int32_t kv_start_pos = 0;
498 const int32_t kv_end_pos = seq_len;
499 int32_t local_split_id = 0;
500
501 AttentionWorkItemGroup curr_workitem(req_id, 0, 0, seq_len);
502 for (int32_t token_id = 0; token_id < q_token_num;
503 token_id += max_num_q_token_per_iter) {
504 const int32_t q_tile_token_num =
505 std::min(max_num_q_token_per_iter, q_token_num - token_id);
506 const int32_t q_tile_pos_left = q_start_pos + token_id;
507 const int32_t q_tile_pos_right = q_tile_pos_left + q_tile_token_num;
508 const auto [kv_tile_pos_left, kv_tile_pos_right] = calcu_kv_tile_pos(
509 kv_start_pos, kv_end_pos, q_tile_pos_left, q_tile_pos_right,
510 sliding_window_size, req_causal);
511 const auto [aligned_kv_tile_pos_left, aligned_kv_tile_pos_right] =
512 align_kv_tile_pos(kv_tile_pos_left, kv_tile_pos_right,
513 kv_len_alignment);
514 int32_t curr_kv_len =
515 aligned_kv_tile_pos_right - aligned_kv_tile_pos_left;
516 int32_t kv_token_pos_start = aligned_kv_tile_pos_left;
517
518 while (curr_kv_len > 0) {
519 if (curr_kv_len <= (remaining_kv_len + min_split_kv_len) ||
520 curr_thread_id == (thread_num - 1)) {
521 curr_workitem.q_token_num += q_tile_token_num;
522 curr_workitem.total_kv_len += curr_kv_len;
523 remaining_kv_len -= curr_kv_len;
524 curr_kv_len = 0;
525
526 if (remaining_kv_len < 0) {
527 // stop to accept more workitems
528 remaining_kv_len -= min_split_kv_len;
529 }
530
531 if (curr_workitem.kv_split_pos_start != 0) {
532 // got a partial kv spilt, need to create a single workitem
533 curr_workitem.split_id = cum_split_num;
534 curr_workitem.local_split_id = local_split_id;
535 workitems.emplace_back(curr_workitem);
536 ++workitem_num_per_thread[curr_thread_id];
537 ++reduce_workitems.back().split_num;
538 ++cum_split_num;
539
540 curr_workitem = AttentionWorkItemGroup(
541 req_id, token_id + max_num_q_token_per_iter, 0, seq_len);
542 }
543
544 break;
545 }
546
547 if (remaining_kv_len < min_split_kv_len &&
548 (curr_workitem.total_kv_len > 0 ||
549 workitem_num_per_thread[curr_thread_id] > 0)) {
550 // remaining_kv_len is too short, and have allocated workitems, just
551 // leave to next thread
552 if (curr_workitem.total_kv_len > 0) {
553 workitems.emplace_back(curr_workitem);
554 ++workitem_num_per_thread[curr_thread_id];
555 curr_workitem =
556 AttentionWorkItemGroup(req_id, token_id, 0, seq_len);
557 }
558
559 // switch to next thread
560 ++curr_thread_id;
561 remaining_kv_len = kv_len_per_thread;
562
563 // retry this iteration
564 continue;
565 }
566
567 // only split tail splits with q_tile_token_num <=
568 // split_kv_q_token_num_threshold
569 if (token_id + max_num_q_token_per_iter < q_token_num ||
570 q_tile_token_num > split_kv_q_token_num_threshold) {
571 // if requires a new q tile iteration and already has workitems,
572 // leave this workitem to next thread
573 if (curr_workitem.q_token_num % default_tile_token_num == 0 &&
574 (curr_workitem.total_kv_len > 0 ||
575 workitem_num_per_thread[curr_thread_id] > 0)) {
576 if (curr_workitem.total_kv_len > 0) {
577 workitems.emplace_back(curr_workitem);
578 ++workitem_num_per_thread[curr_thread_id];
579 }
580 curr_workitem =
581 AttentionWorkItemGroup(req_id, token_id, 0, seq_len);
582
583 // switch to next thread
584 ++curr_thread_id;
585 remaining_kv_len = kv_len_per_thread;
586 }
587
588 curr_workitem.q_token_num += q_tile_token_num;
589 curr_workitem.total_kv_len += curr_kv_len;
590 remaining_kv_len -= curr_kv_len;
591 curr_kv_len = 0;
592 break;
593 }
594
595 // split kv
596 if (curr_workitem.total_kv_len > 0) {
597 // write back curr workitem
598 workitems.emplace_back(curr_workitem);
599 ++workitem_num_per_thread[curr_thread_id];
600 }
601
602 if (kv_token_pos_start == aligned_kv_tile_pos_left) {
603 // first split, init the workitem
604 reduce_workitems.emplace_back(ReductionWorkItemGroup(
605 req_id, token_id, q_tile_token_num, cum_split_num));
606 }
607
608 int32_t spilt_size =
609 std::min(std::max(remaining_kv_len, (int64_t)min_split_kv_len),
610 (int64_t)curr_kv_len);
611 curr_workitem =
612 AttentionWorkItemGroup(req_id, token_id, kv_token_pos_start,
613 kv_token_pos_start + spilt_size);
614 curr_workitem.q_token_num += q_tile_token_num;
615 curr_workitem.total_kv_len += spilt_size;
616 curr_workitem.split_id = cum_split_num;
617 curr_workitem.local_split_id = local_split_id;
618 workitems.emplace_back(curr_workitem);
619 ++workitem_num_per_thread[curr_thread_id];
620 ++reduce_workitems.back().split_num;
621 ++cum_split_num;
622 ++local_split_id;
623
624 kv_token_pos_start += spilt_size;
625 curr_kv_len -= spilt_size;
626 curr_workitem = AttentionWorkItemGroup(req_id, token_id,
627 kv_token_pos_start, seq_len);
628
629 // switch to next thread
630 ++curr_thread_id;
631 remaining_kv_len = kv_len_per_thread;
632 }
633 }
634
635 if (curr_workitem.total_kv_len > 0) {
636 // write back curr workitem
637 workitems.emplace_back(curr_workitem);
638 ++workitem_num_per_thread[curr_thread_id];
639 }
640 }
641
642 int64_t metadata_tensor_size =
643 sizeof(AttentionMetadata) +
644 workitems.size() * sizeof(AttentionWorkItemGroup) +
645 reduce_workitems.size() * sizeof(ReductionWorkItemGroup);
646 auto options =
647 torch::TensorOptions().dtype(torch::kInt8).device(torch::kCPU);
648 torch::Tensor metadata_tensor =
649 torch::empty({metadata_tensor_size}, options);
650 AttentionMetadata* metadata_ptr = new (metadata_tensor.data_ptr())
651 AttentionMetadata(input.isa, workitems.size(), reduce_workitems.size(),
652 cum_split_num, split_kv_q_token_num_threshold);
653 AttentionWorkItemGroup* workitem_groups_ptr =
654 metadata_ptr->workitem_groups_ptr;
655 ReductionWorkItemGroup* reduction_items_ptr =
656 metadata_ptr->reduction_items_ptr;
657 std::memcpy(workitem_groups_ptr, workitems.data(),
658 workitems.size() * sizeof(AttentionWorkItemGroup));
659 std::memcpy(reduction_items_ptr, reduce_workitems.data(),
660 reduce_workitems.size() * sizeof(ReductionWorkItemGroup));
661
662 int32_t effective_thread_num = 0;
663 for (; effective_thread_num < thread_num; ++effective_thread_num) {
664 if (workitem_num_per_thread[effective_thread_num] == 0) {
665 break;
666 }
667 }
668
669 std::memcpy(metadata_ptr->cu_workitem_num_per_thread + 1,
670 workitem_num_per_thread.data(),
671 workitem_num_per_thread.size() * sizeof(int32_t));
672 for (int32_t i = 1; i <= thread_num; ++i) {
673 metadata_ptr->cu_workitem_num_per_thread[i] +=
674 metadata_ptr->cu_workitem_num_per_thread[i - 1];
675 }
676 metadata_ptr->effective_thread_num = effective_thread_num;
677
678 {
679 AttentionScratchPad sc(0, *metadata_ptr, 0x0);
680 int64_t max_attention_scratchpad_size = 0;
681
682 for (const AttentionWorkItemGroup& item : workitems) {
683 const bool curr_use_gqa =
684 use_gqa_fast_path || (supports_gqa && item.q_token_num == 1);
685 const int32_t curr_q_heads_per_kv =
686 curr_use_gqa ? original_q_head_per_kv : 1;
687 const int32_t curr_default_q_tile_token_num =
688 default_tile_size / curr_q_heads_per_kv;
689
690 for (int32_t q_token_offset = 0; q_token_offset < item.q_token_num;
691 q_token_offset += curr_default_q_tile_token_num) {
692 const int32_t actual_q_token_num = std::min(
693 curr_default_q_tile_token_num, item.q_token_num - q_token_offset);
694 const int32_t q_head_tile_size =
695 actual_q_token_num * curr_q_heads_per_kv;
696 const int32_t rounded_q_head_tile_size =
697 ((q_head_tile_size + max_num_q_per_iter - 1) /
698 max_num_q_per_iter) *
699 max_num_q_per_iter;
700
701 const int64_t n = AttentionScheduler::calcu_tile_size_with_constant_q(
702 cache_size, input.head_dim, input.elem_size,
703 input.q_buffer_elem_size, input.logits_buffer_elem_size,
704 input.output_buffer_elem_size, max_num_q_per_iter,
705 kv_len_alignment, rounded_q_head_tile_size,
706 rounded_q_head_tile_size <= max_num_q_per_iter);
707
708 sc.update(input.head_dim, input.q_buffer_elem_size,
709 input.logits_buffer_elem_size,
710 input.output_buffer_elem_size, max_num_q_per_iter,
711 rounded_q_head_tile_size, n);
712
713 max_attention_scratchpad_size = std::max(
714 max_attention_scratchpad_size, sc.get_thread_scratchpad_size());
715 }
716 }
717
718 metadata_ptr->attention_scratchpad_size_per_thread =
719 ((max_attention_scratchpad_size + 63) / 64) * 64;
720
721 int32_t max_reduction_q_head_tile_size = 0;
722 for (const ReductionWorkItemGroup& item : reduce_workitems) {
723 const bool curr_use_gqa =
724 use_gqa_fast_path || (supports_gqa && item.q_token_id_num == 1);
725 const int32_t curr_q_heads_per_kv =
726 curr_use_gqa ? original_q_head_per_kv : 1;
727
728 max_reduction_q_head_tile_size =
729 std::max(max_reduction_q_head_tile_size,
730 item.q_token_id_num * curr_q_heads_per_kv);
731 }
732
733 sc.update(0, metadata_ptr->reduction_split_num, input.head_dim,
734 max_reduction_q_head_tile_size, input.output_buffer_elem_size);
735 metadata_ptr->reduction_scratchpad_size_per_kv_head =
736 ((sc.get_reduction_scratchpad_size() + 63) / 64) * 64;
737 }
738 int64_t scratchpad_size =
739 metadata_ptr->attention_scratchpad_size_per_thread *
740 metadata_ptr->thread_num +
741 metadata_ptr->reduction_scratchpad_size_per_kv_head *
742 (use_gqa_fast_path ? input.num_heads_kv : input.num_heads_q);
743 cpu_utils::ScratchPadManager::get_scratchpad_manager()->realloc(
744 scratchpad_size);
745
746 // metadata_ptr->print();
747
748 // test out of boundary access
749 // {
750 // float* cache_ptr =
751 // cpu_utils::ScratchPadManager::getl_scratchpad_manager()->get_data<float>();
752 // for (int64_t i = 0; i < scratchpad_size / sizeof(float); ++i) {
753 // cache_ptr[i] = std::numeric_limits<float>::quiet_NaN();
754 // }
755 // }
756
757 return metadata_tensor;
758 }
759
760 FORCE_INLINE static std::pair<int32_t, int32_t> calcu_sliding_window_size(
761 int32_t window_size, bool causal) {
762 int32_t left_sliding_window_size, right_sliding_window_size;
763 if (window_size != -1) {
764 left_sliding_window_size = window_size - 1;
765 if (causal) {
766 right_sliding_window_size = 0;
767 } else {
768 right_sliding_window_size = window_size - 1;
769 }
770 } else {
771 left_sliding_window_size = -1;
772 if (causal) {
773 right_sliding_window_size = 0;
774 } else {
775 right_sliding_window_size = -1;
776 }
777 }
778
779 return {left_sliding_window_size, right_sliding_window_size};
780 }
781
782 FORCE_INLINE static std::pair<int32_t, int32_t> calcu_kv_tile_pos(
783 int32_t kv_left_pos, int32_t kv_right_pos, int32_t q_left_pos,
784 int32_t q_right_pos, int32_t window_size, bool causal) {
785 auto [left_sliding_window_size, right_sliding_window_size] =
786 calcu_sliding_window_size(window_size, causal);
787
788 if (left_sliding_window_size != -1) {
789 kv_left_pos =
790 std::max(kv_left_pos, q_left_pos - left_sliding_window_size);
791 }
792 if (right_sliding_window_size != -1) {
793 kv_right_pos =
794 std::min(kv_right_pos, q_right_pos + right_sliding_window_size);
795 }
796 return {kv_left_pos, kv_right_pos};
797 }
798
799 FORCE_INLINE static std::pair<int32_t, int32_t> align_kv_tile_pos(
800 int32_t kv_left_pos, int32_t kv_right_pos, int32_t align_factor) {
801 kv_left_pos = (kv_left_pos / align_factor) * align_factor;
802 kv_right_pos =
803 ((kv_right_pos + align_factor - 1) / align_factor) * align_factor;
804 return {kv_left_pos, kv_right_pos};
805 }
806
807 static int64_t calcu_default_tile_size(int64_t cache_size, int64_t head_dim,
808 int64_t elem_size,
809 int64_t q_buffer_elem_size,
810 int64_t logits_buffer_elem_size,
811 int64_t output_buffer_elem_size,
812 int64_t max_num_q_per_iter,
813 int64_t round_size) {
814 // For CPU, different from CUDA, Q@K^T results should also be hold in cache,
815 // using float32. Intermediate outputs should be float32 to be compatible
816 // with AMX Then the cache includes:
817 // - Q: q_tile_size * head_dim * q_buffer_elem_size
818 // - K, V: 2 * k_tile_size * head_dim * elem_size
819 // - Q@K^T: max_num_q_per_iter * k_tile_size * logits_buffer_elem_size
820 // - Intermediate outputs: q_tile_size * head_dim * output_buffer_elem_size
821 // By default, let tile_size = q_tile_size = k_tile_size. To record
822 // is_first_iter states in a static array, require the default tile <= 128 *
823 // max_num_q_per_iter
824
825 int64_t tile_size =
826 cache_size / (head_dim * (q_buffer_elem_size + 2 * elem_size +
827 output_buffer_elem_size) +
828 max_num_q_per_iter * logits_buffer_elem_size);
829 tile_size = std::min(tile_size, MaxQTileIterNum * max_num_q_per_iter);
830 int64_t rounded_tile_size = (tile_size / round_size) * round_size;
831 return std::max(rounded_tile_size, round_size);
832 }
833
834 static int64_t calcu_tile_size_with_constant_q(
835 int64_t cache_size, int64_t head_dim, int64_t elem_size,
836 int64_t q_buffer_elem_size, int64_t logits_buffer_elem_size,
837 int64_t output_buffer_elem_size, int64_t max_num_q_per_iter,
838 int64_t round_size, int64_t q_tile_size, bool one_round) {
839 // calculate tile_size with known q_tile_size
840 // If one_round is True, the outer Q tile loop time is 1, then the K,V will
841 // not be included in the cache
842 int64_t tile_size;
843 if (one_round) {
844 tile_size =
845 (cache_size - q_tile_size * head_dim *
846 (q_buffer_elem_size + output_buffer_elem_size)) /
847 (logits_buffer_elem_size * max_num_q_per_iter);
848 } else {
849 tile_size =
850 (cache_size - q_tile_size * head_dim *
851 (q_buffer_elem_size + output_buffer_elem_size)) /
852 (logits_buffer_elem_size * max_num_q_per_iter +
853 2 * head_dim * elem_size);
854 }
855 int64_t rounded_tile_size = (tile_size / round_size) * round_size;
856 return std::max(rounded_tile_size, round_size);
857 }
858
859 private:
860 int64_t available_cache_size_;
861 };
862
863 struct AttentionInput {
864 AttentionMetadata* metadata;
865 int32_t num_tokens;
866 int32_t num_heads;
867 int32_t num_kv_heads;
868 int32_t block_size;
869 void* query;
870 int64_t query_num_tokens_stride;
871 int64_t query_num_heads_stride;
872 int64_t cache_num_blocks_stride;
873 int64_t cache_num_kv_heads_stride;
874 int64_t blt_num_tokens_stride;
875 void* key_cache;
876 void* value_cache;
877 void* output;
878 int32_t* query_start_loc;
879 int32_t* seq_lens;
880 int32_t* block_table;
881 float* alibi_slopes;
882 // Attention sinks pointer. May reference bf16 or fp32 data depending on
883 // `s_aux_is_bf16`. bf16 sinks keep the native bf16 path; anything else is
884 // provided as fp32 and executed in full float precision.
885 const void* s_aux;
886 bool s_aux_is_bf16;
887 bool* dynamic_causal;
888 float scale;
889 bool causal;
890 int32_t sliding_window_size;
891 float softcap;
892 // FP8 KV cache scales (used by FP8 attention implementations)
893 float k_scale_fp8 = 1.0f;
894 float v_scale_fp8 = 1.0f;
895 // FP8 query scale (used by AMX_FP8 which quantizes query inside the kernel)
896 float q_scale_fp8 = 1.0f;
897 };
898
899 #define DEFINE_CPU_ATTENTION_PARAMS \
900 q_buffer_t *__restrict__ q_heads_buffer, \
901 kv_cache_t *__restrict__ k_head_cache_ptr, \
902 kv_cache_t *__restrict__ v_head_cache_ptr, \
903 logits_buffer_t *__restrict__ logits_buffer, \
904 float *__restrict__ partial_q_buffer, float *__restrict__ max_buffer, \
905 float *__restrict__ sum_buffer, int32_t *__restrict__ block_table, \
906 const int32_t kv_end_pos, const int32_t kv_tile_start_pos, \
907 const int32_t kv_tile_end_pos, const int32_t kv_tile_token_num, \
908 const int64_t kv_cache_num_blocks_stride, const int32_t q_head_num, \
909 const int32_t q_token_num, const int32_t q_tile_start_pos, \
910 const int32_t q_heads_per_kv, const int32_t block_size, \
911 const int32_t left_window_size, const int32_t right_window_size, \
912 float scale, const float softcap_scale, \
913 const float *__restrict__ alibi_slopes, const bool is_first_iter, \
914 const bool use_sink, const bool debug_info
915
916 #define CPU_ATTENTION_PARAMS \
917 q_heads_buffer, k_head_cache_ptr, v_head_cache_ptr, logits_buffer, \
918 partial_q_buffer, max_buffer, sum_buffer, block_table, kv_end_pos, \
919 kv_tile_start_pos, kv_tile_end_pos, kv_tile_token_num, \
920 kv_cache_num_blocks_stride, q_head_num, q_token_num, q_tile_start_pos, \
921 q_heads_per_kv, block_size, left_window_size, right_window_size, scale, \
922 softcap_scale, alibi_slopes, is_first_iter, use_sink, debug_info
923
924 enum class AttentionGemmPhase { QK, PV };
925
926 template <typename T>
927 struct VecTypeTrait {
928 using vec_t = void;
929 };
930
931 template <>
932 struct VecTypeTrait<float> {
933 using vec_t = vec_op::FP32Vec16;
934 };
935
936 template <>
937 struct VecTypeTrait<c10::BFloat16> {
938 using vec_t = vec_op::BF16Vec16;
939 };
940
941 template <>
942 struct VecTypeTrait<c10::Half> {
943 using vec_t = vec_op::FP16Vec16;
944 };
945
946 template <typename T>
947 void print_logits(const char* name, T* ptr, int32_t row, int32_t col,
948 int32_t stride) {
949 std::stringstream ss;
950 ss << std::fixed << std::setprecision(5) << name << ": [\n";
951 auto* curr_logits_buffer = ptr;
952 for (int32_t m = 0; m < row; ++m) {
953 for (int32_t n = 0; n < col; ++n) {
954 ss << curr_logits_buffer[n] << ", ";
955 }
956 ss << "\n";
957 curr_logits_buffer += stride;
958 }
959 ss << "]\n";
960 std::printf("%s", ss.str().c_str());
961 }
962
963 // This allows us to store probabilities in a packed format
964 // for GEMM implementations that need packed A.
965 template <typename attention_impl_t, typename = void>
966 struct ProbabilityTokenStore {
967 using prob_buffer_t = typename attention_impl_t::prob_buffer_t;
968 static constexpr int32_t TokenStride = 1;
969
970 FORCE_INLINE static void store_probabilities(
971 prob_buffer_t* __restrict__ probability, const vec_op::FP32Vec16& values,
972 const int32_t, const int64_t) {
973 using prob_buffer_vec_t = typename VecTypeTrait<prob_buffer_t>::vec_t;
974 prob_buffer_vec_t output_vec(values);
975 output_vec.save(probability);
976 }
977 };
978
979 template <typename attention_impl_t>
980 struct ProbabilityTokenStore<
981 attention_impl_t,
982 std::void_t<typename attention_impl_t::ProbabilityTokenStore>>
983 : attention_impl_t::ProbabilityTokenStore {};
984
985 template <typename attention_impl_t>
986 class AttentionMainLoop {
987 public:
988 using query_t = typename attention_impl_t::query_t;
989 using q_buffer_t = typename attention_impl_t::q_buffer_t;
990 using kv_cache_t = typename attention_impl_t::kv_cache_t;
991 using logits_buffer_t = typename attention_impl_t::logits_buffer_t;
992 using partial_output_buffer_t =
993 typename attention_impl_t::partial_output_buffer_t;
994 using prob_buffer_t = typename attention_impl_t::prob_buffer_t;
995 using probability_token_store_t = ProbabilityTokenStore<attention_impl_t>;
996
997 static constexpr int64_t max_q_head_num_per_iter =
998 attention_impl_t::MaxQHeadNumPerIteration;
999 static constexpr int64_t blocksize_alignment =
1000 attention_impl_t::BlockSizeAlignment;
1001 static constexpr int64_t headdim_alignment =
1002 attention_impl_t::HeadDimAlignment;
1003 static constexpr int64_t head_dim = attention_impl_t::HeadDim;
1004 static constexpr int32_t probability_token_stride =
1005 probability_token_store_t::TokenStride;
1006 static constexpr ISA ISAType = attention_impl_t::ISAType;
1007 static constexpr bool scale_on_logits =
1008 attention_impl_t::scale_on_logits; // apply scale on logits, otherwise
1009 // apply scale on q_buffer
1010
1011 template <typename tile_gemm_t>
1012 class Attention {
1013 public:
1014 // Args:
1015 // - q_heads_buffer: [MaxQHeadNumPerIteration, head_dim]
1016 // - k_head_cache_ptr: [num_blocks, block_size * head_dim]
1017 // - v_head_cache_ptr: [num_blocks, block_size * head_dim]
1018 // - logits_buffer: [MaxQHeadNumPerIteration, kv_tile_token_num], store Q@K
1019 // - logits partial_q_buffer: [MaxQHeadNumPerIteration, head_dim], store
1020 // partial output
1021 // - max_buffer: [MaxQHeadNumPerIteration, 1], store max logits
1022 // - sum_buffer: [MaxQHeadNumPerIteration, 1], store sum of exp
1023 // - block_table
1024 // - kv_end_pos: un-aligned end position of KV cache
1025 // - kv_tile_start_pos: start position of KV cache, aligned to
1026 // BlockSizeAlignment
1027 // - kv_tile_end_pos: end position of KV cache, aligned to
1028 // BlockSizeAlignment
1029 // - kv_tile_token_num: KV token num, aligned to BlockSizeAlignment
1030 // - kv_cache_num_blocks_stride
1031 // - q_head_num: head num of q_tile
1032 // - q_token_num: token num of q_tile, should be q_head_num /
1033 // q_heads_per_kv
1034 // - q_tile_start_pos: start pos of the first token in q_heads_buffer
1035 // - q_heads_per_kv
1036 // - block_size
1037 // - left_window_size
1038 // - right_window_size
1039 // - scale
1040 // - softcap_scale
1041 // - alibi_slopes
1042 // - is_first_iter
1043 // - use_sink
1044 // - debug_info
1045 void operator()(DEFINE_CPU_ATTENTION_PARAMS) {
1046 // k_cache_token_group_stride: stride of K cache when move to next
1047 // BlockSizeAlignment tokens in a block
1048 const int64_t k_cache_token_group_stride =
1049 attention_impl_t::k_cache_token_group_stride(block_size);
1050 // v_cache_token_group_stride: stride of V cache when move to next
1051 // BlockSizeAlignment tokens in a block
1052 const int64_t v_cache_token_group_stride =
1053 attention_impl_t::v_cache_token_group_stride(block_size);
1054 // v_cache_head_group_stride: stride of V cache when move to next
1055 // HeadDimAlignment head dims in a block
1056 const int64_t v_cache_head_group_stride =
1057 attention_impl_t::v_cache_head_group_stride(block_size);
1058 const int32_t token_group_num = kv_tile_token_num / blocksize_alignment;
1059 const int32_t token_group_num_per_block =
1060 block_size / blocksize_alignment;
1061 const int32_t start_block_idx = kv_tile_start_pos / block_size;
1062 const int32_t start_block_offset = kv_tile_start_pos % block_size;
1063 const int32_t start_block_group_offset =
1064 start_block_offset / blocksize_alignment;
1065 const int32_t end_block_idx =
1066 (kv_tile_start_pos + kv_tile_token_num - 1) / block_size + 1;
1067
1068 // compute Q@K logits
1069 {
1070 int32_t curr_group_offset =
1071 start_block_group_offset * k_cache_token_group_stride;
1072 int32_t curr_group_num_in_block =
1073 token_group_num_per_block - start_block_group_offset;
1074 int32_t remaining_group_num = token_group_num;
1075 logits_buffer_t* curr_logits_buffer = logits_buffer;
1076 for (int32_t block_idx = start_block_idx; block_idx < end_block_idx;
1077 ++block_idx) {
1078 int32_t physical_block_idx = block_table[block_idx];
1079 kv_cache_t* k_cache_block_ptr =
1080 k_head_cache_ptr +
1081 physical_block_idx * kv_cache_num_blocks_stride +
1082 curr_group_offset;
1083 curr_group_num_in_block =
1084 std::min(remaining_group_num, curr_group_num_in_block);
1085
1086 for (int32_t block_group_idx = 0;
1087 block_group_idx < curr_group_num_in_block; ++block_group_idx) {
1088 // logits_tile = q_tile @ k_tile, [MaxQHeadNumPerIteration,
1089 // BlockSizeAlignment] = [MaxQHeadNumPerIteration, head_dim] @
1090 // [head_dim, BlockSizeAlignment]
1091
1092 // By default, logits_buffer, q_buffer and k_cache are row-major,
1093 // but may be packed by ISA implementation.
1094 tile_gemm_t::template gemm<AttentionGemmPhase::QK, head_dim>(
1095 q_head_num, q_heads_buffer, k_cache_block_ptr,
1096 curr_logits_buffer, head_dim, block_size, kv_tile_token_num,
1097 block_size, head_dim, false);
1098
1099 if constexpr (scale_on_logits) {
1100 float* __restrict__ scale_curr_logits_buffer = curr_logits_buffer;
1101 vec_op::FP32Vec16 scale_vec(scale);
1102 for (int32_t i = 0; i < q_head_num; ++i) {
1103 static_assert(blocksize_alignment % 16 == 0);
1104 constexpr int32_t vec_num = blocksize_alignment / 16;
1105 vec_op::unroll_loop<int32_t, vec_num>([&](int32_t vec_idx) {
1106 vec_op::FP32Vec16 vec(scale_curr_logits_buffer +
1107 vec_idx * 16);
1108 vec = vec * scale_vec;
1109 vec.save(scale_curr_logits_buffer + vec_idx * 16);
1110 });
1111 scale_curr_logits_buffer += kv_tile_token_num;
1112 }
1113 }
1114
1115 // Move buffer ptrs
1116 k_cache_block_ptr += k_cache_token_group_stride;
1117 curr_logits_buffer += blocksize_alignment;
1118 }
1119
1120 // Update
1121 remaining_group_num -= curr_group_num_in_block;
1122 curr_group_offset = 0;
1123 curr_group_num_in_block = token_group_num_per_block;
1124 }
1125 }
1126
1127 // process logits
1128 {
1129 if (softcap_scale != 0.0f) {
1130 apply_softcap(logits_buffer, kv_tile_token_num, q_head_num,
1131 kv_tile_token_num, softcap_scale);
1132 // print_logits("softcap raw logits", logits_buffer, q_head_num,
1133 // kv_tile_token_num, kv_tile_token_num);
1134 }
1135
1136 if (alibi_slopes != nullptr) {
1137 apply_alibi_slopes(logits_buffer, alibi_slopes, kv_tile_token_num,
1138 q_tile_start_pos, kv_tile_start_pos, q_token_num,
1139 kv_tile_token_num, q_heads_per_kv);
1140
1141 // print_logits("alibi raw logits", logits_buffer, q_head_num,
1142 // kv_tile_token_num, kv_tile_token_num);
1143 }
1144
1145 apply_mask(logits_buffer, kv_tile_token_num, q_tile_start_pos,
1146 kv_end_pos, kv_tile_start_pos, kv_tile_end_pos, q_token_num,
1147 q_heads_per_kv, left_window_size, right_window_size);
1148
1149 // if (debug_info){
1150 // print_logits("masked logits", logits_buffer, q_head_num,
1151 // kv_tile_token_num, kv_tile_token_num);
1152 // print_logits("old_max", max_buffer, 1, q_head_num, q_head_num);
1153 // print_logits("old_sum", sum_buffer, 1, q_head_num, q_head_num);
1154 // }
1155
1156 apply_softmax(logits_buffer, partial_q_buffer, max_buffer, sum_buffer,
1157 kv_tile_token_num, q_head_num, kv_tile_token_num,
1158 is_first_iter, use_sink);
1159 }
1160
1161 // compute P@V
1162 {
1163 constexpr bool prequantize_probabilities = []() {
1164 if constexpr (requires { tile_gemm_t::prequantize_probabilities; }) {
1165 return tile_gemm_t::prequantize_probabilities;
1166 }
1167 return false;
1168 }();
1169 int32_t curr_group_offset =
1170 start_block_group_offset * v_cache_token_group_stride;
1171 int32_t curr_group_num_in_block =
1172 token_group_num_per_block - start_block_group_offset;
1173 int32_t remaining_group_num = token_group_num;
1174 int32_t head_dim_group_num = head_dim / headdim_alignment;
1175 using pv_prob_buffer_t = std::conditional_t<prequantize_probabilities,
1176 uint8_t, prob_buffer_t>;
1177 pv_prob_buffer_t* curr_prob_buffer =
1178 reinterpret_cast<pv_prob_buffer_t*>(logits_buffer);
1179 const int64_t prob_buffer_stride =
1180 prequantize_probabilities
1181 ? kv_tile_token_num
1182 : kv_tile_token_num *
1183 (sizeof(logits_buffer_t) / sizeof(prob_buffer_t));
1184 partial_output_buffer_t* curr_partial_q_buffer = partial_q_buffer;
1185 bool accum_c = !is_first_iter;
1186 for (int32_t block_idx = start_block_idx; block_idx < end_block_idx;
1187 ++block_idx) {
1188 int32_t physical_block_idx = block_table[block_idx];
1189 kv_cache_t* v_cache_block_ptr =
1190 v_head_cache_ptr +
1191 physical_block_idx * kv_cache_num_blocks_stride +
1192 curr_group_offset;
1193 curr_group_num_in_block =
1194 std::min(remaining_group_num, curr_group_num_in_block);
1195 int32_t curr_token_num =
1196 curr_group_num_in_block * blocksize_alignment;
1197
1198 for (int32_t head_dim_group_idx = 0;
1199 head_dim_group_idx < head_dim_group_num; ++head_dim_group_idx) {
1200 // output_tile = p_tile @ v_tile, [MaxQHeadNumPerIteration,
1201 // HeadDimAlignment] = [MaxQHeadNumPerIteration, block_size] @
1202 // [block_size, HeadDimAlignment]
1203 tile_gemm_t::template gemm<AttentionGemmPhase::PV, -1>(
1204 q_head_num, curr_prob_buffer, v_cache_block_ptr,
1205 curr_partial_q_buffer, prob_buffer_stride, head_dim, head_dim,
1206 block_size, curr_token_num, accum_c);
1207
1208 // Update
1209 curr_partial_q_buffer += headdim_alignment;
1210 v_cache_block_ptr += v_cache_head_group_stride;
1211 }
1212
1213 // Update
1214 remaining_group_num -= curr_group_num_in_block;
1215 curr_group_offset = 0;
1216 curr_group_num_in_block = token_group_num_per_block;
1217 curr_prob_buffer += curr_token_num * probability_token_stride;
1218 curr_partial_q_buffer = partial_q_buffer;
1219 accum_c = true;
1220 }
1221 }
1222 }
1223
1224 void apply_mask(logits_buffer_t* __restrict__ logits_buffer,
1225 const int64_t logits_buffer_stride,
1226 const int32_t q_tile_start_pos, const int32_t kv_end_pos,
1227 const int32_t kv_tile_start_pos,
1228 const int32_t kv_tile_end_pos, const int32_t q_token_num,
1229 const int32_t q_heads_per_kv,
1230 const int32_t sliding_window_left,
1231 const int32_t sliding_window_right) {
1232 // Apply mask
1233 constexpr logits_buffer_t neg_inf =
1234 -std::numeric_limits<logits_buffer_t>::infinity();
1235 logits_buffer_t* __restrict__ curr_logits_buffer = logits_buffer;
1236 int32_t curr_token_pos = q_tile_start_pos;
1237 for (int32_t token_idx = 0; token_idx < q_token_num; ++token_idx) {
1238 int32_t left_kv_pos = [&]() {
1239 int32_t pos = kv_tile_start_pos;
1240 if (sliding_window_left != -1) {
1241 pos = std::max(pos, curr_token_pos - sliding_window_left);
1242 }
1243 // Clamp to tile end to avoid OOB when window starts past the tile
1244 return std::min(pos, kv_tile_end_pos);
1245 }();
1246
1247 int32_t right_kv_pos = [&]() {
1248 int32_t pos = kv_tile_end_pos;
1249 if (sliding_window_right != -1) {
1250 pos = std::min(pos,
1251 std::max(kv_tile_start_pos,
1252 curr_token_pos + sliding_window_right + 1));
1253 }
1254 return std::min(pos, kv_end_pos);
1255 }();
1256
1257 int32_t left_invalid_token_num = left_kv_pos - kv_tile_start_pos;
1258 int32_t right_invalid_token_num = kv_tile_end_pos - right_kv_pos;
1259 for (int32_t head_idx = 0; head_idx < q_heads_per_kv; ++head_idx) {
1260 logits_buffer_t* __restrict__ curr_logits_buffer_tail =
1261 curr_logits_buffer + right_kv_pos - kv_tile_start_pos;
1262 for (int32_t i = 0; i < left_invalid_token_num; ++i) {
1263 curr_logits_buffer[i] = neg_inf;
1264 }
1265 for (int32_t i = 0; i < right_invalid_token_num; ++i) {
1266 curr_logits_buffer_tail[i] = neg_inf;
1267 }
1268
1269 curr_logits_buffer += logits_buffer_stride;
1270 }
1271
1272 ++curr_token_pos;
1273 }
1274 }
1275
1276 void apply_softmax(logits_buffer_t* __restrict__ logits_buffer,
1277 float* __restrict__ partial_q_buffer,
1278 float* __restrict__ max_buffer,
1279 float* __restrict__ sum_buffer,
1280 const int64_t logits_buffer_stride, int32_t q_head_num,
1281 int32_t kv_tile_token_num, bool is_first_iter,
1282 bool use_sink) {
1283 #ifdef DEFINE_FAST_EXP
1284 DEFINE_FAST_EXP
1285 bool constexpr IsReducedPrecision =
1286 std::is_same_v<query_t, c10::BFloat16> ||
1287 std::is_same_v<query_t, c10::Half>;
1288 #endif
1289
1290 constexpr bool prequantize_probabilities = []() {
1291 if constexpr (requires { tile_gemm_t::prequantize_probabilities; }) {
1292 return tile_gemm_t::prequantize_probabilities;
1293 }
1294 return false;
1295 }();
1296 using softmax_prob_buffer_t =
1297 std::conditional_t<prequantize_probabilities, uint8_t, prob_buffer_t>;
1298 static_assert(sizeof(prob_buffer_t) <= sizeof(logits_buffer_t));
1299
1300 logits_buffer_t* __restrict__ curr_logits_buffer = logits_buffer;
1301 softmax_prob_buffer_t* __restrict__ curr_prob_buffer =
1302 reinterpret_cast<softmax_prob_buffer_t*>(logits_buffer);
1303 float* __restrict__ curr_partial_q_buffer = partial_q_buffer;
1304 const int32_t vec_num = kv_tile_token_num / 16;
1305 const int32_t head_vec_num = head_dim / 16;
1306 for (int32_t i = 0; i < q_head_num; ++i) {
1307 float init_max_val = max_buffer[i];
1308 float init_sum_val = sum_buffer[i];
1309
1310 // apply scale and compute max
1311 vec_op::FP32Vec16 max_vec(init_max_val);
1312 {
1313 logits_buffer_t* __restrict__ curr_logits_buffer_iter =
1314 curr_logits_buffer;
1315 for (int32_t j = 0; j < vec_num; ++j) {
1316 vec_op::FP32Vec16 vec(curr_logits_buffer_iter);
1317 max_vec = vec.max(max_vec);
1318
1319 curr_logits_buffer_iter += 16;
1320 }
1321 }
1322 float new_max_val = max_vec.reduce_max();
1323 float rescale_factor = init_max_val - new_max_val;
1324
1325 // use same rescale threshold with FA4.
1326 // https://github.com/Dao-AILab/flash-attention/blob/1b8e1e641c6a179be9a0538b7f40fd595050b735/flash_attn/cute/flash_fwd_sm100.py#L1271
1327 bool need_rescale = rescale_factor < -8.0;
1328 if (!need_rescale) {
1329 new_max_val = init_max_val;
1330 } else {
1331 max_buffer[i] = new_max_val;
1332 }
1333
1334 // sub max, compute exp and sum
1335 max_vec = vec_op::FP32Vec16(new_max_val);
1336 vec_op::FP32Vec16 sum_vec(0.0);
1337 {
1338 logits_buffer_t* __restrict__ curr_logits_buffer_iter =
1339 curr_logits_buffer;
1340 softmax_prob_buffer_t* __restrict__ curr_prob_buffer_iter =
1341 curr_prob_buffer;
1342 for (int32_t j = 0; j < vec_num; ++j) {
1343 vec_op::FP32Vec16 vec(curr_logits_buffer_iter);
1344 vec = vec - max_vec;
1345
1346 // compute exp
1347
1348 #if defined(DEFINE_FAST_EXP)
1349 #ifdef __aarch64__
1350 if constexpr (IsReducedPrecision) {
1351 vec = fast_exp_f16(vec);
1352 } else
1353 #endif
1354 {
1355 vec = fast_exp(vec);
1356 }
1357
1358 #else
1359 vec.save(curr_logits_buffer_iter);
1360 for (int32_t k = 0; k < 16; ++k) {
1361 curr_logits_buffer_iter[k] = std::exp(curr_logits_buffer_iter[k]);
1362 }
1363 vec = vec_op::FP32Vec16(curr_logits_buffer_iter);
1364 #endif
1365
1366 if constexpr (prequantize_probabilities) {
1367 vec.save(curr_logits_buffer_iter);
1368 } else {
1369 probability_token_store_t::store_probabilities(
1370 curr_prob_buffer_iter, vec, i,
1371 logits_buffer_stride * sizeof(logits_buffer_t) /
1372 sizeof(prob_buffer_t));
1373 }
1374
1375 sum_vec = sum_vec + vec;
1376
1377 curr_logits_buffer_iter += 16;
1378 curr_prob_buffer_iter += 16 * probability_token_stride;
1379 }
1380 if constexpr (prequantize_probabilities) {
1381 tile_gemm_t::quantize_probability_row(
1382 curr_logits_buffer, curr_prob_buffer, kv_tile_token_num);
1383 }
1384 }
1385 float new_sum_val = sum_vec.reduce_sum();
1386
1387 // rescale sum and partial outputs
1388 if (need_rescale) {
1389 // compute rescale factor
1390 rescale_factor = std::exp(rescale_factor);
1391 vec_op::FP32Vec16 rescale_factor_vec(rescale_factor);
1392
1393 // rescale sum
1394 new_sum_val += rescale_factor * init_sum_val;
1395
1396 // rescale output
1397 if (!is_first_iter) {
1398 float* __restrict__ curr_partial_q_buffer_iter =
1399 curr_partial_q_buffer;
1400 for (int32_t j = 0; j < head_vec_num; ++j) {
1401 vec_op::FP32Vec16 vec(curr_partial_q_buffer_iter);
1402 vec = vec * rescale_factor_vec;
1403 vec.save(curr_partial_q_buffer_iter);
1404
1405 curr_partial_q_buffer_iter += 16;
1406 }
1407 }
1408 } else {
1409 new_sum_val += init_sum_val;
1410 }
1411
1412 sum_buffer[i] = new_sum_val;
1413
1414 curr_logits_buffer += logits_buffer_stride;
1415 if constexpr (prequantize_probabilities) {
1416 curr_prob_buffer += kv_tile_token_num;
1417 } else {
1418 curr_prob_buffer =
1419 reinterpret_cast<softmax_prob_buffer_t*>(curr_logits_buffer);
1420 }
1421 curr_partial_q_buffer += head_dim;
1422 }
1423 }
1424
1425 void apply_softcap(logits_buffer_t* __restrict__ logits_buffer,
1426 const int64_t logits_buffer_stride, int32_t q_head_num,
1427 int32_t kv_tile_token_num, float softcap_scale) {
1428 #ifdef DEFINE_FAST_EXP
1429 DEFINE_FAST_EXP
1430 bool constexpr IsReducedPrecision =
1431 std::is_same_v<query_t, c10::BFloat16> ||
1432 std::is_same_v<query_t, c10::Half>;
1433 #endif
1434
1435 float inv_softcap_scale = 1.0 / softcap_scale;
1436 vec_op::FP32Vec16 softcap_scale_vec(softcap_scale);
1437 vec_op::FP32Vec16 inv_softcap_scale_vec(inv_softcap_scale);
1438 vec_op::FP32Vec16 ones_vec(1.0);
1439 logits_buffer_t* __restrict__ curr_logits_buffer = logits_buffer;
1440 const int32_t vec_num = kv_tile_token_num / 16;
1441 for (int32_t i = 0; i < q_head_num; ++i) {
1442 logits_buffer_t* __restrict__ curr_logits_buffer_iter =
1443 curr_logits_buffer;
1444 for (int32_t j = 0; j < vec_num; ++j) {
1445 vec_op::FP32Vec16 vec(curr_logits_buffer_iter);
1446 vec = vec * inv_softcap_scale_vec;
1447
1448 #if defined(DEFINE_FAST_EXP)
1449 #ifdef __aarch64__
1450 if constexpr (IsReducedPrecision) {
1451 vec = fast_exp_f16(vec);
1452 } else
1453 #endif
1454 {
1455 vec = fast_exp(vec);
1456 }
1457 vec_op::FP32Vec16 inv_vec = ones_vec / vec;
1458 vec = (vec - inv_vec) / (vec + inv_vec);
1459 #else
1460 vec.save(curr_logits_buffer_iter);
1461 for (int k = 0; k < 16; ++k) {
1462 curr_logits_buffer_iter[k] = std::tanh(curr_logits_buffer_iter[k]);
1463 }
1464 vec = vec_op::FP32Vec16(curr_logits_buffer_iter);
1465 #endif
1466 vec = vec * softcap_scale_vec;
1467 vec.save(curr_logits_buffer_iter);
1468
1469 curr_logits_buffer_iter += 16;
1470 }
1471
1472 curr_logits_buffer += logits_buffer_stride;
1473 }
1474 }
1475
1476 void apply_alibi_slopes(logits_buffer_t* __restrict__ logits_buffer,
1477 const float* __restrict__ alibi_slopes,
1478 const int64_t logits_buffer_stride,
1479 const int32_t q_tile_start_pos,
1480 const int32_t kv_tile_start_pos,
1481 const int32_t q_token_num,
1482 const int32_t kv_tile_token_num,
1483 const int32_t q_heads_per_kv) {
1484 alignas(64) constexpr float initial_arange_vals[16] = {
1485 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f,
1486 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f};
1487 const int32_t vec_num = kv_tile_token_num / 16;
1488
1489 vec_op::FP32Vec16 initial_arange_vals_vec(initial_arange_vals);
1490 initial_arange_vals_vec =
1491 initial_arange_vals_vec + vec_op::FP32Vec16((float)kv_tile_start_pos);
1492 vec_op::FP32Vec16 pos_offset_vec(16.0);
1493 logits_buffer_t* __restrict__ curr_logits_buffer = logits_buffer;
1494 for (int32_t i = 0; i < q_token_num; ++i) {
1495 vec_op::FP32Vec16 curr_q_pos_vec((float)(i + q_tile_start_pos));
1496 for (int32_t j = 0; j < q_heads_per_kv; ++j) {
1497 vec_op::FP32Vec16 alibi_scale_vec(alibi_slopes[j]);
1498 vec_op::FP32Vec16 curr_kv_pos_vec(initial_arange_vals_vec);
1499 logits_buffer_t* __restrict__ curr_logits_buffer_iter =
1500 curr_logits_buffer;
1501 for (int32_t k = 0; k < vec_num; ++k) {
1502 vec_op::FP32Vec16 alibi_bias_vec =
1503 alibi_scale_vec * (curr_kv_pos_vec - curr_q_pos_vec);
1504 vec_op::FP32Vec16 vec(curr_logits_buffer_iter);
1505 vec = vec + alibi_bias_vec;
1506
1507 vec.save(curr_logits_buffer_iter);
1508
1509 curr_kv_pos_vec = curr_kv_pos_vec + pos_offset_vec;
1510 curr_logits_buffer_iter += 16;
1511 }
1512 curr_logits_buffer += logits_buffer_stride;
1513 }
1514 }
1515 }
1516 };
1517
1518 public:
1519 void operator()(const AttentionInput* input) {
1520 const int thread_num = cpu_utils::get_max_threads();
1521 TORCH_CHECK_EQ(input->metadata->thread_num, thread_num);
1522 std::atomic<int32_t> guard_counter(0);
1523 std::atomic<int32_t>* guard_counter_ptr = &guard_counter;
1524
1525 #pragma omp parallel for schedule(static, 1)
1526 for (int thread_id = 0; thread_id < thread_num; ++thread_id) {
1527 AttentionMetadata& metadata = *input->metadata;
1528 if (metadata.workitem_group_num == 0) {
1529 continue;
1530 }
1531
1532 attention_impl_t attn_impl;
1533 constexpr bool fp8_kv = std::is_same_v<kv_cache_t, c10::Float8_e4m3fn> ||
1534 std::is_same_v<kv_cache_t, c10::Float8_e5m2>;
1535 float output_v_scale = 1.0f;
1536 if constexpr (fp8_kv) {
1537 attn_impl.init_from_input(input);
1538 output_v_scale = attn_impl.get_output_v_scale();
1539 }
1540
1541 // general information
1542 const int32_t q_head_num = input->num_heads;
1543 const int32_t kv_head_num = input->num_kv_heads;
1544 const int32_t q_heads_per_kv = q_head_num / kv_head_num;
1545 AttentionWorkItemGroup* const workitem_groups =
1546 metadata.workitem_groups_ptr;
1547 const int32_t* cu_workitem_num_per_thread =
1548 metadata.cu_workitem_num_per_thread;
1549 ReductionWorkItemGroup* const reduction_items =
1550 metadata.reduction_items_ptr;
1551 const bool supports_gqa = q_heads_per_kv <= max_q_head_num_per_iter;
1552 bool decode_only_batch = true;
1553 for (int32_t i = 0; i < metadata.workitem_group_num; ++i) {
1554 decode_only_batch =
1555 decode_only_batch && (workitem_groups[i].q_token_num == 1);
1556 }
1557 const bool use_gqa_fast_path = supports_gqa && decode_only_batch;
1558 const int32_t actual_kv_head_num =
1559 use_gqa_fast_path ? kv_head_num : q_head_num;
1560 const int32_t actual_q_heads_per_kv =
1561 use_gqa_fast_path ? q_heads_per_kv : 1;
1562 TORCH_CHECK_LE(actual_q_heads_per_kv, max_q_head_num_per_iter);
1563 const int64_t q_token_num_stride = input->query_num_tokens_stride;
1564 const int64_t q_head_num_stride = input->query_num_heads_stride;
1565 const int64_t kv_cache_head_num_stride = input->cache_num_kv_heads_stride;
1566 const int64_t kv_cache_block_num_stride = input->cache_num_blocks_stride;
1567 const int32_t sliding_window_size = input->sliding_window_size;
1568 const int32_t block_size = input->block_size;
1569 const float scale = input->scale;
1570 const float softcap_scale = input->softcap;
1571 const float* alibi_slopes = input->alibi_slopes;
1572 const void* s_aux = input->s_aux;
1573 const bool s_aux_is_bf16 = input->s_aux_is_bf16;
1574 const bool* dynamic_causal = input->dynamic_causal;
1575 const bool is_dynamic_causal = dynamic_causal != nullptr;
1576
1577 const bool causal = input->causal;
1578 int32_t* const block_table = input->block_table;
1579 const int64_t block_table_stride = input->blt_num_tokens_stride;
1580
1581 // init buffers
1582 void* scratchpad_ptr =
1583 cpu_utils::ScratchPadManager::get_scratchpad_manager()
1584 ->get_data<void>();
1585 AttentionScratchPad buffer_manager(thread_id, metadata, scratchpad_ptr);
1586
1587 const int32_t total_reduction_split_num = metadata.reduction_split_num;
1588 if (metadata.reduction_split_num > 0) {
1589 // reset split flag
1590 for (int32_t head_idx = thread_id; head_idx < actual_kv_head_num;
1591 head_idx += thread_num) {
1592 buffer_manager.update(head_idx, total_reduction_split_num, head_dim,
1593 0, sizeof(partial_output_buffer_t));
1594 volatile bool* __restrict__ curr_flag_ptr =
1595 buffer_manager.get_reduce_flag_buffer();
1596 for (int32_t split_idx = 0; split_idx < total_reduction_split_num;
1597 ++split_idx) {
1598 curr_flag_ptr[split_idx] = false;
1599 }
1600 }
1601 }
1602
1603 const int64_t available_cache_size = cpu_utils::get_available_l2_size();
1604 const int32_t default_tile_size =
1605 AttentionScheduler::calcu_default_tile_size(
1606 available_cache_size, head_dim, sizeof(kv_cache_t),
1607 sizeof(q_buffer_t), sizeof(logits_buffer_t),
1608 sizeof(partial_output_buffer_t), max_q_head_num_per_iter,
1609 max_q_head_num_per_iter);
1610
1611 const int32_t effective_thread_num = metadata.effective_thread_num;
1612 const int32_t reduction_item_num = metadata.reduction_item_num;
1613 const int32_t split_kv_q_token_num_threshold =
1614 metadata.split_kv_q_token_num_threshold;
1615 const int32_t workitem_groups_counter_num =
1616 actual_kv_head_num * effective_thread_num;
1617 const int32_t reduction_items_counter_num =
1618 actual_kv_head_num * reduction_item_num;
1619 const int32_t total_counter_num =
1620 workitem_groups_counter_num + reduction_items_counter_num;
1621
1622 if (metadata.reduction_split_num > 0) {
1623 ++(*guard_counter_ptr);
1624 while (guard_counter_ptr->load() != thread_num) {
1625 #ifdef FAST_SPINNING
1626 FAST_SPINNING
1627 #else
1628 std::this_thread::yield();
1629 #endif
1630 }
1631 }
1632
1633 // main loop
1634 for (;;) {
1635 int64_t task_idx = metadata.acquire_counter();
1636
1637 if (task_idx >= total_counter_num) {
1638 // no more tasks, leave loop
1639 break;
1640 }
1641
1642 if (task_idx < workitem_groups_counter_num) {
1643 // attention task
1644 // map task_idx to workitem_groups
1645 const int32_t kv_head_idx = task_idx / effective_thread_num;
1646 const int32_t thread_offset = task_idx % effective_thread_num;
1647 AttentionWorkItemGroup* const curr_workitem_groups =
1648 workitem_groups + cu_workitem_num_per_thread[thread_offset];
1649 const int32_t curr_workitem_groups_num =
1650 cu_workitem_num_per_thread[thread_offset + 1] -
1651 cu_workitem_num_per_thread[thread_offset];
1652
1653 for (int32_t workitem_group_idx = 0;
1654 workitem_group_idx < curr_workitem_groups_num;
1655 ++workitem_group_idx) {
1656 AttentionWorkItemGroup* const current_workitem_group =
1657 &curr_workitem_groups[workitem_group_idx];
1658
1659 const int32_t current_group_idx = current_workitem_group->req_id;
1660 const int32_t current_group_causal =
1661 is_dynamic_causal ? dynamic_causal[current_group_idx] : causal;
1662 auto [sliding_window_left, sliding_window_right] =
1663 AttentionScheduler::calcu_sliding_window_size(
1664 sliding_window_size, current_group_causal);
1665 const int32_t kv_start_pos =
1666 current_workitem_group->kv_split_pos_start;
1667 const int32_t kv_end_pos = current_workitem_group->kv_split_pos_end;
1668 const int32_t curr_spilt_id = current_workitem_group->split_id;
1669 const int32_t q_token_id_start =
1670 current_workitem_group->q_token_id_start;
1671 const int32_t q_token_num = current_workitem_group->q_token_num;
1672 const bool curr_use_gqa =
1673 use_gqa_fast_path || (supports_gqa && q_token_num == 1);
1674 if (!use_gqa_fast_path && curr_use_gqa &&
1675 kv_head_idx % q_heads_per_kv != 0) {
1676 continue;
1677 }
1678 const int32_t curr_q_heads_per_kv =
1679 curr_use_gqa ? q_heads_per_kv : 1;
1680 const int32_t curr_max_q_token_num_per_iter =
1681 max_q_head_num_per_iter / curr_q_heads_per_kv;
1682 const int32_t curr_default_q_tile_token_num =
1683 default_tile_size / curr_q_heads_per_kv;
1684 const int32_t q_head_start_idx =
1685 use_gqa_fast_path ? (kv_head_idx * q_heads_per_kv)
1686 : kv_head_idx;
1687
1688 // taskgroup general information
1689 const int32_t q_end = input->query_start_loc[current_group_idx + 1];
1690 const int32_t q_start = input->query_start_loc[current_group_idx];
1691 const int32_t seq_len = input->seq_lens[current_group_idx];
1692 const int32_t q_start_pos = seq_len - (q_end - q_start);
1693 const int32_t block_num = (seq_len + block_size - 1) / block_size;
1694 // Only apply sink for the first KV split
1695 bool use_sink = (s_aux != nullptr &&
1696 current_workitem_group->local_split_id == 0);
1697
1698 for (int32_t q_token_offset = 0; q_token_offset < q_token_num;
1699 q_token_offset += curr_default_q_tile_token_num) {
1700 bool first_iter_flag[AttentionScheduler::MaxQTileIterNum];
1701 for (int32_t i = 0; i < AttentionScheduler::MaxQTileIterNum;
1702 ++i) {
1703 first_iter_flag[i] = true;
1704 }
1705
1706 const int32_t q_token_start_idx =
1707 q_start + q_token_offset + q_token_id_start;
1708 const int32_t actual_q_token_num = std::min(
1709 curr_default_q_tile_token_num, q_token_num - q_token_offset);
1710 const int32_t q_head_tile_size =
1711 actual_q_token_num * curr_q_heads_per_kv;
1712 const int32_t rounded_q_head_tile_size =
1713 ((q_head_tile_size + max_q_head_num_per_iter - 1) /
1714 max_q_head_num_per_iter) *
1715 max_q_head_num_per_iter;
1716 const int32_t kv_tile_size =
1717 AttentionScheduler::calcu_tile_size_with_constant_q(
1718 available_cache_size, head_dim, sizeof(kv_cache_t),
1719 sizeof(q_buffer_t), sizeof(logits_buffer_t),
1720 sizeof(partial_output_buffer_t), max_q_head_num_per_iter,
1721 blocksize_alignment, rounded_q_head_tile_size,
1722 rounded_q_head_tile_size <= max_q_head_num_per_iter);
1723
1724 // update buffers
1725 buffer_manager.update(
1726 head_dim, sizeof(q_buffer_t), sizeof(logits_buffer_t),
1727 sizeof(partial_output_buffer_t), max_q_head_num_per_iter,
1728 rounded_q_head_tile_size, kv_tile_size);
1729 q_buffer_t* q_buffer = buffer_manager.get_q_buffer<q_buffer_t>();
1730 float* logits_buffer = buffer_manager.get_logits_buffer();
1731 float* partial_q_buffer = buffer_manager.get_output_buffer();
1732 float* max_buffer = buffer_manager.get_max_buffer();
1733 float* sum_buffer = buffer_manager.get_sum_buffer();
1734
1735 const int32_t q_tile_start_pos =
1736 q_start_pos + q_token_offset + q_token_id_start;
1737 const int32_t q_tile_end_pos =
1738 q_tile_start_pos + actual_q_token_num;
1739 const auto [kv_tile_start_pos, kv_tile_end_pos] =
1740 AttentionScheduler::calcu_kv_tile_pos(
1741 kv_start_pos, kv_end_pos, q_tile_start_pos,
1742 q_tile_end_pos, sliding_window_size,
1743 current_group_causal);
1744 const auto [rounded_kv_tile_start_pos, rounded_kv_tile_end_pos] =
1745 AttentionScheduler::align_kv_tile_pos(
1746 kv_tile_start_pos, kv_tile_end_pos, blocksize_alignment);
1747
1748 const int32_t curr_kv_head_idx =
1749 use_gqa_fast_path ? kv_head_idx
1750 : (kv_head_idx / q_heads_per_kv);
1751
1752 // std::printf("thread_id: %d, req_id: %d, q_token_start: %d,
1753 // q_token_end: %d, q_head_start: %d, q_head_end: %d, kv_head_idx:
1754 // %d, kv_pos_start: %d, kv_pos_end: %d\n",
1755 // thread_id, current_group_idx,
1756 // q_token_start_idx, q_token_start_idx +
1757 // actual_q_token_num, q_head_start_idx,
1758 // q_head_start_idx + actual_q_heads_per_kv,
1759 // curr_kv_head_idx, kv_tile_start_pos,
1760 // kv_tile_end_pos);
1761
1762 // move buffers
1763 kv_cache_t* curr_k_cache =
1764 reinterpret_cast<kv_cache_t*>(input->key_cache) +
1765 curr_kv_head_idx * kv_cache_head_num_stride;
1766 kv_cache_t* curr_v_cache =
1767 reinterpret_cast<kv_cache_t*>(input->value_cache) +
1768 curr_kv_head_idx * kv_cache_head_num_stride;
1769 query_t* const q_tile_ptr =
1770 reinterpret_cast<query_t*>(input->query) +
1771 q_token_start_idx * q_token_num_stride +
1772 q_head_start_idx * q_head_num_stride;
1773 size_t output_buffer_offset =
1774 q_token_start_idx * q_head_num * head_dim +
1775 q_head_start_idx * head_dim;
1776 int32_t* curr_block_table =
1777 block_table + current_group_idx * block_table_stride;
1778 const float* curr_alibi_slopes =
1779 (alibi_slopes != nullptr ? alibi_slopes + q_head_start_idx
1780 : nullptr);
1781 // copy the Q tile to q_buffer, the logical layout of q_buffer is
1782 // [actual_q_token_num, curr_q_heads_per_kv, head_dim]
1783 {
1784 attn_impl.copy_q_heads_tile(
1785 q_tile_ptr, q_buffer, actual_q_token_num,
1786 curr_q_heads_per_kv, q_token_num_stride, q_head_num_stride,
1787 scale);
1788 }
1789
1790 if (use_sink) {
1791 alignas(64) float s_aux_fp32[16];
1792 // Sinks may be provided as bf16 or fp32. Either way they are
1793 // loaded into fp32: bf16 sinks are up-converted (native
1794 // BF16Vec16 path, available on all platforms), while fp32
1795 // sinks are loaded directly and executed in full precision.
1796 if (s_aux_is_bf16) {
1797 const c10::BFloat16* curr_s_aux =
1798 static_cast<const c10::BFloat16*>(s_aux) +
1799 q_head_start_idx;
1800 vec_op::BF16Vec16 vec_bf16(curr_s_aux);
1801 vec_op::FP32Vec16 vec_fp32(vec_bf16);
1802 vec_fp32.save(s_aux_fp32);
1803 } else {
1804 const float* curr_s_aux =
1805 static_cast<const float*>(s_aux) + q_head_start_idx;
1806 vec_op::FP32Vec16 vec_fp32(curr_s_aux);
1807 vec_fp32.save(s_aux_fp32);
1808 }
1809
1810 float* __restrict__ curr_sum_buffer = sum_buffer;
1811 float* __restrict__ curr_max_buffer = max_buffer;
1812 for (int32_t token_idx = 0; token_idx < actual_q_token_num;
1813 ++token_idx) {
1814 for (int32_t head_idx = 0; head_idx < curr_q_heads_per_kv;
1815 ++head_idx) {
1816 curr_sum_buffer[head_idx] = 1.0f;
1817 curr_max_buffer[head_idx] = s_aux_fp32[head_idx];
1818 }
1819
1820 curr_sum_buffer += curr_q_heads_per_kv;
1821 curr_max_buffer += curr_q_heads_per_kv;
1822 }
1823 } else {
1824 float* __restrict__ curr_sum_buffer = sum_buffer;
1825 float* __restrict__ curr_max_buffer = max_buffer;
1826 for (int32_t token_idx = 0; token_idx < actual_q_token_num;
1827 ++token_idx) {
1828 for (int32_t head_idx = 0; head_idx < curr_q_heads_per_kv;
1829 ++head_idx) {
1830 curr_sum_buffer[head_idx] = 0.0f;
1831 curr_max_buffer[head_idx] =
1832 std::numeric_limits<float>::lowest();
1833 }
1834
1835 curr_sum_buffer += curr_q_heads_per_kv;
1836 curr_max_buffer += curr_q_heads_per_kv;
1837 }
1838 }
1839
1840 // compute loop
1841 for (int32_t kv_tile_pos = rounded_kv_tile_start_pos;
1842 kv_tile_pos < rounded_kv_tile_end_pos;
1843 kv_tile_pos += kv_tile_size) {
1844 const int32_t kv_tile_pos_left = kv_tile_pos;
1845 const int32_t kv_tile_pos_right = std::min(
1846 kv_tile_pos_left + kv_tile_size, rounded_kv_tile_end_pos);
1847 for (int32_t q_head_tile_token_offset = 0;
1848 q_head_tile_token_offset < actual_q_token_num;
1849 q_head_tile_token_offset +=
1850 curr_max_q_token_num_per_iter) {
1851 const int32_t q_tile_pos_left =
1852 q_tile_start_pos + q_head_tile_token_offset;
1853 const int32_t q_tile_token_num =
1854 std::min(curr_max_q_token_num_per_iter,
1855 actual_q_token_num - q_head_tile_token_offset);
1856 const int32_t q_tile_head_offset =
1857 q_head_tile_token_offset * curr_q_heads_per_kv;
1858 const int32_t q_tile_head_num =
1859 q_tile_token_num * curr_q_heads_per_kv;
1860 const int32_t q_tile_pos_right =
1861 q_tile_pos_left + q_tile_token_num;
1862 const auto [actual_kv_tile_pos_left,
1863 actual_kv_tile_pos_right] =
1864 AttentionScheduler::calcu_kv_tile_pos(
1865 kv_tile_pos_left, kv_tile_pos_right, q_tile_pos_left,
1866 q_tile_pos_right, sliding_window_size,
1867 current_group_causal);
1868 const int32_t q_iter_idx =
1869 q_head_tile_token_offset / curr_max_q_token_num_per_iter;
1870
1871 if (actual_kv_tile_pos_right <= actual_kv_tile_pos_left) {
1872 continue;
1873 }
1874
1875 // align kv_pos to blocksize_alignment
1876 const auto [aligned_actual_kv_tile_pos_left,
1877 aligned_actual_kv_tile_pos_right] =
1878 AttentionScheduler::align_kv_tile_pos(
1879 actual_kv_tile_pos_left, actual_kv_tile_pos_right,
1880 blocksize_alignment);
1881 const int32_t actual_kv_token_num =
1882 aligned_actual_kv_tile_pos_right -
1883 aligned_actual_kv_tile_pos_left;
1884
1885 // std::printf("\tq_iter_idx: %d, q_token_start: %d,
1886 // q_token_end: %d, q_token_num: %d, q_head_num: %d,
1887 // q_pos_start: %d, q_pos_end: %d, kv_pos_start: %d,
1888 // kv_pos_end: %d\n",
1889 // q_iter_idx, q_token_start_idx +
1890 // q_head_tile_token_offset, q_token_start_idx +
1891 // q_head_tile_token_offset + q_tile_token_num,
1892 // q_tile_token_num, q_tile_head_num,
1893 // q_tile_pos_left, q_tile_pos_right,
1894 // aligned_actual_kv_tile_pos_left,
1895 // aligned_actual_kv_tile_pos_right);
1896
1897 // Move buffers
1898 q_buffer_t* curr_q_heads_buffer =
1899 q_buffer + q_tile_head_offset * head_dim;
1900 float* curr_partial_q_buffer =
1901 partial_q_buffer + q_tile_head_offset * head_dim;
1902 float* curr_max_buffer = max_buffer + q_tile_head_offset;
1903 float* curr_sum_buffer = sum_buffer + q_tile_head_offset;
1904
1905 bool debug_info = false;
1906 // if (debug_info) {
1907 // std::printf("\tq_iter_idx: %d, q_token_start: %d,"
1908 // "q_token_end: %d, q_token_num: %d, q_head_num: %d,"
1909 // "q_pos_start: %d, q_pos_end: %d, kv_pos_start: %d,"
1910 // "kv_pos_end: %d\n",
1911 // q_iter_idx, q_token_start_idx +
1912 // q_head_tile_token_offset, q_token_start_idx
1913 // + q_head_tile_token_offset +
1914 // q_tile_token_num, q_tile_token_num,
1915 // q_tile_head_num, q_tile_pos_left,
1916 // q_tile_pos_right,
1917 // aligned_actual_kv_tile_pos_left,
1918 // aligned_actual_kv_tile_pos_right);
1919 // }
1920
1921 attn_impl.template execute_attention<Attention>(
1922 curr_q_heads_buffer, curr_k_cache, curr_v_cache,
1923 logits_buffer, curr_partial_q_buffer, curr_max_buffer,
1924 curr_sum_buffer, curr_block_table, kv_end_pos,
1925 aligned_actual_kv_tile_pos_left,
1926 aligned_actual_kv_tile_pos_right, actual_kv_token_num,
1927 kv_cache_block_num_stride, q_tile_head_num,
1928 q_tile_token_num, q_tile_pos_left, curr_q_heads_per_kv,
1929 block_size, sliding_window_left, sliding_window_right,
1930 scale, softcap_scale, curr_alibi_slopes,
1931 first_iter_flag[q_iter_idx], use_sink, debug_info);
1932 first_iter_flag[q_iter_idx] = false;
1933 }
1934 }
1935
1936 // write back partial results to output buffer or reduction buffer
1937 {
1938 if (curr_spilt_id == -1) {
1939 final_output(partial_q_buffer,
1940 reinterpret_cast<query_t*>(input->output) +
1941 output_buffer_offset,
1942 sum_buffer, curr_q_heads_per_kv,
1943 actual_q_token_num, q_head_num, output_v_scale);
1944 } else {
1945 const int32_t stride =
1946 curr_q_heads_per_kv * split_kv_q_token_num_threshold;
1947 buffer_manager.update(kv_head_idx, total_reduction_split_num,
1948 head_dim, stride, sizeof(float));
1949 volatile bool* split_flag_buffer =
1950 buffer_manager.get_reduce_flag_buffer() + curr_spilt_id;
1951 float* split_output_buffer =
1952 buffer_manager.get_reduce_output_buffer() +
1953 curr_spilt_id * stride * head_dim;
1954 float* split_max_buffer =
1955 buffer_manager.get_reduce_max_buffer() +
1956 curr_spilt_id * stride;
1957 float* split_sum_buffer =
1958 buffer_manager.get_reduce_sum_buffer() +
1959 curr_spilt_id * stride;
1960
1961 partial_output(partial_q_buffer, max_buffer, sum_buffer,
1962 q_head_tile_size, split_output_buffer,
1963 split_max_buffer, split_sum_buffer,
1964 split_flag_buffer);
1965 }
1966 }
1967 }
1968 }
1969 } else {
1970 task_idx -= workitem_groups_counter_num;
1971 const int32_t kv_head_idx = task_idx / reduction_item_num;
1972 const int32_t item_offset = task_idx % reduction_item_num;
1973 ReductionWorkItemGroup* const curr_workitem_groups =
1974 reduction_items + item_offset;
1975 const int32_t curr_output_token_idx =
1976 curr_workitem_groups->q_token_id_start;
1977 const int32_t curr_output_token_num =
1978 curr_workitem_groups->q_token_id_num;
1979 const int32_t curr_split_id = curr_workitem_groups->split_start_id;
1980 const int32_t curr_split_num = curr_workitem_groups->split_num;
1981 const int32_t current_group_idx = curr_workitem_groups->req_id;
1982 const bool curr_use_gqa =
1983 use_gqa_fast_path || (supports_gqa && curr_output_token_num == 1);
1984 if (!use_gqa_fast_path && curr_use_gqa &&
1985 kv_head_idx % q_heads_per_kv != 0) {
1986 continue;
1987 }
1988 const int32_t curr_q_heads_per_kv = curr_use_gqa ? q_heads_per_kv : 1;
1989 const int32_t curr_output_head_num =
1990 curr_output_token_num * curr_q_heads_per_kv;
1991
1992 const int32_t q_start = input->query_start_loc[current_group_idx];
1993 const int32_t q_token_start_idx = q_start + curr_output_token_idx;
1994 const int32_t q_head_start_idx =
1995 use_gqa_fast_path ? (kv_head_idx * q_heads_per_kv) : kv_head_idx;
1996 size_t output_buffer_offset =
1997 q_token_start_idx * q_head_num * head_dim +
1998 q_head_start_idx * head_dim;
1999
2000 const int32_t stride =
2001 curr_q_heads_per_kv * split_kv_q_token_num_threshold;
2002 buffer_manager.update(kv_head_idx, total_reduction_split_num,
2003 head_dim, stride, sizeof(float));
2004 volatile bool* split_flag_buffer =
2005 buffer_manager.get_reduce_flag_buffer() + curr_split_id;
2006 float* split_output_buffer =
2007 buffer_manager.get_reduce_output_buffer() +
2008 curr_split_id * stride * head_dim;
2009 float* split_max_buffer =
2010 buffer_manager.get_reduce_max_buffer() + curr_split_id * stride;
2011 float* split_sum_buffer =
2012 buffer_manager.get_reduce_sum_buffer() + curr_split_id * stride;
2013
2014 reduce_splits(split_output_buffer, split_max_buffer, split_sum_buffer,
2015 split_flag_buffer, stride, curr_output_head_num,
2016 curr_split_num);
2017 final_output(
2018 split_output_buffer,
2019 reinterpret_cast<query_t*>(input->output) + output_buffer_offset,
2020 split_sum_buffer, curr_q_heads_per_kv, curr_output_token_num,
2021 q_head_num, output_v_scale);
2022 }
2023 }
2024 }
2025 // Reset counter for next call
2026 input->metadata->reset_counter();
2027 }
2028
2029 void reduce_splits(float* __restrict__ split_output_buffer,
2030 float* __restrict__ split_max_buffer,
2031 float* __restrict__ split_sum_buffer,
2032 volatile bool* __restrict__ flags,
2033 const int32_t head_num_per_split,
2034 const int32_t curr_head_num, const int32_t split_num) {
2035 #ifdef DEFINE_FAST_EXP
2036 DEFINE_FAST_EXP
2037 #endif
2038 // restrict curr_head_num <= 16 in the scheduler
2039 // elems in split_max_buffer, split_sum_buffer are not cache alignment, use
2040 // local buffers to reduce false-sharing
2041 alignas(64) float local_max[16];
2042 alignas(64) float local_sum[16];
2043
2044 float* __restrict__ curr_split_output_buffer = split_output_buffer;
2045 float* __restrict__ curr_split_max_buffer = split_max_buffer;
2046 float* __restrict__ curr_split_sum_buffer = split_sum_buffer;
2047 constexpr int32_t head_dim_group_num = head_dim / 16;
2048 for (int32_t split_idx = 0; split_idx < split_num; ++split_idx) {
2049 while (!flags[split_idx]) {
2050 #ifdef FAST_SPINNING
2051 FAST_SPINNING
2052 #else
2053 std::this_thread::yield();
2054 #endif
2055 }
2056 std::atomic_thread_fence(std::memory_order_acquire);
2057
2058 if (split_idx > 0) {
2059 float* __restrict__ curr_output_buffer = split_output_buffer;
2060 float* __restrict__ curr_split_output_buffer_iter =
2061 curr_split_output_buffer;
2062 for (int32_t head_idx = 0; head_idx < curr_head_num; ++head_idx) {
2063 float final_max = local_max[head_idx];
2064 float curr_max = curr_split_max_buffer[head_idx];
2065 float final_sum = local_sum[head_idx];
2066 float curr_sum = curr_split_sum_buffer[head_idx];
2067 float* __restrict__ non_scale_output_iter =
2068 final_max > curr_max ? curr_output_buffer
2069 : curr_split_output_buffer_iter;
2070 float* __restrict__ scale_output_iter =
2071 final_max > curr_max ? curr_split_output_buffer_iter
2072 : curr_output_buffer;
2073 float rescale_factor = final_max > curr_max ? curr_max - final_max
2074 : final_max - curr_max;
2075 rescale_factor = std::exp(rescale_factor);
2076 vec_op::FP32Vec16 rescale_factor_vec(rescale_factor);
2077
2078 local_sum[head_idx] = final_max > curr_max
2079 ? final_sum + rescale_factor * curr_sum
2080 : rescale_factor * final_sum + curr_sum;
2081
2082 final_max = std::max(final_max, curr_max);
2083 local_max[head_idx] = final_max;
2084 for (int32_t i = 0; i < head_dim_group_num; ++i) {
2085 vec_op::FP32Vec16 non_scale_vec(non_scale_output_iter);
2086 vec_op::FP32Vec16 scale_vec(scale_output_iter);
2087 vec_op::FP32Vec16 final_vec =
2088 non_scale_vec + scale_vec * rescale_factor_vec;
2089 final_vec.save(curr_output_buffer);
2090
2091 non_scale_output_iter += 16;
2092 scale_output_iter += 16;
2093 curr_output_buffer += 16;
2094 }
2095 curr_split_output_buffer_iter += head_dim;
2096 }
2097 } else {
2098 vec_op::FP32Vec16 final_max(split_max_buffer);
2099 final_max.save(local_max);
2100 vec_op::FP32Vec16 final_sum(split_sum_buffer);
2101 final_sum.save(local_sum);
2102 }
2103
2104 curr_split_output_buffer += head_num_per_split * head_dim;
2105 curr_split_max_buffer += head_num_per_split;
2106 curr_split_sum_buffer += head_num_per_split;
2107 }
2108 // write back final max and sum
2109 for (int32_t i = 0; i < curr_head_num; ++i) {
2110 split_max_buffer[i] = local_max[i];
2111 split_sum_buffer[i] = local_sum[i];
2112 }
2113 }
2114
2115 void partial_output(float* __restrict__ partial_output_buffer,
2116 float* __restrict__ partial_max_buffer,
2117 float* __restrict__ partial_sum_buffer,
2118 int32_t curr_head_num,
2119 float* __restrict__ split_output_buffer,
2120 float* __restrict__ split_max_buffer,
2121 float* __restrict__ split_sum_buffer,
2122 volatile bool* __restrict__ flag) {
2123 float* __restrict__ curr_partial_output_buffer = partial_output_buffer;
2124 float* __restrict__ curr_split_output_buffer = split_output_buffer;
2125 constexpr int32_t head_dim_group_num = head_dim / 16;
2126 for (int32_t i = 0; i < curr_head_num; ++i) {
2127 split_max_buffer[i] = partial_max_buffer[i];
2128 split_sum_buffer[i] = partial_sum_buffer[i];
2129 for (int32_t j = 0; j < head_dim_group_num; ++j) {
2130 vec_op::FP32Vec16 vec(curr_partial_output_buffer);
2131 vec.save(curr_split_output_buffer);
2132
2133 curr_partial_output_buffer += 16;
2134 curr_split_output_buffer += 16;
2135 }
2136 }
2137 std::atomic_thread_fence(std::memory_order_release);
2138 *flag = true;
2139 }
2140
2141 void final_output(float* __restrict__ partial_q_buffer,
2142 query_t* __restrict__ curr_output_buffer,
2143 float* __restrict__ sum_buffer,
2144 const int32_t q_heads_per_kv,
2145 const int32_t actual_q_token_num, const int32_t q_head_num,
2146 const float v_scale = 1.0f) {
2147 // final output
2148 using output_vec_t = typename VecTypeTrait<query_t>::vec_t;
2149
2150 float* __restrict__ curr_partial_output_buffer = partial_q_buffer;
2151 float* __restrict__ curr_sum_buffer = sum_buffer;
2152 constexpr int32_t group_num_per_head = head_dim / 16;
2153 const int32_t partial_q_buffer_stride = q_heads_per_kv * head_dim;
2154 const int32_t output_buffer_stride = q_head_num * head_dim;
2155 for (int32_t token_idx = 0; token_idx < actual_q_token_num; ++token_idx) {
2156 float* __restrict__ curr_partial_output_buffer_iter =
2157 curr_partial_output_buffer;
2158 query_t* __restrict__ curr_output_buffer_iter = curr_output_buffer;
2159 for (int32_t head_idx = 0; head_idx < q_heads_per_kv; ++head_idx) {
2160 vec_op::FP32Vec16 inv_sum_scale_vec(v_scale / *curr_sum_buffer);
2161
2162 for (int32_t i = 0; i < group_num_per_head; ++i) {
2163 vec_op::FP32Vec16 vec(curr_partial_output_buffer_iter);
2164 // divide the final sum val of softmax here
2165 vec = inv_sum_scale_vec * vec;
2166
2167 // cast to query type
2168 output_vec_t output_vec(vec);
2169 output_vec.save(curr_output_buffer_iter);
2170
2171 // update
2172 curr_partial_output_buffer_iter += 16;
2173 curr_output_buffer_iter += 16;
2174 }
2175
2176 // update
2177 curr_sum_buffer += 1;
2178 }
2179
2180 // update
2181 curr_partial_output_buffer += partial_q_buffer_stride;
2182 curr_output_buffer += output_buffer_stride;
2183 }
2184 }
2185 };
2186
2187 } // namespace cpu_attention
2188
2189 #endif