Download software/fa_int_model.py from Snapkitty/flash-attention-rtl: direct link, hf CLI and curl.
- Browser
- Download file 2.83 kB
-
https://huggingface.co/Snapkitty/flash-attention-rtl/resolve/main/software/fa_int_model.py
- Command line
-
hf download hf://Snapkitty/flash-attention-rtl/software/fa_int_model.py
-
curl -L -o fa_int_model.py https://huggingface.co/Snapkitty/flash-attention-rtl/resolve/main/software/fa_int_model.py
2.83 kB
| """ | |
| Bit-exact integer model of the FA_ENGINE hardware (rtl/src/fa_engine.sv). | |
| Number formats | |
| Q, K, V elements : signed 8-bit Q4.4 (value = int / 16), taken from the low | |
| 8 bits of each memory word | |
| logits : int, value = int / 256 ((q.k) * scale_q(d) >> 8) | |
| exp(-x) : Q0.16 (65536 == 1.0), LUT(16 segments) + linear interpolation | |
| l, o accumulators: wide integers (hardware: 64-bit) | |
| output O : value = int / 256 ((o * 16) / l, truncated toward zero) | |
| Tiling: keys are processed in tiles of BC=4; the online-softmax rescale happens | |
| once per tile, so BC is part of the numerical definition of the result. | |
| """ | |
| import math | |
| D_MAX = 16 | |
| BR = 4 | |
| BC = 4 | |
| NEG_INF = -(1 << 30) | |
| EXP_LUT = [int(65536 * 2 ** (-i / 16) + 0.5) for i in range(17)] | |
| SCALE_Q = [0] + [int(256 / math.sqrt(d) + 0.5) for d in range(1, D_MAX + 1)] | |
| LOG2E_Q8 = 369 # round(log2(e) * 256) | |
| def s8(x: int) -> int: | |
| x &= 0xFF | |
| return x - 256 if x & 0x80 else x | |
| def exp_neg(delta: int) -> int: | |
| """exp(-delta/256) in Q0.16 for delta >= 0 (delta in logit units).""" | |
| assert delta >= 0 | |
| if delta > 4095: | |
| delta = 4095 | |
| y8 = (delta * LOG2E_Q8) >> 8 | |
| n = y8 >> 8 | |
| f = y8 & 255 | |
| i = f >> 4 | |
| r = f & 15 | |
| t0 = EXP_LUT[i] | |
| t1 = EXP_LUT[i + 1] | |
| base = t0 - (((t0 - t1) * r) >> 4) | |
| return 0 if n >= 16 else base >> n | |
| def trunc_div(n: int, d: int) -> int: | |
| q = abs(n) // abs(d) | |
| return -q if (n < 0) != (d < 0) else q | |
| def fa_int(Q, K, V, N: int, d: int): | |
| """Q, K, V: N x d lists of ints (already int8-valued). Returns N x d ints.""" | |
| assert 1 <= d <= D_MAX and N >= 1 | |
| out = [] | |
| for i in range(N): | |
| m = NEG_INF | |
| l = 0 | |
| o = [0] * d | |
| for j0 in range(0, N, BC): | |
| keys = list(range(j0, min(j0 + BC, N))) | |
| logits = [] | |
| for j in keys: | |
| dot = sum(Q[i][c] * K[j][c] for c in range(d)) | |
| logits.append((dot * SCALE_Q[d]) >> 8) | |
| m_new = max(m, max(logits)) | |
| alpha = exp_neg(m_new - m) | |
| l = (l * alpha) >> 16 | |
| o = [(x * alpha) >> 16 for x in o] | |
| m = m_new | |
| for jj, j in enumerate(keys): | |
| p = exp_neg(m - logits[jj]) | |
| l += p | |
| for c in range(d): | |
| o[c] += p * V[j][c] | |
| out.append([trunc_div(x * 16, l) for x in o]) | |
| return out | |
| def fa_float(Q, K, V, N: int, d: int): | |
| """Plain floating-point attention on the real values (int / 16).""" | |
| import numpy as np | |
| q = np.array(Q, dtype=np.float64) / 16 | |
| k = np.array(K, dtype=np.float64) / 16 | |
| v = np.array(V, dtype=np.float64) / 16 | |
| s = q @ k.T / math.sqrt(d) | |
| s -= s.max(axis=1, keepdims=True) | |
| w = np.exp(s) | |
| w /= w.sum(axis=1, keepdims=True) | |
| return w @ v | |