Instructions to use kernels-community/metal-flash-sdpa with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Kernels
How to use kernels-community/metal-flash-sdpa with Kernels:
# !pip install kernels from kernels import get_kernel kernel = get_kernel("kernels-community/metal-flash-sdpa") - Notebooks
- Google Colab
- Kaggle
Add flash_attn_with_kvcache paged-decode kernel (port from huggingface/transformers#45977)
#4
by ArthurZ HF Staff - opened
sdpa-metal/flash_attn_with_kvcache.metal
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Paged-attention decode kernel — Flash-Decoding style single-pass online
|
| 2 |
+
// softmax, reading K/V from a paged cache via a block-table indirection.
|
| 3 |
+
//
|
| 4 |
+
// One simdgroup (32 threads) per (request, head) pair. Each thread handles a
|
| 5 |
+
// stride-PD_TG_THREADS slice of head_dim. The per-position score is reduced
|
| 6 |
+
// across the simdgroup with simd_sum.
|
| 7 |
+
//
|
| 8 |
+
// Layout:
|
| 9 |
+
// q: (B, H_Q, head_dim) fp32 (one query token per request)
|
| 10 |
+
// k_cache: (num_slots, H_KV, head_dim) fp32 (paged storage)
|
| 11 |
+
// v_cache: (num_slots, H_KV, head_dim) fp32
|
| 12 |
+
// block_table: (B, max_blocks) int32 (slot = bt[b, t/bs] * bs + t%bs)
|
| 13 |
+
// seq_lens: (B,) int32 (valid KV positions per request)
|
| 14 |
+
// out: (B, H_Q, head_dim) fp32
|
| 15 |
+
//
|
| 16 |
+
// Dispatch: grid = (B, H_Q, 1), tg = (32, 1, 1)
|
| 17 |
+
//
|
| 18 |
+
// Threadgroup memory layout (fp32 floats):
|
| 19 |
+
// q_sh: head_dim
|
| 20 |
+
// o_sh: head_dim
|
| 21 |
+
// score_sh: 1 (broadcast slot for the per-position score)
|
| 22 |
+
// m_sh: 1 (broadcast slot for the running max)
|
| 23 |
+
// l_sh: 1 (broadcast slot for the running denominator)
|
| 24 |
+
//
|
| 25 |
+
// Ported from huggingface/transformers' gguf-dequant-kernels package
|
| 26 |
+
// (paged_decode_attention_f32). Bit-equivalent to a reference SDPA over the
|
| 27 |
+
// gathered K/V at (B=4, H_Q=16, head_dim=128, S=50). Runs at ~134 µs/call on
|
| 28 |
+
// M3 Max for that shape; well below the ~16 ms/layer it costs to gather the
|
| 29 |
+
// cache into a contiguous tensor and run torch SDPA over it.
|
| 30 |
+
|
| 31 |
+
#include <metal_stdlib>
|
| 32 |
+
using namespace metal;
|
| 33 |
+
|
| 34 |
+
#define PD_TG_THREADS 32
|
| 35 |
+
|
| 36 |
+
kernel void paged_decode_attention_f32(
|
| 37 |
+
device const float *q [[buffer(0)]], // (B, H_Q, head_dim)
|
| 38 |
+
device const float *k_cache [[buffer(1)]], // (num_slots, H_KV, head_dim)
|
| 39 |
+
device const float *v_cache [[buffer(2)]], // (num_slots, H_KV, head_dim)
|
| 40 |
+
device const int *block_table [[buffer(3)]], // (B, max_blocks)
|
| 41 |
+
device const int *seq_lens [[buffer(4)]], // (B,)
|
| 42 |
+
device float *out [[buffer(5)]], // (B, H_Q, head_dim)
|
| 43 |
+
constant uint &B [[buffer(6)]],
|
| 44 |
+
constant uint &H_Q [[buffer(7)]],
|
| 45 |
+
constant uint &H_KV [[buffer(8)]],
|
| 46 |
+
constant uint &head_dim [[buffer(9)]],
|
| 47 |
+
constant uint &block_size [[buffer(10)]],
|
| 48 |
+
constant uint &max_blocks [[buffer(11)]],
|
| 49 |
+
constant float &scale [[buffer(12)]],
|
| 50 |
+
threadgroup float *shmem [[threadgroup(0)]],
|
| 51 |
+
uint3 gid [[threadgroup_position_in_grid]],
|
| 52 |
+
uint3 tid3 [[thread_position_in_threadgroup]])
|
| 53 |
+
{
|
| 54 |
+
const uint tid = tid3.x;
|
| 55 |
+
const uint b = gid.x;
|
| 56 |
+
const uint h_q = gid.y;
|
| 57 |
+
if (b >= B || h_q >= H_Q) return;
|
| 58 |
+
|
| 59 |
+
const uint num_kv_groups = H_Q / H_KV;
|
| 60 |
+
const uint h_kv = h_q / num_kv_groups;
|
| 61 |
+
|
| 62 |
+
const uint q_row_off = (b * H_Q + h_q) * head_dim;
|
| 63 |
+
const uint S = (uint)seq_lens[b];
|
| 64 |
+
|
| 65 |
+
threadgroup float *q_sh = shmem;
|
| 66 |
+
threadgroup float *o_sh = shmem + head_dim;
|
| 67 |
+
threadgroup float *score_sh = shmem + 2 * head_dim;
|
| 68 |
+
threadgroup float *m_sh = shmem + 2 * head_dim + 1;
|
| 69 |
+
threadgroup float *l_sh = shmem + 2 * head_dim + 2;
|
| 70 |
+
|
| 71 |
+
for (uint i = tid; i < head_dim; i += PD_TG_THREADS) {
|
| 72 |
+
q_sh[i] = q[q_row_off + i];
|
| 73 |
+
o_sh[i] = 0.f;
|
| 74 |
+
}
|
| 75 |
+
if (tid == 0) {
|
| 76 |
+
*m_sh = -INFINITY;
|
| 77 |
+
*l_sh = 0.f;
|
| 78 |
+
}
|
| 79 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 80 |
+
|
| 81 |
+
for (uint pos = 0; pos < S; pos++) {
|
| 82 |
+
const uint blk_idx = pos / block_size;
|
| 83 |
+
const uint in_blk = pos % block_size;
|
| 84 |
+
const int bt_val = block_table[b * max_blocks + blk_idx];
|
| 85 |
+
if (bt_val < 0) continue; // padding slot
|
| 86 |
+
const uint slot = (uint)bt_val * block_size + in_blk;
|
| 87 |
+
const uint k_off = (slot * H_KV + h_kv) * head_dim;
|
| 88 |
+
|
| 89 |
+
float partial = 0.f;
|
| 90 |
+
for (uint i = tid; i < head_dim; i += PD_TG_THREADS) {
|
| 91 |
+
partial += q_sh[i] * k_cache[k_off + i];
|
| 92 |
+
}
|
| 93 |
+
partial = simd_sum(partial);
|
| 94 |
+
if (tid == 0) *score_sh = partial * scale;
|
| 95 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 96 |
+
|
| 97 |
+
const float score = *score_sh;
|
| 98 |
+
const float m_old = *m_sh;
|
| 99 |
+
const float m_new = max(m_old, score);
|
| 100 |
+
const float alpha = exp(m_old - m_new);
|
| 101 |
+
const float p_val = exp(score - m_new);
|
| 102 |
+
|
| 103 |
+
if (tid == 0) {
|
| 104 |
+
*l_sh = (*l_sh) * alpha + p_val;
|
| 105 |
+
*m_sh = m_new;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
const uint v_off = (slot * H_KV + h_kv) * head_dim;
|
| 109 |
+
for (uint i = tid; i < head_dim; i += PD_TG_THREADS) {
|
| 110 |
+
o_sh[i] = o_sh[i] * alpha + p_val * v_cache[v_off + i];
|
| 111 |
+
}
|
| 112 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
const float l_final = *l_sh;
|
| 116 |
+
for (uint i = tid; i < head_dim; i += PD_TG_THREADS) {
|
| 117 |
+
out[q_row_off + i] = o_sh[i] / l_final;
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
// Single-dispatch paged KV cache write. Computes write_slot from
|
| 123 |
+
// block_table + seq_lens on-device and writes new K/V into the paged cache.
|
| 124 |
+
// Used by ``flash_attn_with_kvcache`` to fold the per-step cache write into
|
| 125 |
+
// the same Metal pass.
|
| 126 |
+
//
|
| 127 |
+
// k_cache / v_cache : (num_slots, H_KV, D) fp32 in-place
|
| 128 |
+
// new_k / new_v : (B, H_KV, D) fp32 (per-request new token)
|
| 129 |
+
// block_table : (B, max_blocks) int32
|
| 130 |
+
// seq_lens : (B,) int32 (length AFTER write)
|
| 131 |
+
|
| 132 |
+
kernel void kv_paged_write_f32(
|
| 133 |
+
device float *k_cache [[buffer(0)]],
|
| 134 |
+
device float *v_cache [[buffer(1)]],
|
| 135 |
+
device const float *new_k [[buffer(2)]],
|
| 136 |
+
device const float *new_v [[buffer(3)]],
|
| 137 |
+
device const int *block_table [[buffer(4)]],
|
| 138 |
+
device const int *seq_lens [[buffer(5)]],
|
| 139 |
+
constant uint &B [[buffer(6)]],
|
| 140 |
+
constant uint &H_KV [[buffer(7)]],
|
| 141 |
+
constant uint &D [[buffer(8)]],
|
| 142 |
+
constant uint &block_size [[buffer(9)]],
|
| 143 |
+
constant uint &max_blocks [[buffer(10)]],
|
| 144 |
+
uint3 gid [[thread_position_in_grid]])
|
| 145 |
+
{
|
| 146 |
+
const uint b = gid.x, h = gid.y, d = gid.z;
|
| 147 |
+
if (b >= B || h >= H_KV || d >= D) return;
|
| 148 |
+
const int seq_len = seq_lens[b];
|
| 149 |
+
if (seq_len <= 0) return;
|
| 150 |
+
const uint pos = (uint)(seq_len - 1);
|
| 151 |
+
const uint block_in_seq = pos / block_size;
|
| 152 |
+
if (block_in_seq >= max_blocks) return;
|
| 153 |
+
const uint block_id = (uint)block_table[b * max_blocks + block_in_seq];
|
| 154 |
+
const uint write_slot = block_id * block_size + (pos % block_size);
|
| 155 |
+
const uint64_t new_off = (uint64_t)b * H_KV * D + (uint64_t)h * D + d;
|
| 156 |
+
const uint64_t cache_off = (uint64_t)write_slot * H_KV * D + (uint64_t)h * D + d;
|
| 157 |
+
k_cache[cache_off] = new_k[new_off];
|
| 158 |
+
v_cache[cache_off] = new_v[new_off];
|
| 159 |
+
}
|