Skip to content

Commit 1935ab4

Browse files
author
Awni Hannun
authored
Faster two pass sdpa (#3023)
1 parent 617fd9c commit 1935ab4

2 files changed

Lines changed: 123 additions & 104 deletions

File tree

mlx/backend/metal/kernels/sdpa_vector.h

Lines changed: 68 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ constant bool do_causal [[function_constant(22)]];
1010
constant bool bool_mask [[function_constant(23)]];
1111
constant bool float_mask [[function_constant(24)]];
1212
constant bool has_sinks [[function_constant(25)]];
13+
constant int blocks [[function_constant(26)]];
1314

1415
template <typename T, int D, int V = D>
1516
[[kernel]] void sdpa_vector(
@@ -180,10 +181,9 @@ template <typename T, int D, int V = D>
180181
const device T* queries [[buffer(0)]],
181182
const device T* keys [[buffer(1)]],
182183
const device T* values [[buffer(2)]],
183-
device float* out [[buffer(3)]],
184+
device T* out [[buffer(3)]],
184185
device float* sums [[buffer(4)]],
185186
device float* maxs [[buffer(5)]],
186-
const constant int& gqa_factor [[buffer(6)]],
187187
const constant int& N [[buffer(7)]],
188188
const constant size_t& k_head_stride [[buffer(8)]],
189189
const constant size_t& k_seq_stride [[buffer(9)]],
@@ -199,94 +199,81 @@ template <typename T, int D, int V = D>
199199
const constant int& mask_head_stride
200200
[[buffer(17), function_constant(has_mask)]],
201201
const device T* sinks [[buffer(18), function_constant(has_sinks)]],
202-
const constant int& num_q_heads
203-
[[buffer(19), function_constant(has_sinks)]],
202+
uint3 tptg [[threads_per_threadgroup]],
203+
uint3 tidtg [[thread_position_in_threadgroup]],
204204
uint3 tid [[threadgroup_position_in_grid]],
205205
uint3 tpg [[threadgroups_per_grid]],
206-
uint simd_gid [[simdgroup_index_in_threadgroup]],
207206
uint simd_lid [[thread_index_in_simdgroup]]) {
208-
constexpr int BN = 8;
209207
constexpr int BD = 32;
210208
constexpr int qk_per_thread = D / BD;
211209
constexpr int v_per_thread = V / BD;
212-
int inner_k_stride = BN * int(k_seq_stride);
213-
int inner_v_stride = BN * int(v_seq_stride);
214-
constexpr int blocks = 32;
215210

216211
typedef float U;
217212

218213
thread U q[qk_per_thread];
219-
thread U k[qk_per_thread];
220-
thread U o[v_per_thread];
221-
222-
threadgroup U outputs[BN * BD];
223-
threadgroup U max_scores[BN];
224-
threadgroup U sum_exp_scores[BN];
214+
thread U o[v_per_thread] = {0};
225215

226216
// Adjust positions
217+
const int kv_head_idx = tid.x;
218+
const int batch_idx = tid.y;
227219
const int block_idx = tid.z;
228-
const int q_batch_head_idx = tid.x;
229-
const int q_seq_idx = tid.y;
230-
const int o_offset = q_batch_head_idx * tpg.y + q_seq_idx;
220+
const int gqa_factor = tptg.y;
221+
const int q_seq_len = tptg.z;
222+
const int q_seq_idx = tidtg.z;
223+
const int q_head_idx = gqa_factor * kv_head_idx + tidtg.y;
224+
const int num_kv_heads = tpg.x;
225+
const int num_q_heads = num_kv_heads * gqa_factor;
226+
const int q_batch_head_idx = (batch_idx * num_q_heads + q_head_idx);
227+
const int o_offset = q_batch_head_idx * q_seq_len + q_seq_idx;
231228
const int q_offset =
232-
query_transposed ? tpg.x * q_seq_idx + q_batch_head_idx : o_offset;
233-
const int kv_head_idx = q_batch_head_idx / gqa_factor;
229+
query_transposed ? num_q_heads * q_seq_idx + q_batch_head_idx : o_offset;
234230

235231
queries += q_offset * D + simd_lid * qk_per_thread;
236-
keys += kv_head_idx * k_head_stride +
237-
(block_idx * BN + simd_gid) * k_seq_stride + simd_lid * qk_per_thread;
238-
values += kv_head_idx * v_head_stride +
239-
(block_idx * BN + simd_gid) * v_seq_stride + simd_lid * v_per_thread;
232+
233+
const int kv_batch_head_idx = batch_idx * num_kv_heads + kv_head_idx;
234+
keys += kv_batch_head_idx * k_head_stride + block_idx * k_seq_stride +
235+
simd_lid * qk_per_thread;
236+
values += kv_batch_head_idx * v_head_stride + block_idx * v_seq_stride +
237+
simd_lid * v_per_thread;
240238
out += o_offset * blocks * V + block_idx * V + simd_lid * v_per_thread;
241239
if (bool_mask) {
242240
bmask += q_batch_head_idx * mask_head_stride +
243-
(block_idx * BN + simd_gid) * mask_kv_seq_stride +
244-
q_seq_idx * mask_q_seq_stride;
241+
block_idx * mask_kv_seq_stride + q_seq_idx * mask_q_seq_stride;
245242
}
246243
if (float_mask) {
247244
fmask += q_batch_head_idx * mask_head_stride +
248-
(block_idx * BN + simd_gid) * mask_kv_seq_stride +
249-
q_seq_idx * mask_q_seq_stride;
245+
block_idx * mask_kv_seq_stride + q_seq_idx * mask_q_seq_stride;
250246
}
251247
sums += o_offset * blocks + block_idx;
252248
maxs += o_offset * blocks + block_idx;
253249

254-
// Read the query and 0 the output accumulator
250+
// Read the query
255251
for (int i = 0; i < qk_per_thread; i++) {
256252
q[i] = static_cast<U>(scale) * queries[i];
257253
}
258-
for (int i = 0; i < v_per_thread; i++) {
259-
o[i] = 0;
260-
}
261254

262255
U max_score = Limits<U>::finite_min;
263256
U sum_exp_score = 0;
264-
if (has_sinks && block_idx == 0 && simd_gid == 0) {
265-
int q_head_idx = q_batch_head_idx % num_q_heads;
257+
if (has_sinks && block_idx == 0) {
266258
max_score = static_cast<U>(sinks[q_head_idx]);
267259
sum_exp_score = 1;
268260
}
269261

270262
// For each key
271-
for (int i = block_idx * BN + simd_gid; i < N; i += blocks * BN) {
263+
for (int i = block_idx; i < N; i += blocks) {
272264
bool use_key = true;
273265
if (do_causal) {
274-
use_key = i <= (N - int(tpg.y) + int(q_seq_idx));
266+
use_key = i <= (N - q_seq_len + int(q_seq_idx));
275267
} else if (bool_mask) {
276268
use_key = bmask[0];
277269
} else if (float_mask) {
278270
use_key = (fmask[0] >= Limits<T>::finite_min);
279271
}
280272
if (use_key) {
281-
// Read the key
282-
for (int i = 0; i < qk_per_thread; i++) {
283-
k[i] = keys[i];
284-
}
285-
286273
// Compute the i-th score
287274
U score = 0;
288275
for (int i = 0; i < qk_per_thread; i++) {
289-
score += q[i] * k[i];
276+
score += q[i] * keys[i];
290277
}
291278
score = simd_sum(score);
292279

@@ -309,57 +296,30 @@ template <typename T, int D, int V = D>
309296
}
310297

311298
// Move the pointers to the next kv
312-
keys += blocks * inner_k_stride;
313-
values += blocks * inner_v_stride;
299+
keys += blocks * int(k_seq_stride);
300+
values += blocks * int(v_seq_stride);
314301
if (bool_mask) {
315-
bmask += BN * blocks * mask_kv_seq_stride;
302+
bmask += blocks * mask_kv_seq_stride;
316303
}
317304
if (float_mask) {
318-
fmask += BN * blocks * mask_kv_seq_stride;
305+
fmask += blocks * mask_kv_seq_stride;
319306
}
320307
}
321308

322-
// Each thread has a partial part of the output so we need to combine them.
323-
324-
// First let's communicate the max and sum_exp
309+
// Write the sum and max and outputs
325310
if (simd_lid == 0) {
326-
max_scores[simd_gid] = max_score;
327-
sum_exp_scores[simd_gid] = sum_exp_score;
328-
}
329-
threadgroup_barrier(mem_flags::mem_threadgroup);
330-
max_score = (simd_lid < BN) ? max_scores[simd_lid] : -1e9;
331-
U new_max = simd_max(max_score);
332-
U factor = fast::exp(max_score - new_max);
333-
sum_exp_score = (simd_lid < BN) ? sum_exp_scores[simd_lid] : 0;
334-
sum_exp_score = simd_sum(sum_exp_score * factor);
335-
336-
// Write the sum and new max
337-
if (simd_gid == 0) {
338311
sums[0] = sum_exp_score;
339-
maxs[0] = new_max;
312+
maxs[0] = max_score;
340313
}
341314

342-
// Now we need to aggregate all the outputs
343315
for (int i = 0; i < v_per_thread; i++) {
344-
outputs[simd_lid * BN + simd_gid] =
345-
o[i] * fast::exp(max_scores[simd_gid] - new_max);
346-
threadgroup_barrier(mem_flags::mem_threadgroup);
347-
348-
// And write the output
349-
if (simd_gid == 0) {
350-
U output = outputs[simd_lid * BN];
351-
for (int j = 1; j < BN; j++) {
352-
output += outputs[simd_lid * BN + j];
353-
}
354-
out[i] = static_cast<T>(output);
355-
}
356-
threadgroup_barrier(mem_flags::mem_threadgroup);
316+
out[i] = static_cast<T>(o[i]);
357317
}
358318
}
359319

360320
template <typename T, int D>
361321
[[kernel]] void sdpa_vector_2pass_2(
362-
const device float* partials [[buffer(0)]],
322+
const device T* partials [[buffer(0)]],
363323
const device float* sums [[buffer(1)]],
364324
const device float* maxs [[buffer(2)]],
365325
device T* out [[buffer(3)]],
@@ -370,38 +330,56 @@ template <typename T, int D>
370330
constexpr int BN = 32;
371331
constexpr int BD = 32;
372332
constexpr int elem_per_thread = D / BD;
373-
constexpr int blocks = 32;
374333

375334
typedef float U;
376335

377-
thread U o[elem_per_thread];
336+
thread U o[elem_per_thread] = {0};
378337
threadgroup U outputs[BN * BD];
379338

380339
// Adjust positions
381340
const int head_idx = tid.x;
382341
const int q_seq_idx = tid.y;
383342
const int q_offset = head_idx * tpg.y + q_seq_idx;
384-
;
385343
partials += q_offset * blocks * D + simd_gid * D + simd_lid * elem_per_thread;
386344
sums += q_offset * blocks;
387345
maxs += q_offset * blocks;
388346
out += q_offset * D + simd_gid * elem_per_thread;
389347

390-
// First everybody reads the max and sum_exp
391-
U max_score = maxs[simd_lid];
392-
U new_max = simd_max(max_score);
393-
U factor = fast::exp(max_score - new_max);
394-
U sum_exp_score = simd_sum(sums[simd_lid] * factor);
348+
// Set defaults
349+
U sum_exp_score = 0.0;
350+
U max_score = Limits<U>::finite_min;
395351

396-
// Now read the block into registers and then use shared memory to transpose
397-
// it
398-
for (int i = 0; i < elem_per_thread; i++) {
399-
o[i] = partials[i];
352+
// Reduce the max
353+
for (int b = 0; b < blocks / BN; ++b) {
354+
max_score = max(max_score, maxs[simd_lid + BN * b]);
355+
}
356+
max_score = simd_max(max_score);
357+
358+
// Reduce the d
359+
for (int b = 0; b < blocks / BN; ++b) {
360+
U factor = fast::exp(maxs[simd_lid + BN * b] - max_score);
361+
sum_exp_score += factor * sums[simd_lid + BN * b];
400362
}
363+
sum_exp_score = simd_sum(sum_exp_score);
364+
365+
// Reduce the sum exp and partials
366+
for (int b = 0; b < blocks / BN; ++b) {
367+
U factor = fast::exp(maxs[simd_gid] - max_score);
368+
369+
// Update the output accumulator
370+
for (int i = 0; i < elem_per_thread; i++) {
371+
o[i] += factor * partials[i];
372+
}
373+
maxs += BN;
374+
sums += BN;
375+
partials += BN * D;
376+
}
377+
378+
// Use shared memory to transpose and reduce the final block
401379
for (int i = 0; i < elem_per_thread; i++) {
402380
outputs[simd_lid * BD + simd_gid] = o[i];
403381
threadgroup_barrier(mem_flags::mem_threadgroup);
404-
o[i] = simd_sum(outputs[simd_gid * BD + simd_lid] * factor);
382+
o[i] = simd_sum(outputs[simd_gid * BD + simd_lid]);
405383
o[i] = sum_exp_score == 0 ? o[i] : (o[i] / sum_exp_score);
406384
threadgroup_barrier(mem_flags::mem_threadgroup);
407385
}

0 commit comments

Comments
 (0)