com.microsoft.LinearAttention / build /webgpu /chunk-prep.wgsl.jinja
Xenova's picture
Xenova HF Staff
sync 91d990483a17
59216c2 verified
Raw
History Blame
1.84 kB
{% macro read_scalar(name, index, dtype) %}
{% if dtype == "float16" %}
f32({{ name }}[{{ index }}]){% else %}{{ name }}[{{ index }}]{% endif %}
{% endmacro %}{% if queryDtype == "float16" %}
enable f16;
{% endif %}
{{ env.wgsl.resourceDeclarations }}
// com.microsoft.LinearAttention, chunked prefill: within-chunk decay prefix.
// gexp[b, t, c] = exp(sum of decay[b, s, c] for s from the chunk start through t)
// Every later pass reads the recurrence's decay through this one buffer. Writing
// exp(prefix) rather than the prefix itself keeps the per-element exponential out
// of the O(chunk^2) inner loops, where it would cost one transcendental per
// multiply-add instead of one per element.
const CHUNK: u32 = {{ chunkSize }}u;
const WG: u32 = {{ workgroupSize }}u;
@compute @workgroup_size(WG, 1, 1)
fn main(
@builtin(workgroup_id) wg: vec3<u32>,
@builtin(local_invocation_id) lid: vec3<u32>,
) {
// 2D-folded flat (batch * chunk) index: wg.y carries the high bits past the
// per-axis dispatch fold width.
let flat = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
let num_chunks = (params.seqLength + CHUNK - 1u) / CHUNK;
let chunk = flat % num_chunks;
let batch = flat / num_chunks;
if (batch >= params.batchSize) {
return;
}
let t0 = chunk * CHUNK;
let t1 = min(t0 + CHUNK, params.seqLength);
let packed = params.decayPackedDim;
// One thread owns a decay column and walks the chunk in order: the prefix is
// serial in t but independent across columns, so the whole chunk grid runs at once.
for (var col = lid.x; col < packed; col = col + WG) {
var prefix = 0.0;
for (var t = t0; t < t1; t = t + 1u) {
let idx = (batch * params.seqLength + t) * packed + col;
prefix = prefix + {{ read_scalar("decay", "idx", queryDtype) }};
gexp[idx] = exp(prefix);
}
}
}