A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 15, 11:00 UTC
Implements dual-chunk-flash-attn backend for dual chunk attention with sparse attention support (#11844)
Tao He committed
May 13, 2025 at 10:52 UTC
60f76243344d2d3deca5e5ecdade547acc7fed50
17 files changed
+2444
-32
CMakeLists.txt
+1
@@ -230,6 +230,7 @@ set(VLLM_EXT_SRC
230
"csrc/attention/paged_attention_v1.cu"
231
"csrc/attention/paged_attention_v2.cu"
232
"csrc/attention/merge_attn_states.cu"
233
+ "csrc/attention/vertical_slash_index.cu"
234
"csrc/pos_encoding_kernels.cu"
235
"csrc/activation_kernels.cu"
236
"csrc/layernorm_kernels.cu"
csrc/attention/vertical_slash_index.cu
new
+401
@@ -0,0 +1,401 @@
1
+// Copyright (c) Microsoft Corporation.
2
+// Licensed under the MIT license.
3
+
4
+#include <assert.h>
5
+
6
+#include <cuda.h>
7
+
8
+#include <torch/all.h>
9
+
10
+__device__ int64_t save_blocks(int* block_offset, int64_t range_start,
11
+ int64_t range_end, int64_t block_size,
12
+ int64_t input_block_count, int64_t kv_seqlen) {
13
+ if (range_start >= kv_seqlen) {
14
+ return input_block_count;
15
+ }
16
+ if (range_end > kv_seqlen) {
17
+ range_end = kv_seqlen;
18
+ }
19
+ int64_t current_block_count = input_block_count;
20
+ for (int idx = range_start; idx < range_end; idx += block_size) {
21
+ block_offset[current_block_count++] = idx;
22
+ }
23
+ return current_block_count;
24
+}
25
+
26
+__global__ void convert_vertical_slash_indexes_kernel(
27
+ const int* q_seqlens, // [BATCH, ]
28
+ const int* kv_seqlens, // [BATCH, ]
29
+ const int* vertical_indexes, // [BATCH, N_HEADS, NNZ_V]
30
+ const int* slash_indexes, // [BATCH, N_HEADS, NNZ_S]
31
+ int* block_count, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M)]
32
+ int* block_offset, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M), NNZ_S]
33
+ int* column_count, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M)]
34
+ int* column_index, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M), NNZ_V]
35
+ int64_t N_HEADS, int64_t N_ROWS, int64_t BLOCK_SIZE_M, int64_t BLOCK_SIZE_N,
36
+ int64_t NNZ_V, int64_t NNZ_S,
37
+ bool causal // True for intra, False for succ
38
+) {
39
+ const int batch_idx = blockIdx.y;
40
+ const int head_idx = blockIdx.x;
41
+ const int group_idx = blockIdx.z;
42
+
43
+ int64_t q_seqlen = q_seqlens[batch_idx];
44
+ int64_t kv_seqlen = kv_seqlens[batch_idx];
45
+ int64_t block_idx_m = group_idx * blockDim.x + threadIdx.x;
46
+ int64_t start_m = block_idx_m * BLOCK_SIZE_M;
47
+ if (start_m >= q_seqlen) {
48
+ return;
49
+ }
50
+ int64_t end_m = start_m + BLOCK_SIZE_M;
51
+ vertical_indexes += (batch_idx * N_HEADS + head_idx) * NNZ_V;
52
+ slash_indexes += (batch_idx * N_HEADS + head_idx) * NNZ_S;
53
+ int64_t row_offset = (batch_idx * N_HEADS + head_idx) * N_ROWS + block_idx_m;
54
+ block_count += row_offset;
55
+ block_offset += row_offset * NNZ_S;
56
+ column_count += row_offset;
57
+ column_index += row_offset * NNZ_V;
58
+
59
+ bool has_slash = true;
60
+ int64_t tmp_col_cnt = 0, tmp_blk_cnt = 0;
61
+ int64_t s = 0, v = 0;
62
+ int64_t v_idx = vertical_indexes[v++];
63
+ int64_t s_idx = slash_indexes[s++];
64
+ if (causal) {
65
+ while (s_idx >= end_m + (kv_seqlen - q_seqlen) && s < NNZ_S) {
66
+ s_idx = slash_indexes[s++];
67
+ }
68
+ if (s_idx > end_m + (kv_seqlen - q_seqlen)) has_slash = false;
69
+ s_idx = max((kv_seqlen - q_seqlen) + end_m - s_idx, BLOCK_SIZE_M);
70
+ } else {
71
+ while (s_idx >= end_m + kv_seqlen && s < NNZ_S) {
72
+ s_idx = slash_indexes[s++];
73
+ }
74
+ if (s_idx > end_m + kv_seqlen) has_slash = false;
75
+ s_idx = max(kv_seqlen + end_m - s_idx, BLOCK_SIZE_M);
76
+ }
77
+
78
+ int64_t range_start = s_idx - BLOCK_SIZE_M, range_end = s_idx;
79
+ if (!has_slash) {
80
+ if (causal) {
81
+ range_start = (kv_seqlen - q_seqlen) + end_m;
82
+ range_end = (kv_seqlen - q_seqlen) + end_m + BLOCK_SIZE_N;
83
+ } else {
84
+ range_start = kv_seqlen;
85
+ range_end = kv_seqlen + BLOCK_SIZE_N;
86
+ }
87
+ }
88
+
89
+ bool slash_finished = false;
90
+ while (1) {
91
+ if (v_idx < range_end) {
92
+ if (v_idx < range_start) {
93
+ column_index[tmp_col_cnt++] = v_idx;
94
+ }
95
+ if (v < NNZ_V) {
96
+ v_idx = vertical_indexes[v++];
97
+ } else {
98
+ if (causal)
99
+ v_idx = end_m + BLOCK_SIZE_N + (kv_seqlen - q_seqlen);
100
+ else
101
+ v_idx = end_m + BLOCK_SIZE_N + kv_seqlen;
102
+ }
103
+ } else {
104
+ if ((s < NNZ_S && causal) ||
105
+ (s < NNZ_S && !causal && slash_indexes[s] >= start_m)) {
106
+ if (causal)
107
+ s_idx = max((kv_seqlen - q_seqlen) + end_m - slash_indexes[s++],
108
+ BLOCK_SIZE_M);
109
+ else
110
+ s_idx = max(kv_seqlen + end_m - slash_indexes[s++], BLOCK_SIZE_M);
111
+ } else {
112
+ if (v == NNZ_V || (v_idx > range_start && causal)) {
113
+ // add the last vertical if no more slash
114
+ if (v == NNZ_V && !causal && v_idx < kv_seqlen) {
115
+ column_index[tmp_col_cnt++] = v_idx;
116
+ }
117
+ tmp_blk_cnt = save_blocks(block_offset, range_start, range_end,
118
+ BLOCK_SIZE_N, tmp_blk_cnt, kv_seqlen);
119
+ break;
120
+ } else {
121
+ if (causal) {
122
+ range_start = (kv_seqlen - q_seqlen) + end_m;
123
+ range_end = (kv_seqlen - q_seqlen) + end_m + BLOCK_SIZE_N;
124
+ } else {
125
+ // if slash_finished but there are vertical left, save current
126
+ // blocks
127
+ tmp_blk_cnt = save_blocks(block_offset, range_start, range_end,
128
+ BLOCK_SIZE_N, tmp_blk_cnt, kv_seqlen);
129
+ range_start = kv_seqlen;
130
+ range_end = kv_seqlen + BLOCK_SIZE_N;
131
+ }
132
+ slash_finished = true;
133
+ }
134
+ }
135
+ if (!slash_finished) {
136
+ if (s_idx > range_end + BLOCK_SIZE_M) {
137
+ tmp_blk_cnt = save_blocks(block_offset, range_start, range_end,
138
+ BLOCK_SIZE_N, tmp_blk_cnt, kv_seqlen);
139
+ range_start = s_idx - BLOCK_SIZE_M;
140
+ range_end = s_idx;
141
+ } else if (s_idx > range_end) {
142
+ range_end += BLOCK_SIZE_M;
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ block_count[0] = tmp_blk_cnt;
149
+ column_count[0] = tmp_col_cnt;
150
+}
151
+
152
+void convert_vertical_slash_indexes_64x64(
153
+ const int* q_seqlens, // [BATCH, ]
154
+ const int* kv_seqlens, // [BATCH, ]
155
+ const int* vertical_indexes, // [BATCH, N_HEADS, NNZ_V]
156
+ const int* slash_indexes, // [BATCH, N_HEADS, NNZ_S]
157
+ int* block_count, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M)]
158
+ int* block_offset, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M), NNZ_S]
159
+ int* column_count, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M)]
160
+ int* column_index, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M), NNZ_V]
161
+ int64_t BATCH_SIZE, int64_t N_HEADS, int64_t N_ROWS, int64_t BLOCK_SIZE_M,
162
+ int64_t BLOCK_SIZE_N, int64_t NNZ_V, int64_t NNZ_S, bool causal) {
163
+ const int N_THREADS = 64;
164
+ const dim3 dimBlock(N_THREADS);
165
+ const dim3 dimGrid(N_HEADS, BATCH_SIZE, (N_ROWS + N_THREADS - 1) / N_THREADS);
166
+ convert_vertical_slash_indexes_kernel<<<dimGrid, dimBlock>>>(
167
+ q_seqlens, kv_seqlens, vertical_indexes, slash_indexes, block_count,
168
+ block_offset, column_count, column_index, N_HEADS, N_ROWS, BLOCK_SIZE_M,
169
+ BLOCK_SIZE_N, NNZ_V, NNZ_S, causal);
170
+}
171
+
172
+/**
173
+ * Implements the Algorithm 4 in paper https://arxiv.org/abs/2407.02490.
174
+ *
175
+ * This function builds the index of each row of blocks from vertical indices
176
+ * and slash indices. The vertical indices are treated as points, while the
177
+ * slash indices are converted as ranges. The output consists of the merged
178
+ * ranges and separate column indices, where the ranges are represented by
179
+ * block indices.
180
+ *
181
+ * The implementation is referenced from the original MInference repo:
182
+ * https://github.com/microsoft/MInference/blob/main/csrc/vertical_slash_index.cu.
183
+ */
184
+void convert_vertical_slash_indexes(
185
+ torch::Tensor& block_count, // [BATCH, N_HEADS, NUM_ROWS]
186
+ torch::Tensor& block_offset, // [BATCH, N_HEADS, NUM_ROWS, NNZ_S]
187
+ torch::Tensor& column_count, // [BATCH, N_HEADS, NUM_ROWS]
188
+ torch::Tensor& column_index, // [BATCH, N_HEADS, NUM_ROWS, NNZ_V]
189
+ torch::Tensor q_seqlens, // [BATCH, ]
190
+ torch::Tensor kv_seqlens, // [BATCH, ]
191
+ torch::Tensor vertical_indexes, // [BATCH, N_HEADS, NNZ_V]
192
+ torch::Tensor slash_indexes, // [BATCH, N_HEADS, NNZ_S]
193
+ int64_t context_size, int64_t block_size_M, int64_t block_size_N,
194
+ bool causal) {
195
+ cudaSetDevice(q_seqlens.get_device());
196
+
197
+ int batch_size = slash_indexes.size(0);
198
+ int num_heads = slash_indexes.size(1);
199
+ int nnz_slash = slash_indexes.size(2);
200
+ int nnz_vertical = vertical_indexes.size(2);
201
+ int num_rows = (context_size + block_size_M - 1) / block_size_M;
202
+
203
+ convert_vertical_slash_indexes_64x64(
204
+ q_seqlens.data_ptr<int>(), kv_seqlens.data_ptr<int>(),
205
+ vertical_indexes.data_ptr<int>(), slash_indexes.data_ptr<int>(),
206
+ block_count.data_ptr<int>(), block_offset.data_ptr<int>(),
207
+ column_count.data_ptr<int>(), column_index.data_ptr<int>(), batch_size,
208
+ num_heads, num_rows, block_size_M, block_size_N, nnz_vertical, nnz_slash,
209
+ causal);
210
+}
211
+
212
+__global__ void convert_vertical_slash_indexes_kernel_mergehead(
213
+ const int* q_seqlens, // [BATCH, ]
214
+ const int* kv_seqlens, // [BATCH, ]
215
+ const int* vertical_indexes, // [BATCH, N_HEADS, NNZ_V]
216
+ const int* slash_indexes, // [BATCH, N_HEADS, NNZ_S]
217
+ const int* per_head_vertical_topkv, const int* per_head_slash_topkv,
218
+ int* block_count, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M)]
219
+ int* block_offset, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M), NNZ_S]
220
+ int* column_count, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M)]
221
+ int* column_index, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M), NNZ_V]
222
+ int64_t N_HEADS, int64_t N_ROWS, int64_t BLOCK_SIZE_M, int64_t BLOCK_SIZE_N,
223
+ int64_t NNZ_V, int64_t NNZ_S,
224
+ bool causal // True for intra, False for succ
225
+) {
226
+ const int batch_idx = blockIdx.y;
227
+ const int head_idx = blockIdx.x;
228
+ const int group_idx = blockIdx.z;
229
+
230
+ int64_t q_seqlen = q_seqlens[batch_idx];
231
+ int64_t kv_seqlen = kv_seqlens[batch_idx];
232
+ int64_t block_idx_m = group_idx * blockDim.x + threadIdx.x;
233
+ int64_t start_m = block_idx_m * BLOCK_SIZE_M;
234
+ if (start_m >= q_seqlen) {
235
+ return;
236
+ }
237
+ int64_t end_m = start_m + BLOCK_SIZE_M;
238
+ vertical_indexes += (batch_idx * N_HEADS + head_idx) * NNZ_V;
239
+ slash_indexes += (batch_idx * N_HEADS + head_idx) * NNZ_S;
240
+ int64_t row_offset = (batch_idx * N_HEADS + head_idx) * N_ROWS + block_idx_m;
241
+ block_count += row_offset;
242
+ block_offset += row_offset * NNZ_S;
243
+ column_count += row_offset;
244
+ column_index += row_offset * NNZ_V;
245
+
246
+ // MergeHead: each head has it's unique max topk NNZ_V,NNZ_S. (NNZ_V,NNZ_S
247
+ // above is buffer size, use to compute offset)
248
+ NNZ_S = per_head_slash_topkv[head_idx];
249
+ NNZ_V = per_head_vertical_topkv[head_idx];
250
+
251
+ bool has_slash = true;
252
+ int64_t tmp_col_cnt = 0, tmp_blk_cnt = 0;
253
+ int64_t s = 0, v = 0;
254
+ int64_t v_idx = vertical_indexes[v++];
255
+ int64_t s_idx = slash_indexes[s++];
256
+ if (causal) {
257
+ while (s_idx >= end_m + (kv_seqlen - q_seqlen) && s < NNZ_S) {
258
+ s_idx = slash_indexes[s++];
259
+ }
260
+ if (s_idx > end_m + (kv_seqlen - q_seqlen)) has_slash = false;
261
+ s_idx = max((kv_seqlen - q_seqlen) + end_m - s_idx, BLOCK_SIZE_M);
262
+ } else {
263
+ while (s_idx >= end_m + kv_seqlen && s < NNZ_S) {
264
+ s_idx = slash_indexes[s++];
265
+ }
266
+ if (s_idx > end_m + kv_seqlen) has_slash = false;
267
+ s_idx = max(kv_seqlen + end_m - s_idx, BLOCK_SIZE_M);
268
+ }
269
+
270
+ int64_t range_start = s_idx - BLOCK_SIZE_M, range_end = s_idx;
271
+ if (!has_slash) {
272
+ if (causal) {
273
+ range_start = (kv_seqlen - q_seqlen) + end_m;
274
+ range_end = (kv_seqlen - q_seqlen) + end_m + BLOCK_SIZE_N;
275
+ } else {
276
+ range_start = kv_seqlen;
277
+ range_end = kv_seqlen + BLOCK_SIZE_N;
278
+ }
279
+ }
280
+
281
+ bool slash_finished = false;
282
+ while (1) {
283
+ if (v_idx < range_end) {
284
+ if (v_idx < range_start) {
285
+ column_index[tmp_col_cnt++] = v_idx;
286
+ }
287
+ if (v < NNZ_V) {
288
+ v_idx = vertical_indexes[v++];
289
+ } else {
290
+ if (causal)
291
+ v_idx = end_m + BLOCK_SIZE_N + (kv_seqlen - q_seqlen);
292
+ else
293
+ v_idx = end_m + BLOCK_SIZE_N + kv_seqlen;
294
+ }
295
+ } else {
296
+ if ((s < NNZ_S && causal) ||
297
+ (s < NNZ_S && !causal && slash_indexes[s] >= start_m)) {
298
+ if (causal)
299
+ s_idx = max((kv_seqlen - q_seqlen) + end_m - slash_indexes[s++],
300
+ BLOCK_SIZE_M);
301
+ else
302
+ s_idx = max(kv_seqlen + end_m - slash_indexes[s++], BLOCK_SIZE_M);
303
+ } else {
304
+ if (v == NNZ_V || (v_idx > range_start && causal)) {
305
+ // add the last vertical if no more slash
306
+ if (v == NNZ_V && !causal && v_idx < kv_seqlen) {
307
+ column_index[tmp_col_cnt++] = v_idx;
308
+ }
309
+ tmp_blk_cnt = save_blocks(block_offset, range_start, range_end,
310
+ BLOCK_SIZE_N, tmp_blk_cnt, kv_seqlen);
311
+ break;
312
+ } else {
313
+ if (causal) {
314
+ range_start = (kv_seqlen - q_seqlen) + end_m;
315
+ range_end = (kv_seqlen - q_seqlen) + end_m + BLOCK_SIZE_N;
316
+ } else {
317
+ // if slash_finished but there are vertical left, save current
318
+ // blocks
319
+ tmp_blk_cnt = save_blocks(block_offset, range_start, range_end,
320
+ BLOCK_SIZE_N, tmp_blk_cnt, kv_seqlen);
321
+ range_start = kv_seqlen;
322
+ range_end = kv_seqlen + BLOCK_SIZE_N;
323
+ }
324
+ slash_finished = true;
325
+ }
326
+ }
327
+ if (!slash_finished) {
328
+ if (s_idx > range_end + BLOCK_SIZE_M) {
329
+ tmp_blk_cnt = save_blocks(block_offset, range_start, range_end,
330
+ BLOCK_SIZE_N, tmp_blk_cnt, kv_seqlen);
331
+ range_start = s_idx - BLOCK_SIZE_M;
332
+ range_end = s_idx;
333
+ } else if (s_idx > range_end) {
334
+ range_end += BLOCK_SIZE_M;
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ block_count[0] = tmp_blk_cnt;
341
+ column_count[0] = tmp_col_cnt;
342
+}
343
+
344
+void convert_vertical_slash_indexes_64x64_mergehead(
345
+ const int* q_seqlens, // [BATCH, ]
346
+ const int* kv_seqlens, // [BATCH, ]
347
+ const int* vertical_indexes, // [BATCH, N_HEADS, NNZ_V]
348
+ const int* slash_indexes, // [BATCH, N_HEADS, NNZ_S]
349
+ int* per_head_vertical_topkv, int* per_head_slash_topkv,
350
+ int* block_count, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M)]
351
+ int* block_offset, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M), NNZ_S]
352
+ int* column_count, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M)]
353
+ int* column_index, // [BATCH, N_HEADS, cdiv(N_CTX, BLOCK_SIZE_M), NNZ_V]
354
+ int64_t BATCH_SIZE, int64_t N_HEADS, int64_t N_ROWS, int64_t BLOCK_SIZE_M,
355
+ int64_t BLOCK_SIZE_N, int64_t NNZ_V, int64_t NNZ_S, bool causal) {
356
+ const int N_THREADS = 64;
357
+ const dim3 dimBlock(N_THREADS);
358
+ const dim3 dimGrid(N_HEADS, BATCH_SIZE, (N_ROWS + N_THREADS - 1) / N_THREADS);
359
+ convert_vertical_slash_indexes_kernel_mergehead<<<dimGrid, dimBlock>>>(
360
+ q_seqlens, kv_seqlens, vertical_indexes, slash_indexes,
361
+ per_head_vertical_topkv, per_head_slash_topkv, block_count, block_offset,
362
+ column_count, column_index, N_HEADS, N_ROWS, BLOCK_SIZE_M, BLOCK_SIZE_N,
363
+ NNZ_V, NNZ_S, causal);
364
+}
365
+
366
+/**
367
+ * Implements the Algorithm 4 in paper https://arxiv.org/abs/2407.02490.
368
+ *
369
+ * Like the above convert_vertical_slash_indexes, but with
370
+ * pre-computed vertical and slash counts.
371
+ */
372
+void convert_vertical_slash_indexes_mergehead(
373
+ torch::Tensor& block_count, // [BATCH, N_HEADS, NUM_ROWS]
374
+ torch::Tensor& block_offset, // [BATCH, N_HEADS, NUM_ROWS, NNZ_S]
375
+ torch::Tensor& column_count, // [BATCH, N_HEADS, NUM_ROWS]
376
+ torch::Tensor& column_index, // [BATCH, N_HEADS, NUM_ROWS, NNZ_V]
377
+ torch::Tensor q_seqlens, // [BATCH, ]
378
+ torch::Tensor kv_seqlens, // [BATCH, ]
379
+ torch::Tensor vertical_indexes, // [BATCH, N_HEADS, NNZ_V]
380
+ torch::Tensor slash_indexes, // [BATCH, N_HEADS, NNZ_S]
381
+ torch::Tensor vertical_indices_count, // [N_HEADS, ]
382
+ torch::Tensor slash_indices_count, // [N_HEADS, ]
383
+ int64_t context_size, int64_t block_size_M, int64_t block_size_N,
384
+ bool causal) {
385
+ cudaSetDevice(q_seqlens.get_device());
386
+
387
+ int batch_size = slash_indexes.size(0);
388
+ int num_heads = slash_indexes.size(1);
389
+ int nnz_slash = slash_indexes.size(2);
390
+ int nnz_vertical = vertical_indexes.size(2);
391
+ int num_rows = (context_size + block_size_M - 1) / block_size_M;
392
+
393
+ convert_vertical_slash_indexes_64x64_mergehead(
394
+ q_seqlens.data_ptr<int>(), kv_seqlens.data_ptr<int>(),
395
+ vertical_indexes.data_ptr<int>(), slash_indexes.data_ptr<int>(),
396
+ vertical_indices_count.data_ptr<int>(),
397
+ slash_indices_count.data_ptr<int>(), block_count.data_ptr<int>(),
398
+ block_offset.data_ptr<int>(), column_count.data_ptr<int>(),
399
+ column_index.data_ptr<int>(), batch_size, num_heads, num_rows,
400
+ block_size_M, block_size_N, nnz_vertical, nnz_slash, causal);
401
+}
csrc/ops.h
+25
@@ -59,6 +59,31 @@ void merge_attn_states(torch::Tensor& output,
59
const torch::Tensor& prefix_lse,
60
const torch::Tensor& suffix_output,
61
const torch::Tensor& suffix_lse);
62
+
63
+void convert_vertical_slash_indexes(
64
+ torch::Tensor& block_count, // [BATCH, N_HEADS, NUM_ROWS]
65
+ torch::Tensor& block_offset, // [BATCH, N_HEADS, NUM_ROWS, NNZ_S]
66
+ torch::Tensor& column_count, // [BATCH, N_HEADS, NUM_ROWS]
67
+ torch::Tensor& column_index, // [BATCH, N_HEADS, NUM_ROWS, NNZ_V]
68
+ torch::Tensor q_seqlens, // [BATCH, ]
69
+ torch::Tensor kv_seqlens, // [BATCH, ]
70
+ torch::Tensor vertical_indexes, // [BATCH, N_HEADS, NNZ_V]
71
+ torch::Tensor slash_indexes, // [BATCH, N_HEADS, NNZ_S]
72
+ int64_t context_size, int64_t block_size_M, int64_t block_size_N,
73
+ bool causal);
74
+
75
+void convert_vertical_slash_indexes_mergehead(
76
+ torch::Tensor& block_count, // [BATCH, N_HEADS, NUM_ROWS]
77
+ torch::Tensor& block_offset, // [BATCH, N_HEADS, NUM_ROWS, NNZ_S]
78
+ torch::Tensor& column_count, // [BATCH, N_HEADS, NUM_ROWS]
79
+ torch::Tensor& column_index, // [BATCH, N_HEADS, NUM_ROWS, NNZ_V]
80
+ torch::Tensor q_seqlens, // [BATCH, ]
81
+ torch::Tensor kv_seqlens, // [BATCH, ]
82
+ torch::Tensor vertical_indexes, // [BATCH, N_HEADS, NNZ_V]
83
+ torch::Tensor slash_indexes, // [BATCH, N_HEADS, NNZ_S]
84
+ torch::Tensor vertical_indices_count, // [N_HEADS, ]
85
+ torch::Tensor slash_indices_count, int64_t context_size,
86
+ int64_t block_size_M, int64_t block_size_N, bool causal);
87
#endif
88
89
void rms_norm(torch::Tensor& out, torch::Tensor& input, torch::Tensor& weight,
csrc/torch_bindings.cpp
+23
@@ -77,6 +77,29 @@ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
77
" Tensor suffix_output,"
78
" Tensor suffix_lse) -> ()");
79
ops.impl("merge_attn_states", torch::kCUDA, &merge_attn_states);
80
+
81
+ ops.def(
82
+ "convert_vertical_slash_indexes("
83
+ " Tensor! block_count, Tensor! block_offset, "
84
+ " Tensor! column_count, Tensor! column_index, "
85
+ " Tensor q_seqlens, Tensor q_seqlens, "
86
+ " Tensor vertical_indexes, Tensor slash_indexes, "
87
+ " int context_size, int block_size_M, int block_size_N, "
88
+ " bool causal) -> ()");
89
+ ops.impl("convert_vertical_slash_indexes", torch::kCUDA,
90
+ &convert_vertical_slash_indexes);
91
+
92
+ ops.def(
93
+ "convert_vertical_slash_indexes_mergehead("
94
+ " Tensor! block_count, Tensor! block_offset, "
95
+ " Tensor! column_count, Tensor! column_index, "
96
+ " Tensor q_seqlens, Tensor q_seqlens, "
97
+ " Tensor vertical_indexes, Tensor slash_indexes, "
98
+ " Tensor vertical_indices_count, Tensor slash_indices_count, "
99
+ " int context_size, int block_size_M, int block_size_N, "
100
+ " bool causal) -> ()");
101
+ ops.impl("convert_vertical_slash_indexes_mergehead", torch::kCUDA,
102
+ &convert_vertical_slash_indexes_mergehead);
103
#endif
104
105
// Activation ops
examples/offline_inference/qwen_1m.py
new
+66
@@ -0,0 +1,66 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+import os
3
+from urllib.request import urlopen
4
+
5
+from vllm import LLM, SamplingParams
6
+
7
+os.environ["VLLM_ATTENTION_BACKEND"] = "DUAL_CHUNK_FLASH_ATTN"
8
+os.environ["VLLM_ALLOW_LONG_MAX_MODEL_LEN"] = "1"
9
+
10
+
11
+def load_prompt() -> str:
12
+ # Test cases with various lengths can be found at:
13
+ #
14
+ # https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen2.5-1M/test-data/64k.txt
15
+ # https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen2.5-1M/test-data/200k.txt
16
+ # https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen2.5-1M/test-data/600k.txt
17
+ # https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen2.5-1M/test-data/1m.txt
18
+
19
+ with urlopen(
20
+ "https://qianwen-res.oss-cn-beijing.aliyuncs.com"
21
+ "/Qwen2.5-1M/test-data/600k.txt",
22
+ timeout=5) as response:
23
+ prompt = response.read().decode('utf-8')
24
+ return prompt
25
+
26
+
27
+# Processing the prompt.
28
+def process_requests(llm: LLM, prompts: list[str]) -> None:
29
+ # Create a sampling params object.
30
+ sampling_params = SamplingParams(
31
+ temperature=0.7,
32
+ top_p=0.8,
33
+ top_k=20,
34
+ repetition_penalty=1.05,
35
+ detokenize=True,
36
+ max_tokens=256,
37
+ )
38
+ # Generate texts from the prompts.
39
+ outputs = llm.generate(prompts, sampling_params)
40
+ # Print the outputs.
41
+ for output in outputs:
42
+ prompt_token_ids = output.prompt_token_ids
43
+ generated_text = output.outputs[0].text
44
+ print(f"Prompt length: {len(prompt_token_ids)}, "
45
+ f"Generated text: {generated_text!r}")
46
+
47
+
48
+# Create an LLM.
49
+def initialize_engine() -> LLM:
50
+ llm = LLM(model="Qwen/Qwen2.5-7B-Instruct-1M",
51
+ max_model_len=1048576,
52
+ tensor_parallel_size=4,
53
+ enforce_eager=True,
54
+ enable_chunked_prefill=True,
55
+ max_num_batched_tokens=131072)
56
+ return llm
57
+
58
+
59
+def main():
60
+ llm = initialize_engine()
61
+ prompt = load_prompt()
62
+ process_requests(llm, [prompt])
63
+
64
+
65
+if __name__ == '__main__':
66
+ main()
vllm/_custom_ops.py
+95
@@ -150,6 +150,101 @@ def merge_attn_states(output: torch.Tensor,
150
prefix_lse, suffix_output, suffix_lse)
151
152
153
+def convert_vertical_slash_indexes(
154
+ q_seqlens: torch.Tensor, # [BATCH, ]
155
+ kv_seqlens: torch.Tensor, # [BATCH, ]
156
+ vertical_indexes: torch.Tensor, # [BATCH, N_HEADS, NNZ_V]
157
+ slash_indexes: torch.Tensor, # [BATCH, N_HEADS, NNZ_S]
158
+ context_size: int,
159
+ block_size_M: int,
160
+ block_size_N: int,
161
+ causal: bool = True,
162
+) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
163
+ batch_size = slash_indexes.size(0)
164
+ num_heads = slash_indexes.size(1)
165
+ nnz_slash = slash_indexes.size(2)
166
+ nnz_vertical = vertical_indexes.size(2)
167
+ num_rows = (context_size + block_size_M - 1) // block_size_M
168
+
169
+ block_count = torch.zeros(batch_size,
170
+ num_heads,
171
+ num_rows,
172
+ dtype=q_seqlens.dtype,
173
+ device=q_seqlens.device)
174
+ block_offset = torch.zeros(batch_size,
175
+ num_heads,
176
+ num_rows,
177
+ nnz_slash,
178
+ dtype=q_seqlens.dtype,
179
+ device=q_seqlens.device)
180
+ column_count = torch.zeros(batch_size,
181
+ num_heads,
182
+ num_rows,
183
+ dtype=q_seqlens.dtype,
184
+ device=q_seqlens.device)
185
+ column_index = torch.zeros(batch_size,
186
+ num_heads,
187
+ num_rows,
188
+ nnz_vertical,
189
+ dtype=q_seqlens.dtype,
190
+ device=q_seqlens.device)
191
+
192
+ torch.ops._C.convert_vertical_slash_indexes(
193
+ block_count, block_offset, column_count, column_index, q_seqlens,
194
+ kv_seqlens, vertical_indexes, slash_indexes, context_size,
195
+ block_size_M, block_size_N, causal)
196
+ return block_count, block_offset, column_count, column_index
197
+
198
+
199
+def convert_vertical_slash_indexes_mergehead(
200
+ q_seqlens: torch.Tensor, # [BATCH, ]
201
+ kv_seqlens: torch.Tensor, # [BATCH, ]
202
+ vertical_indexes: torch.Tensor, # [BATCH, N_HEADS, NNZ_V]
203
+ slash_indexes: torch.Tensor, # [BATCH, N_HEADS, NNZ_S]
204
+ # [N_HEADS] : different head use different number of indices
205
+ vertical_indices_count: torch.Tensor,
206
+ slash_indices_count: torch.Tensor,
207
+ context_size: int,
208
+ block_size_M: int,
209
+ block_size_N: int,
210
+ causal: bool = True,
211
+) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
212
+ batch_size = slash_indexes.size(0)
213
+ num_heads = slash_indexes.size(1)
214
+ nnz_slash = slash_indexes.size(2)
215
+ nnz_vertical = vertical_indexes.size(2)
216
+ num_rows = (context_size + block_size_M - 1) // block_size_M
217
+
218
+ block_count = torch.empty(batch_size,
219
+ num_heads,
220
+ num_rows,
221
+ dtype=q_seqlens.dtype,
222
+ device=q_seqlens.device)
223
+ block_offset = torch.empty(batch_size,
224
+ num_heads,
225
+ num_rows,
226
+ nnz_slash,
227
+ dtype=q_seqlens.dtype,
228
+ device=q_seqlens.device)
229
+ column_count = torch.empty(batch_size,
230
+ num_heads,
231
+ num_rows,
232
+ dtype=q_seqlens.dtype,
233
+ device=q_seqlens.device)
234
+ column_index = torch.empty(batch_size,
235
+ num_heads,
236
+ num_rows,
237
+ nnz_vertical,
238
+ dtype=q_seqlens.dtype,
239
+ device=q_seqlens.device)
240
+
241
+ torch.ops._C.convert_vertical_slash_indexes_mergehead(
242
+ block_count, block_offset, column_count, column_index, q_seqlens,
243
+ kv_seqlens, vertical_indexes, slash_indexes, vertical_indices_count,
244
+ slash_indices_count, context_size, block_size_M, block_size_N, causal)
245
+ return block_count, block_offset, column_count, column_index
246
+
247
+
248
# pos encoding ops
249
def rotary_embedding(
250
positions: torch.Tensor,
vllm/attention/backends/dual_chunk_flash_attn.py
new
+1494
@@ -0,0 +1,1494 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+"""Attention layer with Dual chunk flash attention and sparse attention.
3
+"""
4
+import math
5
+from dataclasses import dataclass
6
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type
7
+
8
+import torch
9
+import torch.distributed
10
+import torch.nn.functional as F
11
+
12
+from vllm import _custom_ops as ops
13
+from vllm.attention.backends.abstract import AttentionLayer, AttentionType
14
+from vllm.attention.backends.flash_attn import (FlashAttentionBackend,
15
+ FlashAttentionImpl,
16
+ FlashAttentionMetadata,
17
+ FlashAttentionMetadataBuilder)
18
+from vllm.distributed.parallel_state import get_tensor_model_parallel_rank
19
+from vllm.logger import init_logger
20
+from vllm.utils import async_tensor_h2d
21
+from vllm.vllm_flash_attn import (flash_attn_varlen_func,
22
+ flash_attn_with_kvcache, sparse_attn_func)
23
+
24
+if TYPE_CHECKING:
25
+ from vllm.worker.model_runner import ModelInputForGPUBuilder
26
+
27
+logger = init_logger(__name__)
28
+
29
+
30
+class DualChunkFlashAttentionBackend(FlashAttentionBackend):
31
+
32
+ accept_output_buffer: bool = False
33
+
34
+ @staticmethod
35
+ def get_name() -> str:
36
+ return "DUAL_CHUNK_FLASH_ATTN"
37
+
38
+ @staticmethod
39
+ def get_impl_cls() -> Type["DualChunkFlashAttentionImpl"]:
40
+ return DualChunkFlashAttentionImpl
41
+
42
+ @staticmethod
43
+ def get_metadata_cls() -> Type["DualChunkFlashAttentionMetadata"]:
44
+ return DualChunkFlashAttentionMetadata
45
+
46
+ @staticmethod
47
+ def get_builder_cls() -> Type["DualChunkFlashAttentionMetadataBuilder"]:
48
+ return DualChunkFlashAttentionMetadataBuilder
49
+
50
+
51
+@dataclass
52
+class DualChunkFlashAttentionMetadata(FlashAttentionMetadata):
53
+ # Block size of the paged kv cache.
54
+ block_size: int = 16
55
+
56
+ # Original max position embeddings.
57
+ original_max_position_embeddings: int = 0
58
+
59
+ # Chunk size
60
+ chunk_size: int = 8192
61
+
62
+ # Local size
63
+ local_size: int = 1024
64
+
65
+ # (batch_size,). The orig sequence length per sequence.
66
+ orig_seq_lens: Optional[List[int]] = None
67
+
68
+ # orig_seq_lens stored as a tensor.
69
+ orig_seq_lens_tensor: Optional[torch.Tensor] = None
70
+
71
+ # Length scaling factor
72
+ scaling_factor: Optional[torch.Tensor] = None
73
+
74
+ # (batch_size,). Sequence lengths for intra attention.
75
+ seq_lens_intra: Optional[torch.Tensor] = None
76
+
77
+ # Max sequence length for intra attention.
78
+ max_seq_len_intra: Optional[int] = None
79
+
80
+ # (batch_size, num_blocks). Block table for intra attention.
81
+ block_tables_intra: Optional[torch.Tensor] = None
82
+
83
+ # (batch_size,). Sequence lengths for succ attention.
84
+ seq_lens_succ: Optional[torch.Tensor] = None
85
+
86
+ # Max sequence length for succ attention.
87
+ max_seq_len_succ: Optional[int] = None
88
+
89
+ # (batch_size, num_blocks). Block table for succ attention.
90
+ block_tables_succ: Optional[torch.Tensor] = None
91
+
92
+ # (batch_size,). Sequence lengths for inter attention.
93
+ seq_lens_inter: Optional[torch.Tensor] = None
94
+
95
+ # Max sequence length for inter attention.
96
+ max_seq_len_inter: Optional[int] = None
97
+
98
+ _cached_prefill_metadata: Optional[
99
+ "DualChunkFlashAttentionMetadata"] = None
100
+ _cached_decode_metadata: Optional["DualChunkFlashAttentionMetadata"] = None
101
+
102
+ @property
103
+ def prefill_metadata(self) -> Optional["DualChunkFlashAttentionMetadata"]:
104
+ if self.num_prefills == 0:
105
+ return None
106
+
107
+ if self._cached_prefill_metadata is not None:
108
+ return self._cached_prefill_metadata
109
+
110
+ prefill_metadata = super().prefill_metadata
111
+ if prefill_metadata is None:
112
+ return None
113
+
114
+ prefill_metadata = DualChunkFlashAttentionMetadata(
115
+ **prefill_metadata.asdict_zerocopy())
116
+
117
+ prefill_metadata.orig_seq_lens = (
118
+ None if self.orig_seq_lens is None else
119
+ self.orig_seq_lens[:self.num_prefills])
120
+ prefill_metadata.orig_seq_lens_tensor = (
121
+ None if self.orig_seq_lens_tensor is None else
122
+ self.orig_seq_lens_tensor[:self.num_prefills])
123
+
124
+ if self.original_max_position_embeddings > 0:
125
+ assert prefill_metadata.orig_seq_lens_tensor is not None
126
+ prefill_metadata.scaling_factor = (
127
+ 0.1 * torch.log(prefill_metadata.orig_seq_lens_tensor /
128
+ self.original_max_position_embeddings) +
129
+ 1.0).clip(min=1)
130
+
131
+ self._cached_prefill_metadata = prefill_metadata
132
+ return prefill_metadata
133
+
134
+ @property
135
+ def decode_metadata(self) -> Optional["DualChunkFlashAttentionMetadata"]:
136
+ if self.num_decode_tokens == 0:
137
+ return None
138
+
139
+ if self._cached_decode_metadata is not None:
140
+ return self._cached_decode_metadata
141
+
142
+ decode_metadata = super().decode_metadata
143
+ if decode_metadata is None:
144
+ return None
145
+
146
+ decode_metadata = DualChunkFlashAttentionMetadata(
147
+ **decode_metadata.asdict_zerocopy())
148
+
149
+ decode_metadata.orig_seq_lens_tensor = (
150
+ None if self.orig_seq_lens_tensor is None else
151
+ self.orig_seq_lens_tensor[self.num_prefills:])
152
+
153
+ assert decode_metadata.orig_seq_lens_tensor is not None
154
+ assert decode_metadata.block_tables is not None
155
+
156
+ cache_seq_lens = decode_metadata.orig_seq_lens_tensor
157
+ chunk_len = self.chunk_size - self.local_size
158
+ chunk_num_curr = (cache_seq_lens - 1) // chunk_len
159
+ batch_size = decode_metadata.num_decode_tokens
160
+
161
+ if self.original_max_position_embeddings > 0:
162
+ decode_metadata.scaling_factor = (0.1 * torch.log(
163
+ cache_seq_lens / self.original_max_position_embeddings) +
164
+ 1.0).clip(min=1)
165
+
166
+ seq_lens_intra = cache_seq_lens - chunk_num_curr * chunk_len
167
+ max_seq_len_intra = seq_lens_intra.max().item()
168
+ decode_metadata.seq_lens_intra = seq_lens_intra
169
+ decode_metadata.max_seq_len_intra = max_seq_len_intra
170
+
171
+ block_tables_intra = torch.zeros(
172
+ batch_size,
173
+ (max_seq_len_intra - 1) // self.block_size + 1,
174
+ dtype=decode_metadata.block_tables.dtype,
175
+ device=decode_metadata.block_tables.device,
176
+ )
177
+ for i in range(batch_size):
178
+ st = chunk_num_curr[i] * chunk_len // self.block_size
179
+ ed = min(
180
+ st + (max_seq_len_intra - 1) // self.block_size + 1,
181
+ (cache_seq_lens[i] - 1) // self.block_size + 1,
182
+ )
183
+ block_tables_intra[i, :ed -
184
+ st] = decode_metadata.block_tables[i, st:ed]
185
+ decode_metadata.block_tables_intra = block_tables_intra
186
+
187
+ seq_lens_succ = (chunk_num_curr -
188
+ (chunk_num_curr - 1).clip(min=0)) * chunk_len
189
+ max_seq_len_succ = seq_lens_succ.max().item()
190
+ decode_metadata.seq_lens_succ = seq_lens_succ
191
+ decode_metadata.max_seq_len_succ = max_seq_len_succ
192
+ if max_seq_len_succ:
193
+ block_tables_succ = torch.zeros(
194
+ batch_size,
195
+ (max_seq_len_succ - 1) // self.block_size + 1,
196
+ dtype=decode_metadata.block_tables.dtype,
197
+ device=decode_metadata.block_tables.device,
198
+ )
199
+ for i in range(batch_size):
200
+ start = ((chunk_num_curr[i] - 1).clip(min=0) * chunk_len //
201
+ self.block_size)
202
+ end = min(
203
+ start + (max_seq_len_succ - 1) // self.block_size + 1,
204
+ (cache_seq_lens[i] - 1) // self.block_size + 1,
205
+ )
206
+ block_tables_succ[
207
+ i, :end - start] = decode_metadata.block_tables[i,
208
+ start:end]
209
+ decode_metadata.block_tables_succ = block_tables_succ
210
+
211
+ seq_lens_inter = (chunk_num_curr - 1).clip(min=0) * chunk_len
212
+ max_seq_len_inter = seq_lens_inter.max().item()
213
+ decode_metadata.seq_lens_inter = seq_lens_inter
214
+ decode_metadata.max_seq_len_inter = max_seq_len_inter
215
+
216
+ self._cached_decode_metadata = decode_metadata
217
+ return decode_metadata
218
+
219
+
220
+class DualChunkFlashAttentionMetadataBuilder(FlashAttentionMetadataBuilder):
221
+
222
+ def prepare(self):
223
+ super().prepare()
224
+ self.orig_seq_lens: List[int] = []
225
+
226
+ def _add_seq_group(
227
+ self, inter_data: "ModelInputForGPUBuilder.InterDataForSeqGroup",
228
+ chunked_prefill_enabled: bool, prefix_cache_hit: bool):
229
+ super()._add_seq_group(inter_data, chunked_prefill_enabled,
230
+ prefix_cache_hit)
231
+ for prompt_len, seq_len in zip(inter_data.prompt_lens,
232
+ inter_data.seq_lens):
233
+ self.orig_seq_lens.append(max(prompt_len, seq_len))
234
+
235
+ def build(self, seq_lens: List[int], query_lens: List[int],
236
+ cuda_graph_pad_size: int, batch_size: int):
237
+ attn_metadata = super().build(seq_lens, query_lens,
238
+ cuda_graph_pad_size, batch_size)
239
+ attn_metadata = DualChunkFlashAttentionMetadata(
240
+ **attn_metadata.asdict_zerocopy())
241
+
242
+ device = self.runner.device
243
+ attn_metadata.orig_seq_lens = self.orig_seq_lens
244
+ attn_metadata.orig_seq_lens_tensor = async_tensor_h2d(
245
+ self.orig_seq_lens, torch.int, device, self.runner.pin_memory)
246
+
247
+ attn_metadata.block_size = self.runner.block_size
248
+ dual_chunk_attn_config = getattr(self.runner.model_config.hf_config,
249
+ "dual_chunk_attention_config", {})
250
+ attn_metadata.original_max_position_embeddings = \
251
+ dual_chunk_attn_config.get("original_max_position_embeddings", 0)
252
+ attn_metadata.chunk_size = dual_chunk_attn_config.get(
253
+ "chunk_size", 8192)
254
+ attn_metadata.local_size = dual_chunk_attn_config.get(
255
+ "local_size", 1024)
256
+
257
+ return attn_metadata
258
+
259
+
260
+class DualChunkFlashAttentionImpl(FlashAttentionImpl):
261
+ """
262
+ If the input tensors contain prompt tokens, the layout is as follows:
263
+ |<--------------- num_prefill_tokens ----------------->|
264
+ |<--prefill_0-->|<--prefill_1-->|...|<--prefill_N-1--->|
265
+ Otherwise, the layout is as follows:
266
+ |<----------------- num_decode_tokens ------------------>|
267
+ |<--decode_0-->|..........|<--decode_M-1-->|<--padding-->|
268
+ Generation tokens can contain padding when cuda-graph is used.
269
+ Currently, prompt tokens don't contain any padding.
270
+ The prompts might have different lengths, while the generation tokens
271
+ always have length 1.
272
+ If chunked prefill is enabled, prefill tokens and decode tokens can be
273
+ batched together in a flattened 1D query.
274
+ |<----- num_prefill_tokens ---->|<------- num_decode_tokens --------->|
275
+ |<-prefill_0->|...|<-prefill_N-1->|<--decode_0-->|...|<--decode_M-1-->|
276
+ Currently, cuda graph is disabled for chunked prefill, meaning there's no
277
+ padding between prefill and decode tokens.
278
+ """
279
+
280
+ def __init__(
281
+ self,
282
+ num_heads: int,
283
+ head_size: int,
284
+ scale: float,
285
+ num_kv_heads: int,
286
+ alibi_slopes: Optional[List[float]],
287
+ sliding_window: Optional[int],
288
+ kv_cache_dtype: str,
289
+ blocksparse_params: Optional[Dict[str, Any]] = None,
290
+ logits_soft_cap: Optional[float] = None,
291
+ attn_type: str = AttentionType.DECODER,
292
+ layer_idx: int = -1,
293
+ dual_chunk_attention_config: Optional[Dict[str, Any]] = None,
294
+ ) -> None:
295
+ self.num_heads = num_heads
296
+ self.head_size = head_size
297
+ self.scale = float(scale)
298
+ self.num_kv_heads = num_kv_heads
299
+ if alibi_slopes is not None:
300
+ alibi_slopes = torch.tensor(alibi_slopes, dtype=torch.float32)
301
+ self.alibi_slopes = alibi_slopes
302
+ self.sliding_window = ((sliding_window, sliding_window)
303
+ if sliding_window is not None else (-1, -1))
304
+ self.kv_cache_dtype = kv_cache_dtype
305
+
306
+ assert self.num_heads % self.num_kv_heads == 0
307
+ self.num_queries_per_kv = self.num_heads // self.num_kv_heads
308
+ if sliding_window is not None:
309
+ # NOTE(woosuk): flash-attn's sliding window does not work with
310
+ # paged KV cache.
311
+ raise ValueError(
312
+ "Sliding window is not supported in FlashAttention.")
313
+
314
+ support_head_sizes = (
315
+ DualChunkFlashAttentionBackend.get_supported_head_sizes())
316
+
317
+ if head_size not in support_head_sizes:
318
+ raise ValueError(
319
+ f"Head size {head_size} is not supported by FlashAttention. "
320
+ f"Supported head sizes are: {support_head_sizes}.")
321
+
322
+ assert dual_chunk_attention_config is not None
323
+ self.chunk_size = dual_chunk_attention_config.get("chunk_size", 8192)
324
+ self.local_size = dual_chunk_attention_config.get("local_size", 1024)
325
+ self.original_max_position_embeddings = dual_chunk_attention_config.get(
326
+ "original_max_position_embeddings", 0)
327
+ self.sparse_attention_config = dual_chunk_attention_config.get(
328
+ "sparse_attention_config", None)
329
+ if not self.sparse_attention_config:
330
+ logger.warning_once("Sparse attention will not be enabled as "
331
+ "sparse attention config is not provided.")
332
+ self.sparse_attention_enabled = dual_chunk_attention_config.get(
333
+ "sparse_attention_enabled", self.sparse_attention_config
334
+ is not None)
335
+ self.sparse_attention_threshold = dual_chunk_attention_config.get(
336
+ "sparse_attention_threshold", 32768)
337
+ self.sparse_attention_last_q = dual_chunk_attention_config.get(
338
+ "sparse_attention_last_q", 64)
339
+ self.layer_idx = layer_idx
340
+ self.dual_chunk_attention_config = dual_chunk_attention_config
341
+
342
+ if self.sparse_attention_config:
343
+ self.sparse_attention_config = {
344
+ int(i): j
345
+ for i, j in self.sparse_attention_config[
346
+ self.layer_idx].items()
347
+ }
348
+ start_head = self.num_heads * get_tensor_model_parallel_rank()
349
+ end_head = start_head + self.num_heads
350
+ self.sparse_attention_config = [
351
+ self.sparse_attention_config[i]
352
+ for i in range(start_head, end_head)
353
+ ]
354
+
355
+ if self.sparse_attention_enabled:
356
+ self.arange = torch.arange(self.sparse_attention_last_q,
357
+ device="cuda")
358
+ self.last_q_mask = (self.arange[None, None, :, None]
359
+ >= self.arange[None, None, None, :])
360
+
361
+ def forward( # type: ignore
362
+ self,
363
+ layer: AttentionLayer,
364
+ query: torch.Tensor,
365
+ key: torch.Tensor,
366
+ value: torch.Tensor,
367
+ kv_cache: torch.Tensor,
368
+ attn_metadata: DualChunkFlashAttentionMetadata,
369
+ ) -> torch.Tensor:
370
+ """Forward pass with DualChunkFlashAttention.
371
+ Args:
372
+ query: shape = [num_tokens, num_heads * head_size]
373
+ query_succ: shape = [num_tokens, num_heads * head_size]
374
+ query_inter: shape = [num_tokens, num_heads * head_size]
375
+ key: shape = [num_tokens, num_kv_heads * head_size]
376
+ value: shape = [num_tokens, num_kv_heads * head_size]
377
+ kv_cache = [2, num_blocks, block_size, num_kv_heads * head_size]
378
+ attn_metadata: Metadata for attention.
379
+ Returns:
380
+ shape = [num_tokens, num_heads * head_size]
381
+ """
382
+ (
383
+ query,
384
+ query_succ,
385
+ query_inter,
386
+ query_succ_critical,
387
+ query_inter_critical,
388
+ ) = torch.split(query, query.shape[-1] // 5, dim=-1)
389
+
390
+ assert (
391
+ query_succ is not None and query_inter is not None
392
+ ), "query_succ and query_inter are required in Dual Chunk Attention."
393
+
394
+ num_tokens, hidden_size = query.shape
395
+
396
+ # Reshape the query, key, and value tensors.
397
+ query = query.view(-1, self.num_heads, self.head_size)
398
+ query_succ = query_succ.view(-1, self.num_heads, self.head_size)
399
+ query_inter = query_inter.view(-1, self.num_heads, self.head_size)
400
+ query_succ_critical = query_succ_critical.view(-1, self.num_heads,
401
+ self.head_size)
402
+ query_inter_critical = query_inter_critical.view(
403
+ -1, self.num_heads, self.head_size)
404
+ key = key.view(-1, self.num_kv_heads, self.head_size)
405
+ value = value.view(-1, self.num_kv_heads, self.head_size)
406
+
407
+ if self.original_max_position_embeddings > 0:
408
+ if prefill_meta := attn_metadata.prefill_metadata:
409
+ assert prefill_meta.scaling_factor is not None
410
+ assert prefill_meta.query_start_loc is not None
411
+ assert prefill_meta.orig_seq_lens is not None
412
+ current_start = 0
413
+ query_start_loc_cpu = prefill_meta.query_start_loc.cpu()
414
+ for i in range(len(prefill_meta.orig_seq_lens)):
415
+ current_end = (current_start +
416
+ (query_start_loc_cpu[i + 1] -
417
+ query_start_loc_cpu[i]).item())
418
+ key[current_start:current_end].mul_(
419
+ prefill_meta.scaling_factor[i])
420
+ current_start = current_end
421
+ assert current_end <= attn_metadata.num_prefill_tokens
422
+ if decode_meta := attn_metadata.decode_metadata:
423
+ assert decode_meta.scaling_factor is not None
424
+ scaling_factor = decode_meta.scaling_factor
425
+ key[attn_metadata.num_prefill_tokens:].mul_(
426
+ scaling_factor.unsqueeze(-1).unsqueeze(-1))
427
+
428
+ if kv_cache is not None and kv_cache.numel() > 0:
429
+ key_cache = kv_cache[0]
430
+ value_cache = kv_cache[1]
431
+
432
+ # Reshape the input keys and values and store them in the cache.
433
+ # If kv_cache is not provided, the new key and value tensors are
434
+ # not cached. This happens during the initial memory profiling run.
435
+ ops.reshape_and_cache_flash(
436
+ key,
437
+ value,
438
+ key_cache,
439
+ value_cache,
440
+ attn_metadata.slot_mapping.flatten(),
441
+ self.kv_cache_dtype,
442
+ layer._k_scale,
443
+ layer._v_scale,
444
+ )
445
+
446
+ num_prefill_tokens = attn_metadata.num_prefill_tokens
447
+ num_decode_tokens = attn_metadata.num_decode_tokens
448
+ assert key.shape[0] == num_prefill_tokens + num_decode_tokens
449
+ assert value.shape[0] == num_prefill_tokens + num_decode_tokens
450
+ output = torch.empty_like(query)
451
+
452
+ # Query for decode. KV is not needed because it is already cached.
453
+ decode_query = query[num_prefill_tokens:]
454
+ decode_query_succ = query_succ[num_prefill_tokens:]
455
+ decode_query_inter = query_inter[num_prefill_tokens:]
456
+
457
+ # QKV for prefill.
458
+ query = query[:num_prefill_tokens]
459
+ query_succ = query_succ[:num_prefill_tokens]
460
+ query_inter = query_inter[:num_prefill_tokens]
461
+ query_succ_critical = query_succ_critical[:num_prefill_tokens]
462
+ query_inter_critical = query_inter_critical[:num_prefill_tokens]
463
+ key = key[:num_prefill_tokens]
464
+ value = value[:num_prefill_tokens]
465
+ assert query.shape[0] == num_prefill_tokens
466
+ assert decode_query.shape[0] == num_decode_tokens
467
+
468
+ if prefill_meta := attn_metadata.prefill_metadata:
469
+ # Prompt run.
470
+ if (kv_cache is None or prefill_meta.block_tables is None
471
+ or prefill_meta.block_tables.numel() == 0):
472
+ # normal attention, called during the profiling run.
473
+ out = flash_attn_varlen_func(
474
+ q=query,
475
+ k=key,
476
+ v=value,
477
+ cu_seqlens_q=prefill_meta.seq_start_loc,
478
+ cu_seqlens_k=prefill_meta.seq_start_loc,
479
+ max_seqlen_q=prefill_meta.max_prefill_seq_len,
480
+ max_seqlen_k=prefill_meta.max_prefill_seq_len,
481
+ softmax_scale=self.scale,
482
+ causal=True,
483
+ window_size=self.sliding_window,
484
+ alibi_slopes=self.alibi_slopes,
485
+ )
486
+ assert output[:num_prefill_tokens].shape == out.shape
487
+ output[:num_prefill_tokens] = out
488
+ else:
489
+ # prefix-enabled attention
490
+ assert prefill_meta.seq_lens is not None
491
+ assert prefill_meta.orig_seq_lens is not None
492
+ output[:num_prefill_tokens] = (
493
+ self._dual_chunk_flash_attn_prefill(
494
+ q=query,
495
+ q_succ=query_succ,
496
+ q_inter=query_inter,
497
+ q_succ_critical=query_succ_critical,
498
+ q_inter_critical=query_inter_critical,
499
+ k=key_cache,
500
+ v=value_cache,
501
+ cu_seqlens_q=prefill_meta.query_start_loc,
502
+ cu_seqlens_k=prefill_meta.seq_start_loc,
503
+ orig_seq_lens=prefill_meta.orig_seq_lens,
504
+ scaling_factor=prefill_meta.scaling_factor,
505
+ softmax_scale=self.scale,
506
+ causal=True,
507
+ window_size=(-1, -1),
508
+ alibi_slopes=self.alibi_slopes,
509
+ block_table=prefill_meta.block_tables,
510
+ chunk_size=self.chunk_size,
511
+ local_size=self.local_size,
512
+ ))
513
+
514
+ if decode_meta := attn_metadata.decode_metadata:
515
+ # Decoding run.
516
+ output[num_prefill_tokens:] = (
517
+ self._dual_chunk_flash_attn_decoding(
518
+ decode_query.unsqueeze(1),
519
+ decode_query_succ.unsqueeze(1),
520
+ decode_query_inter.unsqueeze(1),
521
+ key_cache,
522
+ value_cache,
523
+ block_table=decode_meta.block_tables,
524
+ cache_seqlens=decode_meta.seq_lens_tensor,
525
+ softmax_scale=self.scale,
526
+ causal=True,
527
+ alibi_slopes=self.alibi_slopes,
528
+ chunk_size=self.chunk_size,
529
+ local_size=self.local_size,
530
+ original_max_position_embeddings=self.
531
+ original_max_position_embeddings,
532
+ decode_meta=decode_meta,
533
+ ).squeeze(1))
534
+ # Reshape the output tensor.
535
+ return output.view(num_tokens, hidden_size)
536
+
537
+ def _dual_chunk_flash_attn_prefill(
538
+ self,
539
+ q,
540
+ q_succ,
541
+ q_inter,
542
+ q_succ_critical,
543
+ q_inter_critical,
544
+ k,
545
+ v,
546
+ cu_seqlens_q,
547
+ cu_seqlens_k,
548
+ orig_seq_lens: List[int],
549
+ scaling_factor: torch.Tensor,
550
+ softmax_scale: float,
551
+ causal: Optional[bool] = True,
552
+ window_size: Tuple[int, int] = (-1, -1),
553
+ alibi_slopes: Optional[torch.Tensor] = None,
554
+ block_table: Optional[torch.Tensor] = None,
555
+ chunk_size: int = 8192,
556
+ local_size: int = 1024,
557
+ ):
558
+ if alibi_slopes is not None:
559
+ raise ValueError(
560
+ "Dual Chunk Attention does not support alibi_slopes")
561
+ if not causal:
562
+ raise ValueError(
563
+ "Dual Chunk Attention does not support causal=False")
564
+ if window_size != (-1, -1):
565
+ raise ValueError(
566
+ "Dual Chunk Attention does not support window_size")
567
+
568
+ cu_seqlens_q_cpu = cu_seqlens_q.cpu().tolist()
569
+ cu_seqlens_k_cpu = cu_seqlens_k.cpu().tolist()
570
+ all_outputs = []
571
+
572
+ for i in range(0, len(cu_seqlens_q_cpu) - 1):
573
+ qs = cu_seqlens_q_cpu[i]
574
+ qe = cu_seqlens_q_cpu[i:i + 2][-1]
575
+ ks = cu_seqlens_k_cpu[i]
576
+ ke = cu_seqlens_k_cpu[i:i + 2][-1]
577
+
578
+ current_q = q[qs:qe]
579
+ current_q_succ = q_succ[qs:qe]
580
+ current_q_inter = q_inter[qs:qe]
581
+ current_q_succ_critical = q_succ_critical[qs:qe]
582
+ current_q_inter_critical = q_inter_critical[qs:qe]
583
+
584
+ if block_table is None:
585
+ current_k = k[ks:ke]
586
+ current_v = v[ks:ke]
587
+ current_block_table = None
588
+ current_orig_seq_len = orig_seq_lens[i]
589
+ else:
590
+ current_block_table = block_table[i]
591
+ current_orig_seq_len = orig_seq_lens[i]
592
+ current_k = k
593
+ current_v = v
594
+ sparse_attn_enabled = (self.sparse_attention_enabled
595
+ and current_orig_seq_len
596
+ > self.sparse_attention_threshold)
597
+
598
+ if current_q.shape[0] == 0:
599
+ continue
600
+
601
+ if current_k.shape[0] == 0:
602
+ all_outputs.append(
603
+ torch.zeros(
604
+ (current_q.shape[0], current_q.shape[1], v.shape[2]),
605
+ device=q.device,
606
+ dtype=q.dtype,
607
+ ))
608
+ continue
609
+
610
+ current_output = torch.empty_like(current_q)
611
+ group_size = int(current_q.size(-2) / current_k.size(-2))
612
+
613
+ if sparse_attn_enabled:
614
+ num_device_q_heads = current_q.size(-2)
615
+ heads_vertical_size = torch.empty(size=(num_device_q_heads, ),
616
+ dtype=torch.int32)
617
+ heads_slash_size = torch.empty(size=(num_device_q_heads, ),
618
+ dtype=torch.int32)
619
+ for head_id in range(current_q.size(-2)):
620
+ (
621
+ ty,
622
+ vertical_size,
623
+ slash_size,
624
+ _,
625
+ ) = self.sparse_attention_config[head_id]
626
+ assert ty == "vertical_and_slash", "only support slash mode"
627
+
628
+ if vertical_size == 30:
629
+ vertical_size += 100
630
+ heads_vertical_size[head_id] = vertical_size
631
+ heads_slash_size[head_id] = slash_size
632
+
633
+ current_output = self._dual_chunk_flash_attn_prefill_func(
634
+ current_q, # allheads
635
+ current_q_succ,
636
+ current_q_inter,
637
+ current_q_succ_critical,
638
+ current_q_inter_critical,
639
+ current_k,
640
+ current_v,
641
+ current_block_table,
642
+ softmax_scale,
643
+ chunk_size,
644
+ local_size,
645
+ scaling_factor[i].item(),
646
+ ke - ks,
647
+ sparse_attn_enabled=sparse_attn_enabled,
648
+ heads_vertical_size=heads_vertical_size,
649
+ heads_slash_size=heads_slash_size,
650
+ group_size=group_size)
651
+ else:
652
+ for head_id in range(current_q.size(-2)):
653
+ # (seq_len, num_heads, head_size)
654
+ current_q_head = current_q[:, head_id, :].unsqueeze(1)
655
+ current_q_succ_head = \
656
+ current_q_succ[:, head_id, :].unsqueeze(1)
657
+ current_q_inter_head = \
658
+ current_q_inter[:, head_id, :].unsqueeze(1)
659
+ current_q_succ_head_critical = \
660
+ current_q_succ_critical[:, head_id, :].unsqueeze(1)
661
+ current_q_inter_head_critical = \
662
+ current_q_inter_critical[:, head_id, :].unsqueeze(1)
663
+ if block_table is not None:
664
+ current_k_head = current_k[..., head_id //
665
+ group_size, :].unsqueeze(2)
666
+ current_v_head = current_v[..., head_id //
667
+ group_size, :].unsqueeze(2)
668
+
669
+ else:
670
+ current_k_head = current_k[:, head_id, :].unsqueeze(1)
671
+ current_v_head = current_v[:, head_id, :].unsqueeze(1)
672
+
673
+ current_out = self._dual_chunk_flash_attn_prefill_func(
674
+ current_q_head,
675
+ current_q_succ_head,
676
+ current_q_inter_head,
677
+ current_q_succ_head_critical,
678
+ current_q_inter_head_critical,
679
+ current_k_head,
680
+ current_v_head,
681
+ current_block_table,
682
+ softmax_scale,
683
+ chunk_size,
684
+ local_size,
685
+ scaling_factor[i].item(),
686
+ ke - ks,
687
+ sparse_attn_enabled=sparse_attn_enabled,
688
+ )
689
+ current_output[:, head_id:head_id + 1, :] = current_out
690
+ all_outputs.append(current_output)
691
+ return torch.cat(all_outputs, dim=0)
692
+
693
+ def _dual_chunk_flash_attn_prefill_func(
694
+ self,
695
+ q,
696
+ q_succ,
697
+ q_inter,
698
+ q_succ_critical,
699
+ q_inter_critical,
700
+ k,
701
+ v,
702
+ block_table,
703
+ softmax_scale: float,
704
+ chunk_size: int,
705
+ local_size: int,
706
+ scaling_factor: float,
707
+ k_length: int,
708
+ sparse_attn_enabled: Optional[bool] = True,
709
+ heads_vertical_size=None,
710
+ heads_slash_size=None,
711
+ group_size=None,
712
+ ):
713
+ flash_results = []
714
+ chunk_len = chunk_size - local_size
715
+
716
+ if block_table is not None:
717
+ block_size = v.shape[1]
718
+ if chunk_len % block_size != 0:
719
+ raise ValueError("chunk_len must be divisible by block_size.")
720
+ else:
721
+ block_size = 1
722
+
723
+ if self.original_max_position_embeddings > 0:
724
+ softmax_scale = softmax_scale * scaling_factor
725
+
726
+ begin = k_length - q.shape[0]
727
+ while begin < k_length:
728
+ flash_per_chunk = []
729
+
730
+ prev_chunk_end_pos = (begin // chunk_len) * chunk_len
731
+ next_chunk_end_pos = prev_chunk_end_pos + chunk_len
732
+ end = min(next_chunk_end_pos, k_length)
733
+ qbegin = begin - (k_length - q.shape[0])
734
+ qend = end - (k_length - q.shape[0])
735
+
736
+ qk_chunks = []
737
+ q_states_intra = q[qbegin:qend]
738
+ # choose critical token
739
+ if block_table is not None:
740
+ block_tables_intra = _get_block(block_table, block_size,
741
+ prev_chunk_end_pos, end)
742
+ k_states_intra = k[block_tables_intra].view(
743
+ -1, *k.shape[-2:])[:(end - prev_chunk_end_pos)]
744
+ v_states_intra = v[block_tables_intra].view(
745
+ -1, *v.shape[-2:])[:(end - prev_chunk_end_pos)]
746
+ else:
747
+ block_tables_intra = None
748
+ k_states_intra = k[prev_chunk_end_pos:end]
749
+ v_states_intra = v[prev_chunk_end_pos:end]
750
+
751
+ if sparse_attn_enabled:
752
+ last_q_size = min(qend - qbegin, self.sparse_attention_last_q)
753
+ _, num_device_k_heads, head_dim = k_states_intra.shape
754
+ k_states_intra = (k_states_intra.unsqueeze(2).repeat(
755
+ 1, 1, group_size,
756
+ 1).reshape(-1, num_device_k_heads * group_size, head_dim))
757
+ v_states_intra = (v_states_intra.unsqueeze(2).repeat(
758
+ 1, 1, group_size,
759
+ 1).reshape(-1, num_device_k_heads * group_size, head_dim))
760
+ qk_chunks.append(
761
+ (q_states_intra.transpose(0, 1)[:, -last_q_size:] *
762
+ softmax_scale) @ k_states_intra.permute(1, 2, 0))
763
+
764
+ if prev_chunk_end_pos - chunk_len >= 0:
765
+ q_states_succ = q_succ[qbegin:qend]
766
+ q_states_succ_critical = q_succ_critical[qbegin:qend]
767
+ if block_table is not None:
768
+ block_tables_succ = _get_block(
769
+ block_table, block_size,
770
+ prev_chunk_end_pos - chunk_len, prev_chunk_end_pos)
771
+ k_states_succ = k[block_tables_succ].view(
772
+ -1, *k.shape[-2:])[:chunk_len]
773
+ v_states_succ = v[block_tables_succ].view(
774
+ -1, *v.shape[-2:])[:chunk_len]
775
+ else:
776
+ k_states_succ = k[prev_chunk_end_pos -
777
+ chunk_len:prev_chunk_end_pos]
778
+ v_states_succ = v[prev_chunk_end_pos -
779
+ chunk_len:prev_chunk_end_pos]
780
+
781
+ if sparse_attn_enabled:
782
+ k_states_succ = (k_states_succ.unsqueeze(2).repeat(
783
+ 1, 1, group_size,
784
+ 1).reshape(-1, num_device_k_heads * group_size,
785
+ head_dim))
786
+ v_states_succ = (v_states_succ.unsqueeze(2).repeat(
787
+ 1, 1, group_size,
788
+ 1).reshape(-1, num_device_k_heads * group_size,
789
+ head_dim))
790
+ qk_chunks.append((q_states_succ_critical.transpose(
791
+ 0, 1)[:, -last_q_size:] * softmax_scale)
792
+ @ k_states_succ.permute(1, 2, 0))
793
+
794
+ if prev_chunk_end_pos - chunk_len * 2 >= 0:
795
+ q_states_inter = q_inter[qbegin:qend]
796
+ q_states_inter_critical = q_inter_critical[qbegin:qend]
797
+ if block_table is not None:
798
+ block_tables_inter = _get_block(
799
+ block_table, block_size, 0,
800
+ prev_chunk_end_pos - chunk_len)
801
+ k_states_inter = k[block_tables_inter].view(
802
+ -1, *k.shape[-2:])[:(prev_chunk_end_pos - chunk_len)]
803
+ v_states_inter = v[block_tables_inter].view(
804
+ -1, *v.shape[-2:])[:(prev_chunk_end_pos - chunk_len)]
805
+ else:
806
+ k_states_inter = k[:prev_chunk_end_pos - chunk_len]
807
+ v_states_inter = v[:prev_chunk_end_pos - chunk_len]
808
+
809
+ if sparse_attn_enabled:
810
+ k_states_inter = (k_states_inter.unsqueeze(2).repeat(
811
+ 1, 1, group_size,
812
+ 1).reshape(-1, num_device_k_heads * group_size,
813
+ head_dim))
814
+ v_states_inter = (v_states_inter.unsqueeze(2).repeat(
815
+ 1, 1, group_size,
816
+ 1).reshape(-1, num_device_k_heads * group_size,
817
+ head_dim))
818
+ qk_chunks.append((q_states_inter_critical.transpose(
819
+ 0, 1)[:, -last_q_size:] * softmax_scale)
820
+ @ k_states_inter.permute(1, 2, 0))
821
+
822
+ if sparse_attn_enabled:
823
+ reversed_qk = qk_chunks[::-1]
824
+ qk = torch.cat(reversed_qk, dim=-1)
825
+
826
+ qk[:, :, -last_q_size:] = torch.where(
827
+ self.last_q_mask[..., -last_q_size:,
828
+ -last_q_size:].to(qk.device),
829
+ qk[:, :, -last_q_size:], -torch.inf)
830
+ qk = F.softmax(qk, dim=-1, dtype=torch.float32)
831
+
832
+ vertical = qk.sum(-2, keepdim=True)
833
+ vertical[..., :30] = torch.inf
834
+
835
+ # Avoid sorting by using the min/max ints to fill the indexer
836
+ # buffers.
837
+ int32_max = torch.iinfo(torch.int32).max
838
+ int32_min = torch.iinfo(torch.int32).min
839
+ n_heads = qk.size()[0]
840
+ max_slash_topk = torch.max(heads_slash_size).item()
841
+ max_vertical_topk = torch.max(heads_vertical_size).item()
842
+ # store each head's slash topk, vertical topk
843
+ vertical = vertical.reshape((n_heads, -1))
844
+ # prevent out of range when prompt size < max_vertical_topk
845
+ max_vertical_topk = min(vertical.shape[-1], max_vertical_topk)
846
+ vertical_topk_buffer = torch.topk(vertical, max_vertical_topk,
847
+ -1).indices
848
+ slash_topk_buffer = torch.empty(size=(n_heads, max_slash_topk),
849
+ dtype=torch.int64,
850
+ device=qk.device)
851
+ for head_i in range(n_heads):
852
+ # (nqheads=1, lastq, k_len)
853
+ head_score = qk[head_i:head_i + 1, :, :]
854
+ slash_scores = _sum_all_diagonal_matrix(head_score)
855
+ if head_score.size(1) != 1:
856
+ # drop right up corner
857
+ slash_scores = slash_scores[..., :-last_q_size + 1]
858
+ slash_scores[..., -100:] = torch.inf
859
+
860
+ head_slash_size = heads_slash_size[head_i]
861
+ head_slash_size = min(head_slash_size, vertical.size(-1))
862
+ slash_topk = torch.topk(slash_scores, head_slash_size,
863
+ -1).indices
864
+ #(nheads, max_topk)
865
+ slash_topk_buffer[head_i, :head_slash_size] = slash_topk
866
+
867
+ # reset heads topk
868
+ heads_slash_size[head_i] = head_slash_size
869
+ heads_vertical_size[head_i] = min(
870
+ heads_vertical_size[head_i], max_vertical_topk)
871
+
872
+ # store
873
+ vertical_buffer = torch.full((n_heads, max_vertical_topk),
874
+ int32_max,
875
+ dtype=torch.int64,
876
+ device=q.device)
877
+ slash_buffer = torch.full((n_heads, max_slash_topk),
878
+ int32_min,
879
+ dtype=torch.int64,
880
+ device=q.device)
881
+ succ_vertical_buffer = torch.full((n_heads, max_vertical_topk),
882
+ int32_max,
883
+ dtype=torch.int64,
884
+ device=q.device)
885
+ succ_slash_buffer = torch.full((n_heads, max_slash_topk),
886
+ int32_min,
887
+ dtype=torch.int64,
888
+ device=q.device)
889
+ inter_vertical_buffer = torch.full(
890
+ (n_heads, max_vertical_topk),
891
+ int32_max,
892
+ dtype=torch.int64,
893
+ device=q.device)
894
+ inter_slash_buffer = torch.full((n_heads, max_slash_topk),
895
+ int32_min,
896
+ dtype=torch.int64,
897
+ device=q.device)
898
+
899
+ vertical_size_buffer = torch.empty(size=(n_heads, ),
900
+ dtype=torch.int32,
901
+ device=q.device)
902
+ slash_sizes_buffer = torch.empty(size=(n_heads, ),
903
+ dtype=torch.int32,
904
+ device=q.device)
905
+ succ_vertical_size_buffer = torch.empty(size=(n_heads, ),
906
+ dtype=torch.int32,
907
+ device=q.device)
908
+ succ_slash_sizes_buffer = torch.empty(size=(n_heads, ),
909
+ dtype=torch.int32,
910
+ device=q.device)
911
+ inter_vertical_size_buffer = torch.empty(size=(n_heads, ),
912
+ dtype=torch.int32,
913
+ device=q.device)
914
+ inter_slash_sizes_buffer = torch.empty(size=(n_heads, ),
915
+ dtype=torch.int32,
916
+ device=q.device)
917
+
918
+ for head_i in range(n_heads):
919
+ vertical_topk = vertical_topk_buffer[
920
+ head_i, :heads_vertical_size[head_i]]
921
+ # intra
922
+ intra_vertical_indices = vertical_topk[
923
+ vertical_topk >=
924
+ prev_chunk_end_pos] - prev_chunk_end_pos
925
+ if intra_vertical_indices.nelement() == 0:
926
+ intra_vertical_indices = torch.cat([
927
+ intra_vertical_indices,
928
+ torch.arange(0,
929
+ k_states_intra.size(0),
930
+ max(1,
931
+ k_states_intra.size(0) / 5),
932
+ dtype=torch.int32,
933
+ device=intra_vertical_indices.device)
934
+ ])
935
+ slash_topk = slash_topk_buffer[
936
+ head_i, :heads_slash_size[head_i]]
937
+ intra_slash_indices = (
938
+ (qk.size(-1) - 1) -
939
+ slash_topk[slash_topk >= prev_chunk_end_pos])
940
+ # fill buffer
941
+ v_count = intra_vertical_indices.nelement()
942
+ s_count = intra_slash_indices.nelement()
943
+ vertical_size_buffer[head_i] = v_count
944
+ slash_sizes_buffer[head_i] = s_count
945
+ vertical_buffer[head_i, :v_count].copy_(
946
+ intra_vertical_indices)
947
+ slash_buffer[head_i, :s_count].copy_(intra_slash_indices)
948
+ # succ
949
+ if prev_chunk_end_pos - chunk_len >= 0:
950
+ succ_vertical_indices = vertical_topk[
951
+ (vertical_topk < prev_chunk_end_pos)
952
+ & (vertical_topk >= prev_chunk_end_pos -
953
+ chunk_len)] - (prev_chunk_end_pos - chunk_len)
954
+ # TODO: support no vertical
955
+ if succ_vertical_indices.nelement() == 0:
956
+ succ_vertical_indices = torch.cat([
957
+ succ_vertical_indices,
958
+ torch.arange(
959
+ 0,
960
+ k_states_succ.size(0),
961
+ max(1,
962
+ k_states_succ.size(0) / 5),
963
+ dtype=torch.int32,
964
+ device=intra_vertical_indices.device)
965
+ ])
966
+ succ_slash_indices = (
967
+ (prev_chunk_end_pos + (qend - qbegin) - 1) -
968
+ slash_topk[((slash_topk >=
969
+ (prev_chunk_end_pos - chunk_len)) &
970
+ (slash_topk < (prev_chunk_end_pos +
971
+ (qend - qbegin))))])
972
+ if succ_slash_indices.nelement() == 0:
973
+ succ_slash_indices = torch.cat([
974
+ succ_slash_indices,
975
+ torch.arange(
976
+ 0,
977
+ k_states_succ.size(0),
978
+ max(1,
979
+ k_states_succ.size(0) / 5),
980
+ dtype=torch.int32,
981
+ device=intra_vertical_indices.device)
982
+ ])
983
+ # fill buffer
984
+ v_count = succ_vertical_indices.nelement()
985
+ s_count = succ_slash_indices.nelement()
986
+ succ_vertical_size_buffer[head_i] = v_count
987
+ succ_slash_sizes_buffer[head_i] = s_count
988
+ succ_vertical_buffer[head_i, :v_count].copy_(
989
+ succ_vertical_indices)
990
+ succ_slash_buffer[head_i, :s_count].copy_(
991
+ succ_slash_indices)
992
+
993
+ if prev_chunk_end_pos - 2 * chunk_len >= 0:
994
+ inter_vertical_indices = vertical_topk[
995
+ vertical_topk < prev_chunk_end_pos - chunk_len]
996
+
997
+ if inter_vertical_indices.nelement() == 0:
998
+ inter_vertical_indices = torch.cat([
999
+ inter_vertical_indices,
1000
+ torch.arange(
1001
+ 0,
1002
+ k_states_inter.size(0),
1003
+ max(1,
1004
+ k_states_inter.size(0) / 5),
1005
+ dtype=torch.int32,
1006
+ device=intra_vertical_indices.device)
1007
+ ])
1008
+ inter_slash_indices = (
1009
+ (prev_chunk_end_pos - chunk_len +
1010
+ (qend - qbegin) - 1) -
1011
+ slash_topk[slash_topk < (prev_chunk_end_pos -
1012
+ chunk_len +
1013
+ (qend - qbegin))])
1014
+ if inter_slash_indices.nelement() == 0:
1015
+ inter_slash_indices = torch.cat([
1016
+ inter_slash_indices,
1017
+ torch.arange(
1018
+ 0,
1019
+ k_states_inter.size(0),
1020
+ max(1,
1021
+ k_states_inter.size(0) / 5),
1022
+ dtype=torch.int32,
1023
+ device=intra_vertical_indices.device)
1024
+ ])
1025
+ # fill buffer
1026
+ v_count = inter_vertical_indices.nelement()
1027
+ s_count = inter_slash_indices.nelement()
1028
+ inter_vertical_size_buffer[head_i] = v_count
1029
+ inter_slash_sizes_buffer[head_i] = s_count
1030
+ inter_vertical_buffer[head_i, :v_count].copy_(
1031
+ inter_vertical_indices)
1032
+ inter_slash_buffer[head_i, :s_count].copy_(
1033
+ inter_slash_indices)
1034
+ else:
1035
+ intra_vertical_indices, intra_slash_indices = None, None
1036
+ succ_vertical_indices, succ_slash_indices = None, None
1037
+ inter_vertical_indices, inter_slash_indices = None, None
1038
+
1039
+ if sparse_attn_enabled:
1040
+ flash_result = self._do_flash_attn(
1041
+ q_states_intra,
1042
+ k_states_intra,
1043
+ v_states_intra,
1044
+ softmax_scale=softmax_scale,
1045
+ causal=True,
1046
+ block_table=block_table,
1047
+ stage="intra",
1048
+ vertical_indices=vertical_buffer,
1049
+ slash_indices=slash_buffer,
1050
+ vertical_indices_count=vertical_size_buffer,
1051
+ slash_indices_count=slash_sizes_buffer,
1052
+ mergehead_softmax_scale=softmax_scale,
1053
+ sparse_attn_enabled=sparse_attn_enabled)
1054
+ else:
1055
+ flash_result = self._do_flash_attn(
1056
+ q_states_intra,
1057
+ k_states_intra,
1058
+ v_states_intra,
1059
+ softmax_scale=softmax_scale,
1060
+ causal=True,
1061
+ block_table=block_table,
1062
+ stage="intra",
1063
+ vertical_indices=intra_vertical_indices,
1064
+ slash_indices=intra_slash_indices,
1065
+ sparse_attn_enabled=sparse_attn_enabled)
1066
+ flash_per_chunk.append(flash_result)
1067
+
1068
+ if prev_chunk_end_pos - chunk_len >= 0:
1069
+ if sparse_attn_enabled:
1070
+ flash_result = self._do_flash_attn(
1071
+ q_states_succ,
1072
+ k_states_succ,
1073
+ v_states_succ,
1074
+ softmax_scale=softmax_scale,
1075
+ causal=False,
1076
+ block_table=block_table,
1077
+ stage="succ",
1078
+ vertical_indices=succ_vertical_buffer,
1079
+ slash_indices=succ_slash_buffer,
1080
+ vertical_indices_count=succ_vertical_size_buffer,
1081
+ slash_indices_count=succ_slash_sizes_buffer,
1082
+ mergehead_softmax_scale=softmax_scale,
1083
+ sparse_attn_enabled=sparse_attn_enabled)
1084
+ else:
1085
+ flash_result = self._do_flash_attn(
1086
+ q_states_succ,
1087
+ k_states_succ,
1088
+ v_states_succ,
1089
+ softmax_scale=softmax_scale,
1090
+ causal=False,
1091
+ block_table=block_table,
1092
+ stage="succ",
1093
+ vertical_indices=succ_vertical_indices,
1094
+ slash_indices=succ_slash_indices,
1095
+ sparse_attn_enabled=sparse_attn_enabled)
1096
+ flash_per_chunk.append(flash_result)
1097
+
1098
+ if prev_chunk_end_pos - chunk_len * 2 >= 0:
1099
+ if sparse_attn_enabled:
1100
+ flash_result = self._do_flash_attn(
1101
+ q_states_inter,
1102
+ k_states_inter,
1103
+ v_states_inter,
1104
+ softmax_scale=softmax_scale,
1105
+ causal=False,
1106
+ block_table=block_table,
1107
+ stage="inter",
1108
+ vertical_indices=inter_vertical_buffer,
1109
+ slash_indices=inter_slash_buffer,
1110
+ vertical_indices_count=inter_vertical_size_buffer,
1111
+ slash_indices_count=inter_slash_sizes_buffer,
1112
+ mergehead_softmax_scale=softmax_scale,
1113
+ sparse_attn_enabled=sparse_attn_enabled)
1114
+ else:
1115
+ flash_result = self._do_flash_attn(
1116
+ q_states_inter,
1117
+ k_states_inter,
1118
+ v_states_inter,
1119
+ softmax_scale=softmax_scale,
1120
+ causal=False,
1121
+ block_table=block_table,
1122
+ stage="inter",
1123
+ vertical_indices=inter_vertical_indices,
1124
+ slash_indices=inter_slash_indices,
1125
+ sparse_attn_enabled=sparse_attn_enabled)
1126
+ flash_per_chunk.append(flash_result)
1127
+
1128
+ flash_results.append(flash_per_chunk)
1129
+ begin = end
1130
+
1131
+ attn_output = self._merge_attn_outputs(flash_results)
1132
+ del flash_results
1133
+ return attn_output
1134
+
1135
+ def _do_flash_attn(
1136
+ self,
1137
+ query_states: torch.Tensor,
1138
+ key_states: torch.Tensor,
1139
+ value_states: torch.Tensor,
1140
+ softmax_scale: float,
1141
+ causal: bool = True,
1142
+ block_table: torch.Tensor = None,
1143
+ max_seqlen_k: Optional[int] = None,
1144
+ stage: str = "intra",
1145
+ vertical_indices: Optional[torch.Tensor] = None,
1146
+ slash_indices: Optional[torch.Tensor] = None,
1147
+ vertical_indices_count: Optional[torch.Tensor] = None,
1148
+ slash_indices_count: Optional[torch.Tensor] = None,
1149
+ mergehead_softmax_scale: Optional[float] = None,
1150
+ sparse_attn_enabled: Optional[bool] = False,
1151
+ ):
1152
+ if max_seqlen_k is None:
1153
+ max_seqlen_k = key_states.shape[0]
1154
+
1155
+ q_len = query_states.shape[0]
1156
+ q_heads = query_states.shape[1]
1157
+ h_dim = query_states.shape[-1]
1158
+
1159
+ if sparse_attn_enabled:
1160
+ assert slash_indices is not None
1161
+ if stage == "intra":
1162
+ assert causal
1163
+ else:
1164
+ assert not causal
1165
+
1166
+ query_states = query_states.unsqueeze(0).transpose(1, 2)
1167
+ key_states = key_states.unsqueeze(0).transpose(1, 2)
1168
+ value_states = value_states.unsqueeze(0).transpose(1, 2)
1169
+
1170
+ q = query_states
1171
+ k = key_states
1172
+ v = value_states
1173
+
1174
+ if (vertical_indices_count is not None and \
1175
+ slash_indices_count is not None):
1176
+ assert mergehead_softmax_scale is not None
1177
+
1178
+ res, s_lse = _vertical_slash_sparse_attention(
1179
+ q,
1180
+ k,
1181
+ v,
1182
+ vertical_indices,
1183
+ slash_indices,
1184
+ mergehead_softmax_scale,
1185
+ causal=causal,
1186
+ stage=stage,
1187
+ vertical_indices_count=vertical_indices_count,
1188
+ slash_indices_count=slash_indices_count)
1189
+ res = res.view(q_heads, q_len,
1190
+ h_dim).transpose(0, 1) # (qlen,nhead,h_dim)
1191
+ s_lse = s_lse.view(
1192
+ q_heads, q_len,
1193
+ 1).squeeze(-1).unsqueeze(0).float() # (1, nhead,qlen)
1194
+ else:
1195
+ res, s_lse = _vertical_slash_sparse_attention(q,
1196
+ k,
1197
+ v,
1198
+ vertical_indices,
1199
+ slash_indices,
1200
+ softmax_scale,
1201
+ causal=causal,
1202
+ stage=stage)
1203
+ res = res.view(q_len, q_heads, h_dim)
1204
+ s_lse = s_lse.view(q_len, q_heads, 1).transpose(0, 2).float()
1205
+ return res, s_lse
1206
+
1207
+ output, softmax_lse = flash_attn_varlen_func(
1208
+ q=query_states,
1209
+ k=key_states,
1210
+ v=value_states,
1211
+ softmax_scale=softmax_scale,
1212
+ cu_seqlens_q=torch.tensor([0, query_states.shape[0]],
1213
+ dtype=torch.int32,
1214
+ device=query_states.device),
1215
+ max_seqlen_q=query_states.shape[0],
1216
+ cu_seqlens_k=torch.tensor([0, max_seqlen_k],
1217
+ dtype=torch.int32,
1218
+ device=query_states.device),
1219
+ max_seqlen_k=max_seqlen_k,
1220
+ causal=causal,
1221
+ block_table=block_table.unsqueeze(0),
1222
+ return_softmax_lse=True,
1223
+ )
1224
+ softmax_lse = softmax_lse.view(q_len, q_heads, 1).transpose(0,
1225
+ 2).float()
1226
+ return output, softmax_lse
1227
+
1228
+ def _merge_attn_outputs(
1229
+ self,
1230
+ flash_results: List[List[Tuple[torch.Tensor, torch.Tensor]]],
1231
+ return_lse: Optional[bool] = False,
1232
+ ) -> torch.Tensor:
1233
+ attn_outputs_all = []
1234
+ logits_all = []
1235
+
1236
+ for flash_per_chunk in flash_results:
1237
+ if len(flash_per_chunk) == 1:
1238
+ attn_outputs_all.append(flash_per_chunk[0][0])
1239
+ if return_lse:
1240
+ logits_all.append(flash_per_chunk[0][1])
1241
+ continue
1242
+
1243
+ attn_outputs = torch.stack([
1244
+ flash_attn_output[0] for flash_attn_output in flash_per_chunk
1245
+ ])
1246
+ logits = torch.stack([
1247
+ flash_attn_output[1] for flash_attn_output in flash_per_chunk
1248
+ ])
1249
+ logits = logits.to(torch.float32)
1250
+
1251
+ if return_lse:
1252
+ max_val = torch.max(logits, dim=0).values
1253
+ diff = torch.abs(logits[0] - logits[1])
1254
+ log_sum_exp = max_val + torch.log1p(torch.exp(-diff))
1255
+ logits_all.append(log_sum_exp)
1256
+
1257
+ max_logits = torch.max(logits, dim=0).values
1258
+ stable_logits = logits - max_logits.unsqueeze(0)
1259
+ lse_s = torch.exp(stable_logits).detach()
1260
+ lse_sum = torch.sum(lse_s, dim=0)
1261
+ lse_s /= lse_sum
1262
+ attn_outputs *= lse_s.unsqueeze(-1).transpose(2, 3).squeeze(1)
1263
+ attn_outputs_all.append(attn_outputs.sum(dim=0))
1264
+
1265
+ if return_lse:
1266
+ return (torch.cat(attn_outputs_all,
1267
+ dim=0), torch.cat(logits_all, dim=-1))
1268
+ else:
1269
+ return torch.cat(attn_outputs_all, dim=0)
1270
+
1271
+ def _dual_chunk_flash_attn_decoding(
1272
+ self,
1273
+ query: torch.Tensor,
1274
+ query_succ: torch.Tensor,
1275
+ query_inter: torch.Tensor,
1276
+ key_cache: torch.Tensor,
1277
+ value_cache: torch.Tensor,
1278
+ block_table: torch.Tensor,
1279
+ cache_seqlens: torch.Tensor,
1280
+ softmax_scale: float,
1281
+ causal: bool,
1282
+ alibi_slopes: Optional[torch.Tensor],
1283
+ chunk_size: int,
1284
+ local_size: int,
1285
+ original_max_position_embeddings: int,
1286
+ decode_meta: DualChunkFlashAttentionMetadata,
1287
+ ):
1288
+ if not causal:
1289
+ raise ValueError(
1290
+ "Dual Chunk Attention does not support causal=False")
1291
+
1292
+ block_size = value_cache.shape[1]
1293
+ chunk_len = chunk_size - local_size
1294
+ if chunk_len % block_size != 0:
1295
+ raise ValueError("chunk_len must be divisible by block_size.")
1296
+ if original_max_position_embeddings > 0:
1297
+ assert decode_meta.scaling_factor is not None
1298
+ scaling_factor = decode_meta.scaling_factor
1299
+ query = (query * scaling_factor.view(-1, 1, 1, 1)).to(
1300
+ query.dtype
1301
+ ) # possible for numerical issue, need to fused in the kernel
1302
+ query_succ = (query_succ * scaling_factor.view(-1, 1, 1, 1)).to(
1303
+ query.dtype)
1304
+ query_inter = (query_inter * scaling_factor.view(-1, 1, 1, 1)).to(
1305
+ query.dtype)
1306
+ outputs_list = []
1307
+ softmax_lses_list = []
1308
+
1309
+ # intra-attention
1310
+ intra_output, intra_softmax_lse = (
1311
+ self._dual_chunk_flash_attn_decoding_with_exp_sums(
1312
+ query,
1313
+ key_cache,
1314
+ value_cache,
1315
+ decode_meta.block_tables_intra,
1316
+ decode_meta.seq_lens_intra,
1317
+ softmax_scale,
1318
+ alibi_slopes,
1319
+ causal=False,
1320
+ ))
1321
+ outputs_list.append(intra_output)
1322
+ softmax_lses_list.append(intra_softmax_lse)
1323
+
1324
+ # succ-attention
1325
+ if decode_meta.max_seq_len_succ:
1326
+ succ_output, succ_softmax_lse = (
1327
+ self._dual_chunk_flash_attn_decoding_with_exp_sums(
1328
+ query_succ,
1329
+ key_cache,
1330
+ value_cache,
1331
+ decode_meta.block_tables_succ,
1332
+ decode_meta.seq_lens_succ,
1333
+ softmax_scale,
1334
+ alibi_slopes,
1335
+ causal=False,
1336
+ ))
1337
+ outputs_list.append(succ_output)
1338
+ softmax_lses_list.append(succ_softmax_lse)
1339
+
1340
+ # inter-attention
1341
+ if decode_meta.max_seq_len_inter:
1342
+ inter_output, inter_softmax_lse = (
1343
+ self._dual_chunk_flash_attn_decoding_with_exp_sums(
1344
+ query_inter,
1345
+ key_cache,
1346
+ value_cache,
1347
+ block_table[:, :decode_meta.max_seq_len_inter],
1348
+ decode_meta.seq_lens_inter,
1349
+ softmax_scale,
1350
+ alibi_slopes,
1351
+ causal=False,
1352
+ ))
1353
+ outputs_list.append(inter_output)
1354
+ softmax_lses_list.append(inter_softmax_lse)
1355
+ outputs = torch.stack(outputs_list, dim=0)
1356
+ del outputs_list
1357
+ softmax_lses = torch.stack(softmax_lses_list, dim=0).to(torch.float32)
1358
+ del softmax_lses_list
1359
+ max_logits = torch.max(softmax_lses, dim=0).values
1360
+ stable_logits = softmax_lses - max_logits.unsqueeze(0)
1361
+ lse_s = torch.exp(stable_logits).detach()
1362
+ lse_sum = torch.sum(lse_s, dim=0)
1363
+ lse_s /= lse_sum
1364
+ outputs *= lse_s.unsqueeze(-1).transpose(2, 3)
1365
+ return outputs.sum(0)
1366
+
1367
+ def _dual_chunk_flash_attn_decoding_with_exp_sums(
1368
+ self,
1369
+ query: torch.Tensor,
1370
+ key_cache: torch.Tensor,
1371
+ value_cache: torch.Tensor,
1372
+ block_table: torch.Tensor,
1373
+ cache_seqlens: torch.Tensor,
1374
+ softmax_scale: float,
1375
+ alibi_slopes: Optional[torch.Tensor],
1376
+ causal: bool,
1377
+ ):
1378
+ out, softmax_lse = flash_attn_with_kvcache(
1379
+ q=query,
1380
+ k_cache=key_cache,
1381
+ v_cache=value_cache,
1382
+ block_table=block_table,
1383
+ cache_seqlens=cache_seqlens,
1384
+ softmax_scale=softmax_scale,
1385
+ alibi_slopes=alibi_slopes,
1386
+ causal=causal,
1387
+ return_softmax_lse=True,
1388
+ )
1389
+ mask = (cache_seqlens == 0)
1390
+ out[mask] = 0
1391
+ softmax_lse[mask] = -float("inf")
1392
+ return out, softmax_lse
1393
+
1394
+
1395
+def _vertical_slash_sparse_attention(
1396
+ query: torch.Tensor, # [BATCH, N_HEADS, N_CTX, D_HEAD]
1397
+ key: torch.Tensor, # [BATCH, N_HEADS, N_KV_CTX, D_HEAD]
1398
+ value: torch.Tensor, # [BATCH, N_HEADS, N_KV_CTX, D_HEAD]
1399
+ v_idx: torch.Tensor, # [BATCH, N_HEADS, NNZ_V]
1400
+ s_idx: torch.Tensor, # [BATCH, N_HEADS, NNZ_S]
1401
+ softmax_scale: float,
1402
+ causal: bool = True,
1403
+ stage: str = "intra",
1404
+ block_size_M: int = 64,
1405
+ block_size_N: int = 64,
1406
+ vertical_indices_count: torch.Tensor = None, # [N_HEADS,]
1407
+ slash_indices_count: torch.Tensor = None,
1408
+):
1409
+ if stage == "intra":
1410
+ assert causal
1411
+ else:
1412
+ assert not causal
1413
+
1414
+ batch_size, num_heads, context_size, head_dim = query.shape
1415
+ _, _, kv_seq_len, _ = key.shape
1416
+
1417
+ if head_dim not in [16, 32, 64, 128, 256, 512]:
1418
+ target_dim = 2**math.ceil(math.log2(head_dim)) - head_dim
1419
+ query = F.pad(query, [0, target_dim, 0, 0, 0, 0, 0, 0])
1420
+ key = F.pad(key, [0, target_dim, 0, 0, 0, 0, 0, 0])
1421
+ value = F.pad(value, [0, target_dim, 0, 0, 0, 0, 0, 0])
1422
+
1423
+ v_idx = v_idx.to(torch.int32).reshape(
1424
+ (batch_size, num_heads, -1)).sort(dim=-1, descending=False)[0]
1425
+ s_idx = s_idx.to(torch.int32).reshape(
1426
+ (batch_size, num_heads, -1)).sort(dim=-1, descending=True)[0]
1427
+ q_seqlens = torch.tensor([context_size],
1428
+ dtype=torch.int32,
1429
+ device=query.device)
1430
+ kv_seqlens = torch.tensor([kv_seq_len],
1431
+ dtype=torch.int32,
1432
+ device=query.device)
1433
+
1434
+ if vertical_indices_count is not None and slash_indices_count is not None:
1435
+ (
1436
+ block_count,
1437
+ block_offset,
1438
+ column_count,
1439
+ column_index,
1440
+ ) = ops.convert_vertical_slash_indexes_mergehead(
1441
+ q_seqlens, kv_seqlens, v_idx, s_idx, vertical_indices_count,
1442
+ slash_indices_count, context_size, block_size_M, block_size_N,
1443
+ causal)
1444
+ else:
1445
+ (
1446
+ block_count,
1447
+ block_offset,
1448
+ column_count,
1449
+ column_index,
1450
+ ) = ops.convert_vertical_slash_indexes(q_seqlens, kv_seqlens, v_idx,
1451
+ s_idx, context_size,
1452
+ block_size_M, block_size_N,
1453
+ causal)
1454
+
1455
+ q = query.transpose(1, 2).contiguous()
1456
+ k = key.transpose(1, 2).contiguous()
1457
+ v = value.transpose(1, 2).contiguous()
1458
+ out, lse = sparse_attn_func(
1459
+ q,
1460
+ k,
1461
+ v,
1462
+ block_count,
1463
+ block_offset,
1464
+ column_count,
1465
+ column_index,
1466
+ causal=causal,
1467
+ softmax_scale=softmax_scale,
1468
+ return_softmax_lse=True,
1469
+ )
1470
+ out = out.transpose(1, 2).contiguous()
1471
+ softmax_lse = lse.reshape(*lse.shape, 1)
1472
+ return (out[..., :context_size, :head_dim],
1473
+ softmax_lse[..., :context_size, :])
1474
+
1475
+
1476
+def _sum_all_diagonal_matrix(mat: torch.tensor):
1477
+ h, n, m = mat.shape
1478
+ # Zero matrix used for padding
1479
+ zero_mat = torch.zeros((h, n, n), device=mat.device)
1480
+ # pads the matrix on left and right
1481
+ mat_padded = torch.cat((zero_mat, mat, zero_mat), -1)
1482
+ # Change the strides
1483
+ mat_strided = mat_padded.as_strided((1, n, n + m),
1484
+ (n * (2 * n + m), 2 * n + m + 1, 1))
1485
+ # Sums the resulting matrix's columns
1486
+ sum_diags = torch.sum(mat_strided, 1)
1487
+ return sum_diags[:, 1:] # drop left bottom corner
1488
+
1489
+
1490
+def _get_block(block_table: torch.Tensor, block_size: int, begin: int,
1491
+ end: int):
1492
+ begin_block = begin // block_size
1493
+ end_block = (end - 1) // block_size + 1
1494
+ return block_table[begin_block:end_block]
vllm/config.py
+19
@@ -929,6 +929,23 @@ class ModelConfig:
929
"Number of experts in the model must be greater than 0 "
930
"when expert parallelism is enabled.")
931
932
+ def verify_dual_chunk_attention_config(
933
+ self,
934
+ load_config: "LoadConfig",
935
+ ) -> None:
936
+ if hasattr(self.hf_config, "dual_chunk_attention_config"):
937
+ # Try loading the sparse attention config
938
+ from vllm.model_executor.model_loader.weight_utils import (
939
+ get_sparse_attention_config)
940
+ sparse_attn_config = get_sparse_attention_config(self, load_config)
941
+ if sparse_attn_config:
942
+ self.hf_config.dual_chunk_attention_config[
943
+ "sparse_attention_config"] = sparse_attn_config
944
+ if "sparse_attention_enabled" not in \
945
+ self.hf_config.dual_chunk_attention_config:
946
+ self.hf_config.dual_chunk_attention_config[
947
+ "sparse_attention_enabled"] = True
948
+
949
def verify_async_output_proc(self, parallel_config, speculative_config,
950
device_config) -> None:
951
if not self.use_async_output_proc:
@@ -4187,6 +4204,8 @@ class VllmConfig:
4204
self.speculative_config,
4205
self.device_config)
4206
self.model_config.verify_with_parallel_config(self.parallel_config)
4207
+ self.model_config.verify_dual_chunk_attention_config(
4208
+ self.load_config)
4209
4210
if self.cache_config is not None:
4211
self.cache_config.verify_with_parallel_config(self.parallel_config)
vllm/engine/arg_utils.py
+13
-2
@@ -37,8 +37,8 @@ from vllm.reasoning import ReasoningParserManager
37
from vllm.test_utils import MODEL_WEIGHTS_S3_BUCKET, MODELS_ON_S3
38
from vllm.transformers_utils.utils import check_gguf_file
39
from vllm.usage.usage_lib import UsageContext
40
-from vllm.utils import (FlexibleArgumentParser, GiB_bytes, is_in_doc_build,
41
- is_in_ray_actor)
40
+from vllm.utils import (STR_DUAL_CHUNK_FLASH_ATTN_VAL, FlexibleArgumentParser,
41
+ GiB_bytes, is_in_doc_build, is_in_ray_actor)
42
43
# yapf: enable
44
@@ -983,6 +983,17 @@ class EngineArgs:
983
984
assert self.enable_chunked_prefill is not None
985
986
+ if envs.VLLM_ATTENTION_BACKEND in [STR_DUAL_CHUNK_FLASH_ATTN_VAL]:
987
+ assert self.enforce_eager, (
988
+ "Cuda graph is not supported with DualChunkFlashAttention. "
989
+ "To run the model in eager mode, set 'enforce_eager=True' "
990
+ "or use '--enforce-eager' in the CLI.")
991
+ assert current_platform.is_cuda(), (
992
+ "DualChunkFlashAttention is only supported on CUDA platform.")
993
+ assert not use_v1, (
994
+ "DualChunkFlashAttention is not supported on V1 engine. "
995
+ "To run the model in V0 engine, try set 'VLLM_USE_V1=0'")
996
+
997
cache_config = CacheConfig(
998
block_size=self.block_size,
999
gpu_memory_utilization=self.gpu_memory_utilization,
vllm/model_executor/layers/rotary_embedding.py
+202
-2
@@ -1486,6 +1486,184 @@ class MRotaryEmbedding(RotaryEmbedding):
1486
return updates
1487
1488
1489
+@CustomOp.register("dual_chunk_rotary_embedding")
1490
+class DualChunkRotaryEmbedding(CustomOp):
1491
+ """Rotary positional embedding for Dual Chunk Attention."""
1492
+
1493
+ def __init__(
1494
+ self,
1495
+ head_size: int,
1496
+ rotary_dim: int,
1497
+ max_position_embeddings: int,
1498
+ base: int,
1499
+ is_neox_style: bool,
1500
+ dtype: torch.dtype,
1501
+ chunk_size: int,
1502
+ local_size: int,
1503
+ ) -> None:
1504
+ super().__init__()
1505
+ self.head_size = head_size
1506
+ self.rotary_dim = rotary_dim
1507
+ self.max_position_embeddings = max_position_embeddings
1508
+ self.base = base
1509
+ self.is_neox_style = is_neox_style
1510
+ self.chunk_size = chunk_size
1511
+ self.local_size = local_size
1512
+ self.dtype = dtype
1513
+ self.device = torch.device(f"cuda:{torch.cuda.current_device()}")
1514
+ (q_cache, qc_cache, k_cache, qc_no_clamp_cache,
1515
+ q_inter_cache) = self._compute_cos_sin_cache()
1516
+
1517
+ self.register_buffer("cos_sin_q_cache", q_cache, persistent=False)
1518
+ self.register_buffer("cos_sin_qc_cache", qc_cache, persistent=False)
1519
+ self.register_buffer("cos_sin_k_cache", k_cache, persistent=False)
1520
+ self.register_buffer("cos_sin_qc_no_clamp_cache",
1521
+ qc_no_clamp_cache,
1522
+ persistent=False)
1523
+ self.register_buffer("cos_sin_q_inter_cache",
1524
+ q_inter_cache,
1525
+ persistent=False)
1526
+
1527
+ def _compute_inv_freq(self, base: Union[int, float]) -> torch.Tensor:
1528
+ """Compute the inverse frequency."""
1529
+ # NOTE(woosuk): The HF implementation uses `torch.arange(...).float()`.
1530
+ # However, we use `torch.arange(..., dtype=torch.float)` instead to
1531
+ # avoid numerical issues with large base values (e.g., 10000000).
1532
+ # This may cause a slight numerical difference between the HF
1533
+ # implementation and ours.
1534
+ # NOTE(woosuk): To exactly match the HF implementation, we need to
1535
+ # use CPU to compute the cache and then move it to GPU. However, we
1536
+ # create the cache on GPU for faster initialization. This may cause
1537
+ # a slight numerical difference between the HF implementation and ours.
1538
+ inv_freq = 1.0 / (base**(torch.arange(
1539
+ 0, self.rotary_dim, 2, dtype=torch.float) / self.rotary_dim))
1540
+ return inv_freq
1541
+
1542
+ def _compute_cos_sin_cache(self) -> torch.Tensor:
1543
+ """Compute the cos and sin cache."""
1544
+ inv_freq = self._compute_inv_freq(self.base)
1545
+ chunk_len = self.chunk_size - self.local_size
1546
+ q_t = torch.arange(chunk_len, dtype=torch.float)
1547
+ qc_t = (torch.arange(chunk_len, dtype=torch.float) +
1548
+ chunk_len).clamp(max=self.chunk_size)
1549
+ k_t = torch.arange(self.max_position_embeddings,
1550
+ dtype=torch.float) % chunk_len
1551
+
1552
+ # count from chunk_len, no clamp(self.chunk_size) restriction
1553
+ qc_no_clamp_t = torch.arange(chunk_len, dtype=torch.float) + chunk_len
1554
+ # count from self.chunk_size for q_inter's rope
1555
+ q_inter_t = torch.arange(chunk_len,
1556
+ dtype=torch.float) + self.chunk_size
1557
+
1558
+ q_freqs = torch.outer(q_t, inv_freq)
1559
+ qc_freqs = torch.outer(qc_t, inv_freq)
1560
+ k_freqs = torch.outer(k_t, inv_freq)
1561
+ qc_no_clamp_freqs = torch.outer(qc_no_clamp_t, inv_freq)
1562
+ q_inter_freqs = torch.outer(q_inter_t, inv_freq)
1563
+
1564
+ q_cos = q_freqs.cos()
1565
+ q_sin = q_freqs.sin()
1566
+ qc_cos = qc_freqs.cos()
1567
+ qc_sin = qc_freqs.sin()
1568
+ k_cos = k_freqs.cos()
1569
+ k_sin = k_freqs.sin()
1570
+
1571
+ qc_no_clamp_cos = qc_no_clamp_freqs.cos()
1572
+ qc_no_clamp_sin = qc_no_clamp_freqs.sin()
1573
+ q_inter_cos = q_inter_freqs.cos()
1574
+ q_inter_sin = q_inter_freqs.sin()
1575
+
1576
+ q_cache = torch.cat((q_cos, q_sin), dim=-1).to(dtype=self.dtype,
1577
+ device=self.device)
1578
+ qc_cache = torch.cat((qc_cos, qc_sin), dim=-1).to(dtype=self.dtype,
1579
+ device=self.device)
1580
+ k_cache = torch.cat((k_cos, k_sin), dim=-1).to(dtype=self.dtype,
1581
+ device=self.device)
1582
+ qc_no_clamp_cache = torch.cat((qc_no_clamp_cos, qc_no_clamp_sin),
1583
+ dim=-1).to(dtype=self.dtype,
1584
+ device=self.device)
1585
+ q_inter_cache = torch.cat((q_inter_cos, q_inter_sin),
1586
+ dim=-1).to(dtype=self.dtype,
1587
+ device=self.device)
1588
+ return q_cache, qc_cache, k_cache, qc_no_clamp_cache, q_inter_cache
1589
+
1590
+ def forward(
1591
+ self,
1592
+ positions: torch.Tensor,
1593
+ query: torch.Tensor,
1594
+ key: torch.Tensor,
1595
+ offsets: Optional[torch.Tensor] = None,
1596
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
1597
+ query = query.view(*query.shape[:-1], -1, self.head_size)
1598
+ key = key.view(*key.shape[:-1], -1, self.head_size)
1599
+ query_rot = query[..., :self.rotary_dim]
1600
+ key_rot = key[..., :self.rotary_dim]
1601
+ if self.rotary_dim < self.head_size:
1602
+ query_pass = query[..., self.rotary_dim:]
1603
+ key_pass = key[..., self.rotary_dim:]
1604
+ else:
1605
+ query_pass = None
1606
+ key_pass = None
1607
+
1608
+ positions_with_offsets = (torch.add(positions, offsets)
1609
+ if offsets is not None else positions)
1610
+ key = self._apply_rotary_embedding(
1611
+ self.cos_sin_k_cache[positions_with_offsets], key_rot, key_pass)
1612
+ chunk_len = self.chunk_size - self.local_size
1613
+ query = self._apply_rotary_embedding(
1614
+ self.cos_sin_q_cache[positions_with_offsets % chunk_len],
1615
+ query_rot, query_pass)
1616
+ query_succ = self._apply_rotary_embedding(
1617
+ self.cos_sin_qc_cache[positions_with_offsets % chunk_len],
1618
+ query_rot, query_pass)
1619
+ query_inter = self._apply_rotary_embedding(
1620
+ self.cos_sin_qc_cache[chunk_len - 1].repeat(positions.shape[0], 1),
1621
+ query_rot, query_pass)
1622
+ query_succ_critical = self._apply_rotary_embedding(
1623
+ self.cos_sin_qc_no_clamp_cache[positions_with_offsets % chunk_len],
1624
+ query_rot, query_pass)
1625
+ query_inter_critical = self._apply_rotary_embedding(
1626
+ self.cos_sin_q_inter_cache[positions_with_offsets % chunk_len],
1627
+ query_rot, query_pass)
1628
+
1629
+ # merge query into one tensor to simplify the interfaces
1630
+ query = torch.cat((
1631
+ query,
1632
+ query_succ,
1633
+ query_inter,
1634
+ query_succ_critical,
1635
+ query_inter_critical,
1636
+ ),
1637
+ dim=-1)
1638
+ return query, key
1639
+
1640
+ def _apply_rotary_embedding(self, cos_sin, hidden_rot, hidden_pass):
1641
+ cos, sin = cos_sin.chunk(2, dim=-1)
1642
+ if self.is_neox_style:
1643
+ # NOTE(woosuk): Here we assume that the positions tensor has the
1644
+ # shape [batch_size, seq_len].
1645
+ cos = cos.repeat(1, 1, 2).unsqueeze(-2)
1646
+ sin = sin.repeat(1, 1, 2).unsqueeze(-2)
1647
+ else:
1648
+ cos = cos.repeat_interleave(2, dim=-1).unsqueeze(-2)
1649
+ sin = sin.repeat_interleave(2, dim=-1).unsqueeze(-2)
1650
+ rotate_fn = _rotate_neox if self.is_neox_style else _rotate_gptj
1651
+ hidden_rot = hidden_rot * cos + rotate_fn(hidden_rot) * sin
1652
+
1653
+ if self.rotary_dim < self.head_size:
1654
+ hidden = torch.cat((hidden_rot, hidden_pass), dim=-1)
1655
+ else:
1656
+ hidden = hidden_rot
1657
+ return hidden.flatten(-2).squeeze(0)
1658
+
1659
+ def extra_repr(self) -> str:
1660
+ s = f"head_size={self.head_size}, rotary_dim={self.rotary_dim}"
1661
+ s += f", max_position_embeddings={self.max_position_embeddings}"
1662
+ s += f", base={self.base}, is_neox_style={self.is_neox_style}"
1663
+ s += f", chunk_size={self.chunk_size}, local_size={self.local_size}"
1664
+ return s
1665
+
1666
+
1667
_ROPE_DICT: Dict[Tuple, RotaryEmbedding] = {}
1668
1669
@@ -1498,6 +1676,7 @@ def get_rope(
1676
rope_scaling: Optional[Dict[str, Any]] = None,
1677
dtype: Optional[torch.dtype] = None,
1678
partial_rotary_factor: float = 1.0,
1679
+ dual_chunk_attention_config: Optional[Dict[str, Any]] = None,
1680
) -> RotaryEmbedding:
1681
if dtype is None:
1682
dtype = torch.get_default_dtype()
@@ -1510,14 +1689,35 @@ def get_rope(
1689
rope_scaling_args = tuple(rope_scaling_tuple.items())
1690
else:
1691
rope_scaling_args = None
1692
+
1693
+ if dual_chunk_attention_config is not None:
1694
+ dual_chunk_attention_tuple = {
1695
+ k: tuple(v) if isinstance(v, list) else v
1696
+ for k, v in dual_chunk_attention_config.items()
1697
+ if k != "sparse_attention_config"
1698
+ }
1699
+ dual_chunk_attention_args = tuple(dual_chunk_attention_tuple.items())
1700
+ else:
1701
+ dual_chunk_attention_args = None
1702
+
1703
if partial_rotary_factor < 1.0:
1704
rotary_dim = int(rotary_dim * partial_rotary_factor)
1705
key = (head_size, rotary_dim, max_position, base, is_neox_style,
1516
- rope_scaling_args, dtype)
1706
+ rope_scaling_args, dual_chunk_attention_args, dtype)
1707
if key in _ROPE_DICT:
1708
return _ROPE_DICT[key]
1709
1520
- if not rope_scaling:
1710
+ if dual_chunk_attention_config is not None:
1711
+ extra_kwargs = {
1712
+ k: v
1713
+ for k, v in dual_chunk_attention_config.items()
1714
+ if k in ("chunk_size", "local_size")
1715
+ }
1716
+ rotary_emb = DualChunkRotaryEmbedding(head_size, rotary_dim,
1717
+ max_position, base,
1718
+ is_neox_style, dtype,
1719
+ **extra_kwargs)
1720
+ elif not rope_scaling:
1721
rotary_emb = RotaryEmbedding(head_size, rotary_dim, max_position, base,
1722
is_neox_style, dtype)
1723
else:
vllm/model_executor/model_loader/weight_utils.py
+33
@@ -217,6 +217,39 @@ def get_quant_config(model_config: ModelConfig,
217
return quant_cls.from_config(config)
218
219
220
+def get_sparse_attention_config(
221
+ model_config: ModelConfig,
222
+ load_config: LoadConfig,
223
+ sparse_attention_config_filename: str = "sparse_attention_config.json",
224
+) -> Dict[str, Any]:
225
+ model_name_or_path = model_config.model
226
+ is_local = os.path.isdir(model_name_or_path)
227
+ if not is_local:
228
+ # Download the config files.
229
+ with get_lock(model_name_or_path, load_config.download_dir):
230
+ hf_folder = snapshot_download(
231
+ model_name_or_path,
232
+ revision=model_config.revision,
233
+ allow_patterns="*.json",
234
+ cache_dir=load_config.download_dir,
235
+ local_files_only=huggingface_hub.constants.HF_HUB_OFFLINE,
236
+ tqdm_class=DisabledTqdm,
237
+ )
238
+ else:
239
+ hf_folder = model_name_or_path
240
+
241
+ config_file = os.path.join(hf_folder, sparse_attention_config_filename)
242
+ if not os.path.exists(config_file):
243
+ return {}
244
+
245
+ # Load the sparse attention config.
246
+ with open(config_file) as f:
247
+ config = json.load(f)
248
+ logger.info("Loaded sparse attention config from %s", config_file)
249
+
250
+ return config
251
+
252
+
253
def download_weights_from_hf(
254
model_name_or_path: str,
255
cache_dir: Optional[str],
vllm/model_executor/models/qwen2.py
+35
-21
@@ -23,7 +23,7 @@
23
# See the License for the specific language governing permissions and
24
# limitations under the License.
25
"""Inference-only Qwen2 model compatible with HuggingFace weights."""
26
-from typing import Iterable, Optional, Set, Tuple, Union
26
+from typing import Any, Iterable, Optional, Set, Tuple, Union
27
28
import torch
29
from torch import nn
@@ -53,7 +53,7 @@ from vllm.sequence import IntermediateTensors, PoolerOutput
53
54
from .interfaces import SupportsLoRA, SupportsPP
55
from .utils import (AutoWeightsLoader, PPMissingLayer, WeightsMapper,
56
- is_pp_missing_parameter,
56
+ extract_layer_index, is_pp_missing_parameter,
57
make_empty_intermediate_tensors_factory, make_layers,
58
maybe_prefix)
59
@@ -99,17 +99,20 @@ class Qwen2MLP(nn.Module):
99
100
class Qwen2Attention(nn.Module):
101
102
- def __init__(self,
103
- hidden_size: int,
104
- num_heads: int,
105
- num_kv_heads: int,
106
- max_position: int = 4096 * 32,
107
- rope_theta: float = 10000,
108
- cache_config: Optional[CacheConfig] = None,
109
- quant_config: Optional[QuantizationConfig] = None,
110
- rope_scaling: Optional[Tuple] = None,
111
- prefix: str = "",
112
- attn_type: str = AttentionType.DECODER) -> None:
102
+ def __init__(
103
+ self,
104
+ hidden_size: int,
105
+ num_heads: int,
106
+ num_kv_heads: int,
107
+ max_position: int = 4096 * 32,
108
+ rope_theta: float = 10000,
109
+ cache_config: Optional[CacheConfig] = None,
110
+ quant_config: Optional[QuantizationConfig] = None,
111
+ rope_scaling: Optional[Tuple] = None,
112
+ prefix: str = "",
113
+ attn_type: str = AttentionType.DECODER,
114
+ dual_chunk_attention_config: Optional[dict[str,
115
+ Any]] = None) -> None:
116
super().__init__()
117
self.hidden_size = hidden_size
118
tp_size = get_tensor_model_parallel_world_size()
@@ -131,6 +134,7 @@ class Qwen2Attention(nn.Module):
134
self.kv_size = self.num_kv_heads * self.head_dim
135
self.scaling = self.head_dim**-0.5
136
self.rope_theta = rope_theta
137
+ self.dual_chunk_attention_config = dual_chunk_attention_config
138
139
self.qkv_proj = QKVParallelLinear(
140
hidden_size,
@@ -155,15 +159,21 @@ class Qwen2Attention(nn.Module):
159
max_position=max_position,
160
base=self.rope_theta,
161
rope_scaling=rope_scaling,
162
+ dual_chunk_attention_config=dual_chunk_attention_config,
163
)
159
- self.attn = Attention(self.num_heads,
160
- self.head_dim,
161
- self.scaling,
162
- num_kv_heads=self.num_kv_heads,
163
- cache_config=cache_config,
164
- quant_config=quant_config,
165
- prefix=f"{prefix}.attn",
166
- attn_type=attn_type)
164
+ self.attn = Attention(
165
+ self.num_heads,
166
+ self.head_dim,
167
+ self.scaling,
168
+ num_kv_heads=self.num_kv_heads,
169
+ cache_config=cache_config,
170
+ quant_config=quant_config,
171
+ attn_type=attn_type,
172
+ prefix=f"{prefix}.attn",
173
+ **{
174
+ "layer_idx": extract_layer_index(prefix),
175
+ "dual_chunk_attention_config": dual_chunk_attention_config,
176
+ } if dual_chunk_attention_config else {})
177
178
def forward(
179
self,
@@ -192,6 +202,9 @@ class Qwen2DecoderLayer(nn.Module):
202
# Requires transformers > 4.32.0
203
rope_theta = getattr(config, "rope_theta", 1000000)
204
rope_scaling = getattr(config, "rope_scaling", None)
205
+ dual_chunk_attention_config = getattr(config,
206
+ "dual_chunk_attention_config",
207
+ None)
208
209
# By default, Qwen2 uses causal attention as it is a decoder-only model.
210
# You can override the HF config with `is_causal=False` to enable
@@ -213,6 +226,7 @@ class Qwen2DecoderLayer(nn.Module):
226
rope_scaling=rope_scaling,
227
prefix=f"{prefix}.self_attn",
228
attn_type=attn_type,
229
+ dual_chunk_attention_config=dual_chunk_attention_config,
230
)
231
self.mlp = Qwen2MLP(
232
hidden_size=self.hidden_size,
vllm/model_executor/models/qwen2_moe.py
+19
-7
@@ -175,6 +175,7 @@ class Qwen2MoeAttention(nn.Module):
175
cache_config: Optional[CacheConfig] = None,
176
quant_config: Optional[QuantizationConfig] = None,
177
prefix: str = "",
178
+ dual_chunk_attention_config: Optional[Dict[str, Any]] = None,
179
) -> None:
180
super().__init__()
181
self.hidden_size = hidden_size
@@ -198,6 +199,7 @@ class Qwen2MoeAttention(nn.Module):
199
self.scaling = self.head_dim**-0.5
200
self.rope_theta = rope_theta
201
self.max_position_embeddings = max_position_embeddings
202
+ self.dual_chunk_attention_config = dual_chunk_attention_config
203
204
self.qkv_proj = QKVParallelLinear(
205
hidden_size,
@@ -221,14 +223,20 @@ class Qwen2MoeAttention(nn.Module):
223
max_position=max_position_embeddings,
224
base=rope_theta,
225
rope_scaling=rope_scaling,
226
+ dual_chunk_attention_config=dual_chunk_attention_config,
227
)
225
- self.attn = Attention(self.num_heads,
226
- self.head_dim,
227
- self.scaling,
228
- num_kv_heads=self.num_kv_heads,
229
- cache_config=cache_config,
230
- quant_config=quant_config,
231
- prefix=f"{prefix}.attn")
228
+ self.attn = Attention(
229
+ self.num_heads,
230
+ self.head_dim,
231
+ self.scaling,
232
+ num_kv_heads=self.num_kv_heads,
233
+ cache_config=cache_config,
234
+ quant_config=quant_config,
235
+ prefix=f"{prefix}.attn",
236
+ **{
237
+ "layer_idx": extract_layer_index(prefix),
238
+ "dual_chunk_attention_config": dual_chunk_attention_config,
239
+ } if dual_chunk_attention_config else {})
240
241
def forward(
242
self,
@@ -256,6 +264,9 @@ class Qwen2MoeDecoderLayer(nn.Module):
264
self.hidden_size = config.hidden_size
265
rope_theta = getattr(config, "rope_theta", 10000)
266
rope_scaling = getattr(config, "rope_scaling", None)
267
+ dual_chunk_attention_config = getattr(config,
268
+ "dual_chunk_attention_config",
269
+ None)
270
max_position_embeddings = getattr(config, "max_position_embeddings",
271
8192)
272
self.self_attn = Qwen2MoeAttention(
@@ -268,6 +279,7 @@ class Qwen2MoeDecoderLayer(nn.Module):
279
cache_config=cache_config,
280
quant_config=quant_config,
281
prefix=f"{prefix}.self_attn",
282
+ dual_chunk_attention_config=dual_chunk_attention_config,
283
)
284
285
# Note: Qwen/Qwen2-57B-A14B-Instruct does not have
vllm/platforms/cuda.py
+4
@@ -222,6 +222,10 @@ class CudaPlatformBase(Platform):
222
elif selected_backend == _Backend.XFORMERS:
223
logger.info("Using XFormers backend.")
224
return "vllm.attention.backends.xformers.XFormersBackend"
225
+ elif selected_backend == _Backend.DUAL_CHUNK_FLASH_ATTN:
226
+ logger.info("Using DualChunkFlashAttention backend.")
227
+ return ("vllm.attention.backends.dual_chunk_flash_attn."
228
+ "DualChunkFlashAttentionBackend")
229
elif selected_backend == _Backend.FLASH_ATTN:
230
pass
231
elif selected_backend:
vllm/platforms/interface.py
+1
@@ -51,6 +51,7 @@ class _Backend(enum.Enum):
51
PALLAS_VLLM_V1 = enum.auto()
52
IPEX = enum.auto()
53
BLOCK_SPARSE_FLASH_ATTN = enum.auto()
54
+ DUAL_CHUNK_FLASH_ATTN = enum.auto()
55
NO_ATTENTION = enum.auto()
56
57
vllm/utils.py
+1
@@ -153,6 +153,7 @@ STR_TORCH_SDPA_ATTN_VAL: str = "TORCH_SDPA"
153
STR_ROCM_FLASH_ATTN_VAL: str = "ROCM_FLASH"
154
STR_XFORMERS_ATTN_VAL: str = "XFORMERS"
155
STR_FLASH_ATTN_VAL: str = "FLASH_ATTN"
156
+STR_DUAL_CHUNK_FLASH_ATTN_VAL: str = "DUAL_CHUNK_FLASH_ATTN"
157
STR_INVALID_VAL: str = "INVALID"
158
159
GB_bytes = 1_000_000_000
vllm/worker/model_runner.py
+12
@@ -204,6 +204,7 @@ class ModelInputForGPUBuilder(ModelRunnerInputBuilderBase[ModelInputForGPU]):
204
self.mrope_input_positions = None # type: ignore
205
self.seq_lens[0] = 0 # type: ignore
206
self.orig_seq_lens[0] = 0 # type: ignore
207
+ self.prompt_lens[0] = 0 # type: ignore
208
self.query_lens[0] = 0 # type: ignore
209
self.context_lens[0] = 0 # type: ignore
210
self.curr_sliding_window_blocks[0] = 0 # type: ignore
@@ -236,6 +237,8 @@ class ModelInputForGPUBuilder(ModelRunnerInputBuilderBase[ModelInputForGPU]):
237
# The original sequence length (before applying sliding window).
238
# This is used to compute slot mapping.
239
orig_seq_lens: Optional[List[int]] = None,
240
+ # This is used in the dual-chunk flash attention backend.
241
+ prompt_lens: Optional[List[int]] = None,
242
# The query length.
243
query_lens: Optional[List[int]] = None,
244
# The number of tokens that are already computed.
@@ -316,6 +319,12 @@ class ModelInputForGPUBuilder(ModelRunnerInputBuilderBase[ModelInputForGPU]):
319
for seq_id in range(len(self.seq_ids)):
320
self.orig_seq_lens[seq_id] = 0
321
322
+ if prompt_lens:
323
+ self.prompt_lens = prompt_lens
324
+ else:
325
+ for seq_id in range(len(self.seq_ids)):
326
+ self.prompt_lens[seq_id] = 0
327
+
328
if query_lens:
329
self.query_lens = query_lens
330
else:
@@ -370,6 +379,7 @@ class ModelInputForGPUBuilder(ModelRunnerInputBuilderBase[ModelInputForGPU]):
379
self.mrope_input_positions = mrope_input_positions or None
380
self.seq_lens = seq_lens or []
381
self.orig_seq_lens = orig_seq_lens or []
382
+ self.prompt_lens = prompt_lens or []
383
self.query_lens = query_lens or []
384
self.context_lens = context_lens or []
385
self.curr_sliding_window_blocks = \
@@ -403,6 +413,7 @@ class ModelInputForGPUBuilder(ModelRunnerInputBuilderBase[ModelInputForGPU]):
413
self.mrope_input_positions = None
414
self.seq_lens = [0] * self.n_seqs
415
self.orig_seq_lens = [0] * self.n_seqs
416
+ self.prompt_lens = [0] * self.n_seqs
417
self.query_lens = [0] * self.n_seqs
418
self.context_lens = [0] * self.n_seqs
419
self.curr_sliding_window_blocks = [0] * self.n_seqs
@@ -552,6 +563,7 @@ class ModelInputForGPUBuilder(ModelRunnerInputBuilderBase[ModelInputForGPU]):
563
564
inter_data.seq_lens[seq_idx] = seq_len
565
inter_data.orig_seq_lens[seq_idx] = seq_len
566
+ inter_data.prompt_lens[seq_idx] = seq_data.get_prompt_len()
567
inter_data.context_lens[seq_idx] = context_len
568
inter_data.input_tokens[seq_idx].extend(tokens)
569
inter_data.inputs_embeds = prompt_embeds