727 lines
29 KiB
C
727 lines
29 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/**
|
|
* @file ft_det_math.h
|
|
* @brief Deterministic, INTEGER-ONLY fp32 math for the Gated-DeltaNet gates, on fp32 BIT
|
|
* PATTERNS (uint32_t). Two layers:
|
|
* - FAST (the card path): ft_det_gdn_gate (decay, fused) and ft_det_gdn_beta.
|
|
* - REFERENCE: add, mul, exp, softplus, sigmoid and the fp32-step composites
|
|
* ft_det_gdn_g / ft_det_gdn_decay / ft_det_gdn_beta_reference.
|
|
*
|
|
* WHY: the card's RISC-V data-movement core (rv32im, no FPU) and the x86 host (-ffast-math,
|
|
* FTZ+DAZ) must produce the SAME BITS for beta/g/decay. libm cannot be matched, so both run
|
|
* this header. No float type or float operation appears below, so the result is independent
|
|
* of compiler float flags; no 64-bit division, no recursion, no libc, no <math.h>.
|
|
*
|
|
* SEMANTICS (every function):
|
|
* - DAZ: a denormal input reads as a zero of the same sign.
|
|
* - FTZ after rounding: a result whose 24-bit-rounded value (unbounded exponent) is below
|
|
* 2^-126 is returned as a signed zero (x86 SSE with MXCSR FTZ|DAZ).
|
|
* - add/mul: IEEE RNE, bit-identical to x86 addss/mulss under FTZ|DAZ, including x86's NaN
|
|
* rules (first NaN operand quieted; invalid -> default NaN 0xFFC00000). ft_det_mul equals
|
|
* ftgc_compact.h's ftgc_mul on every finite pair (tests/test_det_math.c checks it).
|
|
* - exp/softplus/sigmoid: an internal fixed-point value good to ~2^-29 relative, rounded
|
|
* ONCE to nearest-even. NaN in -> the same NaN quieted. exp(+Inf)=+Inf, exp(-Inf)=+0,
|
|
* sigmoid(+-Inf)=1/+0, softplus(+Inf)=+Inf, softplus(-Inf)=+0.
|
|
* - softplus(x) returns x unchanged when x > 20.0f (the host's `x > 20.0f` branch).
|
|
* Measured accuracy vs the correctly rounded result: tests/test_det_math.c.
|
|
*
|
|
* Constant data: four 16-word tables (256 B; the fast path uses three). Code avoids clz/ctz
|
|
* builtins (libgcc calls on rv32im without Zbb); right shifts of negative int32/int64 are
|
|
* assumed arithmetic (gcc/clang). The REFERENCE exp core has a variable 64-bit shift
|
|
* (__lshrdi3 under gcc -Os on rv32); the fast path has none.
|
|
*
|
|
* Size (clang 22 -Os): fast path (gate + beta + helpers + pack) 2048 B rv32im / 1560 B
|
|
* rv32imc; everything 4248 / 3206 B. No libgcc calls. Not measured with riscv-tt-elf-gcc.
|
|
*
|
|
* @status Off-hardware verified (tests/test_det_math.c); fast path not yet run on the card.
|
|
* @issues The tables are .rodata: the kernel linker script must place .rodata in local memory.
|
|
* @todo None
|
|
*/
|
|
#ifndef FT_DET_MATH_H
|
|
#define FT_DET_MATH_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#define FT_DET_SIGN 0x80000000u
|
|
#define FT_DET_INF 0x7F800000u
|
|
#define FT_DET_QUIET 0x00400000u
|
|
#define FT_DET_DEFAULT_NAN 0xFFC00000u /* x86 "real indefinite" */
|
|
#define FT_DET_ONE 0x3F800000u
|
|
#define FT_DET_HALF 0x3F000000u
|
|
#define FT_DET_SOFTPLUS_THRESHOLD 0x41A00000u /* 20.0f */
|
|
#define FT_DET_ABS_128 0x43000000u /* |x| >= 128: exp saturates */
|
|
|
|
/* Overridable ONLY so tests/test_det_math.c can build mutated (red) arms. */
|
|
#ifndef FT_DET_ROUND_HALF
|
|
#define FT_DET_ROUND_HALF 0x80u /* half an ulp of ft_det_pack's 8 round bits */
|
|
#endif
|
|
#ifndef FT_DET_EXP_C6
|
|
#define FT_DET_EXP_C6 0x2AAAAAABu /* 1/6, Q32 */
|
|
#endif
|
|
|
|
/* Every non-trivial function is kept out of line: the cores have several callers, and one
|
|
* copy of each is what holds the library to its text budget on the reader core (clang -Os
|
|
* otherwise inlines exp_core three times). `inline` keeps unused ones warning-free. */
|
|
#define FT_DET_SHARED static inline __attribute__((noinline))
|
|
|
|
/** (2^(i/16) - 1) in Q32, i = 0..15. */
|
|
static const uint32_t ft_det_exp2_table[16] = {
|
|
0x00000000u, 0x0B5586D0u, 0x172B83C8u, 0x2387A6E7u, 0x306FE0A3u, 0x3DEA64C1u,
|
|
0x4BFDAD53u, 0x5AB07DD5u, 0x6A09E668u, 0x7A11473Fu, 0x8ACE5423u, 0x9C49182Au,
|
|
0xAE89F996u, 0xC199BDD8u, 0xD5818DD0u, 0xEA4AFA2Au};
|
|
|
|
/** r_i = 1 / (1 + (i + 0.5)/16) in Q32, rounded. */
|
|
static const uint32_t ft_det_log_recip_table[16] = {
|
|
0xF83E0F84u, 0xEA0EA0EAu, 0xDD67C8A6u, 0xD20D20D2u, 0xC7CE0C7Du, 0xBE82FA0Cu,
|
|
0xB60B60B6u, 0xAE4C415Du, 0xA72F0539u, 0xA0A0A0A1u, 0x9A90E7D9u, 0x94F2094Fu,
|
|
0x8FB823EEu, 0x8AD8F2FCu, 0x864B8A7Eu, 0x82082082u};
|
|
|
|
/** -ln(r_i) in Q32, for the ROUNDED r_i above. */
|
|
static const uint32_t ft_det_log_neg_table[16] = {
|
|
0x07E0A6C3u, 0x16F0D28Bu, 0x252AA5F0u, 0x32A4B53Au, 0x3F7230DBu, 0x4BA38AEBu,
|
|
0x5746F6FDu, 0x6268CE1Au, 0x6D13DDF0u, 0x7751A812u, 0x812A952Eu, 0x8AA61E98u,
|
|
0x93CAF094u, 0x9C9F069Au, 0xA527C2EDu, 0xAD6A0262u};
|
|
|
|
static inline uint32_t ft_det_mulhu(uint32_t a, uint32_t b)
|
|
{
|
|
return (uint32_t)(((uint64_t)a * b) >> 32);
|
|
}
|
|
|
|
/** a * b >> 35 on signed values (Q35 x QN -> QN). */
|
|
static inline int32_t ft_det_mul_q35(int32_t a, int32_t b)
|
|
{
|
|
return (int32_t)(((int64_t)a * b) >> 35);
|
|
}
|
|
|
|
static inline int ft_det_is_nan(uint32_t a)
|
|
{
|
|
return (a & 0x7FFFFFFFu) > FT_DET_INF;
|
|
}
|
|
|
|
/** x86 two-operand NaN rule: the first NaN operand, quieted. */
|
|
static inline uint32_t ft_det_nan_pick(uint32_t a, uint32_t b)
|
|
{
|
|
return (ft_det_is_nan(a) ? a : b) | FT_DET_QUIET;
|
|
}
|
|
|
|
/* An unrounded intermediate: low word the mantissa M (bit 31 set), high word the exponent k
|
|
* (int32), value M * 2^(k - 31). A uint64_t so it comes back in two registers on rv32. */
|
|
static inline uint64_t ft_det_unpacked(int32_t exponent, uint32_t mantissa)
|
|
{
|
|
return ((uint64_t)(uint32_t)exponent << 32) | mantissa;
|
|
}
|
|
|
|
static inline int32_t ft_det_unpacked_exponent(uint64_t value)
|
|
{
|
|
return (int32_t)(uint32_t)(value >> 32);
|
|
}
|
|
|
|
/**
|
|
* Round an unpacked value to fp32 (mantissa bit 31 set, any sticky information already OR-ed
|
|
* into bit 0). RNE on the low 8 bits; FTZ after rounding; overflow -> Inf.
|
|
*/
|
|
FT_DET_SHARED uint32_t ft_det_pack(uint32_t sign, uint64_t value)
|
|
{
|
|
int32_t exponent = ft_det_unpacked_exponent(value);
|
|
uint32_t mant = (uint32_t)value >> 8;
|
|
const uint32_t round = (uint32_t)value & 0xFFu;
|
|
|
|
if (round > FT_DET_ROUND_HALF || (round == FT_DET_ROUND_HALF && (mant & 1u)))
|
|
mant++;
|
|
if (mant == 0x01000000u) {
|
|
mant >>= 1;
|
|
exponent++;
|
|
}
|
|
exponent += 127;
|
|
if (exponent <= 0)
|
|
return sign;
|
|
if (exponent >= 255)
|
|
return sign | FT_DET_INF;
|
|
return sign | ((uint32_t)exponent << 23) | (mant & 0x007FFFFFu);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- add / mul (x86 SSE) */
|
|
|
|
static inline uint32_t ft_det_add_special(uint32_t a, uint32_t b)
|
|
{
|
|
const uint32_t a_inf = (a & 0x7FFFFFFFu) == FT_DET_INF;
|
|
const uint32_t b_inf = (b & 0x7FFFFFFFu) == FT_DET_INF;
|
|
|
|
if (ft_det_is_nan(a) || ft_det_is_nan(b))
|
|
return ft_det_nan_pick(a, b);
|
|
if (a_inf && b_inf && ((a ^ b) & FT_DET_SIGN))
|
|
return FT_DET_DEFAULT_NAN;
|
|
return a_inf ? a : b;
|
|
}
|
|
|
|
/** a + b, x86 addss under FTZ|DAZ, round to nearest even. */
|
|
FT_DET_SHARED uint32_t ft_det_add(uint32_t a, uint32_t b)
|
|
{
|
|
const uint32_t ea = (a >> 23) & 0xFFu, eb = (b >> 23) & 0xFFu;
|
|
uint32_t big, small, shift, ma, mb, m;
|
|
int32_t e;
|
|
|
|
if (ea == 0xFFu || eb == 0xFFu)
|
|
return ft_det_add_special(a, b);
|
|
if (ea == 0u || eb == 0u) { /* zeros, denormals read as zero (DAZ) */
|
|
if (ea != 0u)
|
|
return a;
|
|
if (eb != 0u)
|
|
return b;
|
|
return a & b & FT_DET_SIGN; /* -0 only if both are negative */
|
|
}
|
|
big = ((a & 0x7FFFFFFFu) >= (b & 0x7FFFFFFFu)) ? a : b;
|
|
small = (big == a) ? b : a;
|
|
shift = ((big >> 23) & 0xFFu) - ((small >> 23) & 0xFFu);
|
|
e = (int32_t)((big >> 23) & 0xFFu) - 126; /* exponent of a bit-31-led mantissa */
|
|
ma = ((big << 8) | FT_DET_SIGN) >> 1; /* 1.m at bits [30:7]: one bit of carry room */
|
|
mb = ((small << 8) | FT_DET_SIGN) >> 1;
|
|
if (shift >= 31u)
|
|
mb = 1u; /* only a sticky bit survives */
|
|
else if (shift != 0u)
|
|
mb = (mb >> shift) | (uint32_t)((mb << (32u - shift)) != 0u);
|
|
if ((big ^ small) & FT_DET_SIGN) {
|
|
m = ma - mb;
|
|
if (m == 0u)
|
|
return 0u; /* exact cancellation is +0 under RNE */
|
|
} else {
|
|
m = ma + mb;
|
|
}
|
|
while (!(m & FT_DET_SIGN)) {
|
|
m <<= 1;
|
|
e--;
|
|
}
|
|
return ft_det_pack(big & FT_DET_SIGN, ft_det_unpacked(e, m));
|
|
}
|
|
|
|
/** a * b, x86 mulss under FTZ|DAZ (the same function as ftgc_mul, plus Inf/NaN). */
|
|
FT_DET_SHARED uint32_t ft_det_mul(uint32_t a, uint32_t b)
|
|
{
|
|
const uint32_t ea = (a >> 23) & 0xFFu, eb = (b >> 23) & 0xFFu;
|
|
const uint32_t sign = (a ^ b) & FT_DET_SIGN;
|
|
uint64_t product;
|
|
uint32_t hi, lo;
|
|
int32_t e;
|
|
|
|
if (ea == 0xFFu || eb == 0xFFu) {
|
|
if (ft_det_is_nan(a) || ft_det_is_nan(b))
|
|
return ft_det_nan_pick(a, b);
|
|
if (ea == 0u || eb == 0u) /* Inf * 0 (or * denormal, DAZ) */
|
|
return FT_DET_DEFAULT_NAN;
|
|
return sign | FT_DET_INF;
|
|
}
|
|
if (ea == 0u || eb == 0u) /* zero, or a denormal read as zero (DAZ) */
|
|
return sign;
|
|
product = (uint64_t)((a << 8) | FT_DET_SIGN) * ((b << 8) | FT_DET_SIGN); /* Q62, [1, 4) */
|
|
hi = (uint32_t)(product >> 32);
|
|
lo = (uint32_t)product;
|
|
e = (int32_t)(ea + eb) - 253;
|
|
if (!(hi & FT_DET_SIGN)) { /* product in [1, 2) */
|
|
hi = (hi << 1) | (lo >> 31);
|
|
lo <<= 1;
|
|
e--;
|
|
}
|
|
return ft_det_pack(sign, ft_det_unpacked(e, hi | (uint32_t)(lo != 0u)));
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- transcendental cores */
|
|
|
|
/**
|
|
* e^x for finite x with |x| < 128 (zero/denormal allowed: reads as 0), unpacked, good to
|
|
* ~2^-29 relative.
|
|
*
|
|
* x*log2(e) = k + f in Q32 (log2(e) to 64 bits), 2^f = 2^(i/16) * e^(r*ln2) with i the top
|
|
* 4 bits of f and e^y - 1 a degree-5 Taylor polynomial on y < ln2/16 (error < 2^-36).
|
|
*/
|
|
FT_DET_SHARED uint64_t ft_det_exp_core(uint32_t x)
|
|
{
|
|
const uint32_t ex = (x >> 23) & 0xFFu;
|
|
uint32_t whole = 0u, frac = 0u, y, h, em1, tf;
|
|
int32_t k = 0;
|
|
uint64_t f;
|
|
|
|
if (ex >= 86u) { /* below 2^-41, |x*log2 e| < 2^-32: e^x is 1 to this precision */
|
|
const uint32_t m = (x & 0x007FFFFFu) | 0x00800000u;
|
|
uint64_t t = (uint64_t)m * 0xB8AA3B29u + (((uint64_t)m * 0x5C17F0BBu) >> 32);
|
|
|
|
t >>= (149u - ex); /* |x| * log2(e) in Q32 */
|
|
whole = (uint32_t)(t >> 32);
|
|
frac = (uint32_t)t;
|
|
}
|
|
if (x & FT_DET_SIGN) {
|
|
k = -(int32_t)whole - (int32_t)(frac != 0u);
|
|
frac = 0u - frac;
|
|
} else {
|
|
k = (int32_t)whole;
|
|
}
|
|
y = ft_det_mulhu(frac & 0x0FFFFFFFu, 0xB17217F8u); /* r * ln2, Q32 */
|
|
h = 0x0AAAAAABu + ft_det_mulhu(y, 0x02222222u); /* 1/24 + y/120 */
|
|
h = FT_DET_EXP_C6 + ft_det_mulhu(y, h); /* 1/6 + ... */
|
|
h = 0x80000000u + ft_det_mulhu(y, h); /* 1/2 + ... */
|
|
em1 = y + ft_det_mulhu(ft_det_mulhu(y, y), h); /* e^y - 1 */
|
|
tf = ft_det_exp2_table[frac >> 28];
|
|
f = (uint64_t)tf + em1 + ft_det_mulhu(tf, em1); /* (1+tf)(1+em1) - 1, Q32 */
|
|
if (f > 0xFFFFFFFFu)
|
|
f = 0xFFFFFFFFu;
|
|
return ft_det_unpacked(k, FT_DET_SIGN | (uint32_t)(f >> 1) | ((uint32_t)f & 1u));
|
|
}
|
|
|
|
/** h(v) = 1/2 - v/3 + v^2/4 - v^3/5 + v^4/6, v in Q35, result Q31: log1p(v) = v - v^2 h(v). */
|
|
FT_DET_SHARED int32_t ft_det_log1p_horner(int32_t v)
|
|
{
|
|
int32_t h = 0x1999999A - ft_det_mul_q35(v, 0x15555555);
|
|
|
|
h = 0x20000000 - ft_det_mul_q35(v, h);
|
|
h = 0x2AAAAAAB - ft_det_mul_q35(v, h);
|
|
return 0x40000000 - ft_det_mul_q35(v, h);
|
|
}
|
|
|
|
/**
|
|
* log(1 + u) for u = mant_u * 2^(ku - 31) in (0, 1] (ft_det_exp_core of a value <= 0).
|
|
* Returns an unpacked value like ft_det_exp_core.
|
|
* u >= 2^-5: w = 1+u, w*r_i = 1+v with |v| < 1/32, log w = -ln r_i + log1p(v).
|
|
* u < 2^-5: log1p(u) = u (1 - u h(u)), relative, so tiny u keeps full precision.
|
|
*/
|
|
FT_DET_SHARED uint64_t ft_det_log1p_core(uint32_t mant_u, int32_t ku)
|
|
{
|
|
uint32_t m;
|
|
int32_t e;
|
|
|
|
if (ku == 0) /* u == 1 */
|
|
return ft_det_unpacked(-1, 0xB17217F8u); /* ln 2 */
|
|
if (ku >= -5) {
|
|
const uint32_t u = mant_u >> (uint32_t)(-1 - ku); /* Q32, [2^-5, 1) */
|
|
const uint32_t recip = ft_det_log_recip_table[u >> 28];
|
|
const uint64_t prod = (((uint64_t)u * recip) >> 1) + ((uint64_t)recip << 31); /* Q63 */
|
|
const int32_t v = (int32_t)((int64_t)(prod >> 28) - ((int64_t)1 << 35)); /* Q35 */
|
|
const int32_t v2h =
|
|
(int32_t)(((int64_t)ft_det_mul_q35(v, v) * ft_det_log1p_horner(v)) >> 31);
|
|
|
|
m = ft_det_log_neg_table[u >> 28] + (uint32_t)((v - v2h) >> 3); /* Q32 */
|
|
e = -1;
|
|
while (!(m & FT_DET_SIGN)) {
|
|
m <<= 1;
|
|
e--;
|
|
}
|
|
} else {
|
|
const uint32_t shift = (uint32_t)(-4 - ku);
|
|
const uint32_t u35 = (shift < 32u) ? (mant_u >> shift) : 0u; /* Q35, < 2^30 */
|
|
const uint32_t uh = (uint32_t)(
|
|
((uint64_t)u35 * (uint32_t)ft_det_log1p_horner((int32_t)u35)) >> 34); /* Q32 */
|
|
|
|
m = mant_u - ft_det_mulhu(mant_u, uh);
|
|
e = ku;
|
|
if (!(m & FT_DET_SIGN)) {
|
|
m <<= 1;
|
|
e--;
|
|
}
|
|
}
|
|
return ft_det_unpacked(e, m);
|
|
}
|
|
|
|
/**
|
|
* floor(2^63 / w) for w in (2^31, 2^32), i.e. 1/w in Q32, with a non-zero remainder OR-ed
|
|
* into bit 0 (sticky). Restoring division, 32-bit only.
|
|
*/
|
|
FT_DET_SHARED uint32_t ft_det_reciprocal_q32(uint32_t w)
|
|
{
|
|
uint32_t remainder = 0x80000000u, quotient = 0u, carry, bit;
|
|
|
|
for (bit = 0u; bit < 32u; bit++) {
|
|
carry = remainder >> 31;
|
|
remainder <<= 1;
|
|
quotient <<= 1;
|
|
if (carry || remainder >= w) {
|
|
remainder -= w;
|
|
quotient |= 1u;
|
|
}
|
|
}
|
|
return quotient | (uint32_t)(remainder != 0u);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- public functions */
|
|
|
|
/** e^x. FTZ below 2^-126, +Inf on overflow. */
|
|
FT_DET_SHARED uint32_t ft_det_exp(uint32_t x)
|
|
{
|
|
if ((x & 0x7FFFFFFFu) > FT_DET_INF)
|
|
return x | FT_DET_QUIET;
|
|
if ((x & 0x7FFFFFFFu) >= FT_DET_ABS_128) /* includes +-Inf */
|
|
return (x & FT_DET_SIGN) ? 0u : FT_DET_INF;
|
|
return ft_det_pack(0u, ft_det_exp_core(x));
|
|
}
|
|
|
|
/**
|
|
* x + L for 0 < x <= 20, with L = log(1 + e^-x) unpacked.
|
|
* 32-bit only: both terms in units of 2^-q, q chosen so x's mantissa sits at [29:6] (x >= 1)
|
|
* or q = 30 (x < 1): six guard bits below the result's ulp and no overflow (x + L < 2^31).
|
|
*/
|
|
static inline uint32_t ft_det_softplus_positive(uint32_t x, uint64_t log_value)
|
|
{
|
|
const uint32_t log_mant = (uint32_t)log_value;
|
|
const int32_t log_exp = ft_det_unpacked_exponent(log_value);
|
|
const int32_t ex = (int32_t)((x >> 23) & 0xFFu);
|
|
const int32_t q = (ex >= 127) ? 156 - ex : 30;
|
|
const int32_t x_shift = q + ex - 150; /* x = M * 2^(ex-150) = (M << x_shift) * 2^-q */
|
|
const uint32_t log_shift = (uint32_t)(31 - q - log_exp); /* >= 2 */
|
|
const uint32_t mant = (x & 0x007FFFFFu) | 0x00800000u;
|
|
uint32_t sum = 0u;
|
|
int32_t e = 31 - q;
|
|
|
|
if (log_shift < 32u)
|
|
sum = (log_mant >> log_shift) | (uint32_t)((log_mant << (32u - log_shift)) != 0u);
|
|
if (ex != 0 && x_shift >= 0)
|
|
sum += mant << x_shift;
|
|
else if (ex != 0 && x_shift > -24)
|
|
sum += mant >> -x_shift;
|
|
while (!(sum & FT_DET_SIGN)) { /* sum >= 2^29: at most two steps */
|
|
sum <<= 1;
|
|
e--;
|
|
}
|
|
return ft_det_pack(0u, ft_det_unpacked(e, sum));
|
|
}
|
|
|
|
/** log(1 + e^x); x itself when x > 20.0f (bit-identical to the host's branch). */
|
|
FT_DET_SHARED uint32_t ft_det_softplus(uint32_t x)
|
|
{
|
|
const uint32_t magnitude = x & 0x7FFFFFFFu;
|
|
uint64_t u, log_value;
|
|
|
|
if (magnitude > FT_DET_INF)
|
|
return x | FT_DET_QUIET;
|
|
if (!(x & FT_DET_SIGN) && magnitude > FT_DET_SOFTPLUS_THRESHOLD)
|
|
return x; /* includes +Inf */
|
|
if ((x & FT_DET_SIGN) && magnitude >= FT_DET_ABS_128)
|
|
return 0u; /* e^x flushes; includes -Inf */
|
|
u = ft_det_exp_core(x | FT_DET_SIGN); /* u = e^-|x| */
|
|
log_value = ft_det_log1p_core((uint32_t)u, ft_det_unpacked_exponent(u));
|
|
if (x & FT_DET_SIGN)
|
|
return ft_det_pack(0u, log_value);
|
|
return ft_det_softplus_positive(x, log_value);
|
|
}
|
|
|
|
/** 1 / (1 + e^-x); for x < 0 computed as u / (1 + u), u = e^x. */
|
|
FT_DET_SHARED uint32_t ft_det_sigmoid(uint32_t x)
|
|
{
|
|
const uint32_t negative = x & FT_DET_SIGN;
|
|
uint32_t mant_u, w, q;
|
|
uint64_t u, prod;
|
|
int32_t ku;
|
|
|
|
if ((x & 0x7FFFFFFFu) > FT_DET_INF)
|
|
return x | FT_DET_QUIET;
|
|
if ((x & 0x7FFFFFFFu) >= FT_DET_ABS_128) /* includes +-Inf */
|
|
return negative ? 0u : FT_DET_ONE;
|
|
u = ft_det_exp_core(x | FT_DET_SIGN); /* u = e^-|x| */
|
|
mant_u = (uint32_t)u;
|
|
ku = ft_det_unpacked_exponent(u);
|
|
if (ku == 0)
|
|
return FT_DET_HALF; /* u == 1 */
|
|
w = FT_DET_SIGN + ((-ku < 32) ? (mant_u >> (uint32_t)(-ku)) : 0u); /* 1 + u, Q31 */
|
|
if (w == FT_DET_SIGN)
|
|
return negative ? ft_det_pack(0u, u) : FT_DET_ONE;
|
|
q = ft_det_reciprocal_q32(w); /* 1 / (1 + u), Q32, in (1/2, 1), sticky in bit 0 */
|
|
if (!negative)
|
|
return ft_det_pack(0u, ft_det_unpacked(-1, q));
|
|
prod = (uint64_t)mant_u * q; /* u / (1 + u) = prod * 2^(ku - 63), prod in (2^62, 2^64) */
|
|
if (!(prod >> 63)) {
|
|
prod <<= 1;
|
|
ku--;
|
|
}
|
|
return ft_det_pack(0u, ft_det_unpacked(ku, (uint32_t)(prod >> 32) |
|
|
(uint32_t)((uint32_t)prod != 0u)));
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- fast gate path
|
|
*
|
|
* The card path. 32-bit arithmetic plus 32x32->64 products only: no variable 64-bit shift
|
|
* (gcc -Os on rv32 turns those into __lshrdi3 calls), no division, short fixed loops.
|
|
* Works in base 2: e^x = 2^t with t = x*log2(e), so
|
|
* decay = exp(a * softplus(x)) = 2^(a * S(t)), S(t) = max(t, 0) + log2(1 + 2^-|t|)
|
|
* beta = 1 / (1 + 2^-t) (Newton reciprocal, 3 steps)
|
|
* Accuracy is defined against an all-double reference: tests/test_det_math.c.
|
|
*/
|
|
|
|
#ifndef FT_DET_FAST_LOG1P_C3
|
|
#define FT_DET_FAST_LOG1P_C3 0x2AAAAAABu /* 1/3, Q31 (overridable for a red arm) */
|
|
#endif
|
|
#ifndef FT_DET_FAST_NEWTON_STEPS
|
|
#define FT_DET_FAST_NEWTON_STEPS 3 /* 1/17 seed -> 2^-8 -> 2^-16 -> 2^-32 */
|
|
#endif
|
|
|
|
/** -log2(r_i) in Q31, for the rounded r_i of ft_det_log_recip_table. */
|
|
static const uint32_t ft_det_log2_neg_table[16] = {
|
|
0x05AEB4DDu, 0x108C588Du, 0x1ACF5E2Eu, 0x24880F56u, 0x2DC4439Bu, 0x368FD7EEu,
|
|
0x3EF50AD2u, 0x46FCC47Au, 0x4EAECFEBu, 0x5612089Au, 0x5D2C7F5Au, 0x64039858u,
|
|
0x6A9C23D6u, 0x70FA728Bu, 0x772266ADu, 0x7D17822Fu};
|
|
|
|
/** a * b / 2^32 rounded to nearest (unbiased products keep the gate's error from adding up). */
|
|
static inline uint32_t ft_det_mulhu_round(uint32_t a, uint32_t b)
|
|
{
|
|
return (uint32_t)(((uint64_t)a * b + 0x80000000u) >> 32);
|
|
}
|
|
|
|
/** p / 2^shift rounded to nearest, shift in [1, 31]. */
|
|
static inline uint32_t ft_det_shift_round(uint32_t p, uint32_t shift)
|
|
{
|
|
return ((p >> (shift - 1u)) + 1u) >> 1;
|
|
}
|
|
|
|
static inline int32_t ft_det_mulh(int32_t a, int32_t b)
|
|
{
|
|
return (int32_t)(((int64_t)a * b) >> 32);
|
|
}
|
|
|
|
/** 2^F for F in [0, 1) (Q32): mantissa in [2^31, 2^32), ~2^-29 relative. */
|
|
FT_DET_SHARED uint32_t ft_det_fast_exp2_frac(uint32_t frac)
|
|
{
|
|
const uint32_t tf = ft_det_exp2_table[frac >> 28];
|
|
const uint32_t y = ft_det_mulhu(frac & 0x0FFFFFFFu, 0xB17217F8u); /* r ln2 < 0.0434, Q32 */
|
|
uint32_t h = FT_DET_EXP_C6 + ft_det_mulhu(y, 0x0AAAAAABu); /* 1/6 + y/24 */
|
|
uint32_t em1, cross, sum, carry;
|
|
|
|
h = 0x80000000u + ft_det_mulhu(y, h); /* 1/2 + y/6 + y^2/24 */
|
|
em1 = y + ft_det_mulhu(ft_det_mulhu(y, y), h); /* e^y - 1, error < 2^-29 */
|
|
cross = ft_det_mulhu(tf, em1);
|
|
sum = tf + em1; /* (1 + tf)(1 + em1) - 1 */
|
|
carry = sum < tf;
|
|
sum += cross;
|
|
carry |= sum < cross;
|
|
return carry ? 0xFFFFFFFFu : (FT_DET_SIGN | (sum >> 1));
|
|
}
|
|
|
|
/**
|
|
* |x| * log2(e) for |x| = m * 2^(e - 31) (m bit 31 set, e <= 7): high word the integer
|
|
* part k, low word the fraction (Q32). The 64-bit product is funnel-shifted in 32-bit pieces.
|
|
*/
|
|
FT_DET_SHARED uint64_t ft_det_fast_log2e_split(uint32_t m, int32_t e)
|
|
{
|
|
const uint32_t correction = ft_det_mulhu(m, 0x5C17F0BBu); /* log2(e) bits 32..63 */
|
|
const uint32_t shift = (uint32_t)(30 - e); /* >= 23 */
|
|
uint32_t lo = m * 0xB8AA3B29u, hi = ft_det_mulhu(m, 0xB8AA3B29u);
|
|
|
|
lo += correction;
|
|
hi += (uint32_t)(lo < correction); /* P = |x| log2(e) 2^(62-e) */
|
|
if (shift < 32u)
|
|
return ((uint64_t)(hi >> shift) << 32) | (hi << (32u - shift)) | (lo >> shift);
|
|
return (shift < 64u) ? (hi >> (shift - 32u)) : 0u;
|
|
}
|
|
|
|
/** log2(1 + u), u in [0, 1) Q32 -> Q31 (clamped at 0), absolute error ~2^-31. */
|
|
FT_DET_SHARED uint32_t ft_det_fast_log2_1p(uint32_t u)
|
|
{
|
|
const uint32_t recip = ft_det_log_recip_table[u >> 28];
|
|
const int32_t v = (int32_t)(recip + ft_det_mulhu(u, recip)); /* (1+u) r - 1, Q32, |v|<1/32 */
|
|
int32_t h = 0x20000000 - ft_det_mulh(v, 0x1999999A); /* Q31: 1/4 - v/5 */
|
|
int32_t natural, result;
|
|
|
|
h = (int32_t)FT_DET_FAST_LOG1P_C3 - ft_det_mulh(v, h);
|
|
h = 0x40000000 - ft_det_mulh(v, h); /* 1/2 - v/3 + v^2/4 - v^3/5 */
|
|
natural = v - ft_det_mulh(ft_det_mulh(v, v), h) * 2; /* log1p(v), Q32 */
|
|
result = (int32_t)ft_det_log2_neg_table[u >> 28] +
|
|
((natural + ft_det_mulh(natural, 0x71547653)) >> 1); /* * log2(e) -> Q31 */
|
|
return result < 0 ? 0u : (uint32_t)result;
|
|
}
|
|
|
|
/** p * 2^shift (p < 0xFFFFFFFF), rounded to nearest, saturating at 0xFFFFFFFF. */
|
|
static inline uint32_t ft_det_fast_scale(uint32_t p, int32_t shift)
|
|
{
|
|
if (shift == 0)
|
|
return p;
|
|
if (shift < 0)
|
|
return (shift > -32) ? ft_det_shift_round(p, (uint32_t)-shift) : 0u;
|
|
if (shift >= 32 || (p >> (32u - (uint32_t)shift)) != 0u)
|
|
return 0xFFFFFFFFu;
|
|
return p << (uint32_t)shift;
|
|
}
|
|
|
|
/** Normalizes m (non-zero) to bit 31 in five fixed steps; returns the shift in the high word. */
|
|
static inline uint64_t ft_det_fast_normalize(uint32_t m)
|
|
{
|
|
uint32_t n = 0u;
|
|
|
|
if (!(m >> 16)) { m <<= 16; n += 16u; }
|
|
if (!(m >> 24)) { m <<= 8; n += 8u; }
|
|
if (!(m >> 28)) { m <<= 4; n += 4u; }
|
|
if (!(m >> 30)) { m <<= 2; n += 2u; }
|
|
if (!(m >> 31)) { m <<= 1; n += 1u; }
|
|
return ((uint64_t)n << 32) | m;
|
|
}
|
|
|
|
/** Inf/NaN anywhere in the gate's inputs (alpha + dt = x; decay = exp(a * softplus(x))). */
|
|
FT_DET_SHARED uint32_t ft_det_fast_gate_special(uint32_t alpha, uint32_t dt, uint32_t a)
|
|
{
|
|
const uint32_t alpha_inf = (alpha & 0x7FFFFFFFu) == FT_DET_INF;
|
|
const uint32_t dt_inf = (dt & 0x7FFFFFFFu) == FT_DET_INF;
|
|
const uint32_t a_zero = ((a >> 23) & 0xFFu) == 0u;
|
|
uint32_t x_sign;
|
|
|
|
if (ft_det_is_nan(alpha) || ft_det_is_nan(dt))
|
|
return ft_det_nan_pick(alpha, dt);
|
|
if (ft_det_is_nan(a))
|
|
return a | FT_DET_QUIET;
|
|
if (alpha_inf && dt_inf && ((alpha ^ dt) & FT_DET_SIGN))
|
|
return FT_DET_DEFAULT_NAN;
|
|
if (!alpha_inf && !dt_inf) /* only a is infinite; softplus(finite) > 0 */
|
|
return (a & FT_DET_SIGN) ? 0u : FT_DET_INF;
|
|
x_sign = (alpha_inf ? alpha : dt) & FT_DET_SIGN;
|
|
if (x_sign) /* softplus(-Inf) = 0: a * 0 */
|
|
return ((a & 0x7FFFFFFFu) == FT_DET_INF) ? FT_DET_DEFAULT_NAN : FT_DET_ONE;
|
|
if (a_zero) /* 0 * Inf */
|
|
return FT_DET_DEFAULT_NAN;
|
|
return (a & FT_DET_SIGN) ? 0u : FT_DET_INF;
|
|
}
|
|
|
|
/**
|
|
* x = alpha + dt as an unrounded 32-bit mantissa: returns (E << 32) | m with |x| =
|
|
* m * 2^(E - 31), m bit 31 set, and the sign in *negative; 0 when x == 0. Exact whenever the
|
|
* operands overlap (cancellation), else truncated ~2^-30 relative. Finite inputs only.
|
|
*/
|
|
static inline uint64_t ft_det_fast_sum(uint32_t alpha, uint32_t dt, uint32_t *negative)
|
|
{
|
|
const uint32_t big = ((alpha & 0x7FFFFFFFu) >= (dt & 0x7FFFFFFFu)) ? alpha : dt;
|
|
const uint32_t small = (big == alpha) ? dt : alpha;
|
|
const uint32_t e_big = (big >> 23) & 0xFFu, e_small = (small >> 23) & 0xFFu;
|
|
const uint32_t shift = e_big - e_small;
|
|
uint32_t m_big = ((big << 8) | FT_DET_SIGN) >> 1, m_small = 0u, m;
|
|
uint64_t normalized;
|
|
|
|
if (e_big == 0u) /* both zero (DAZ) */
|
|
return 0u;
|
|
if (e_small != 0u && shift < 32u) /* exact when the operands overlap; else rounded */
|
|
m_small = shift ? ft_det_shift_round(((small << 8) | FT_DET_SIGN) >> 1, shift)
|
|
: ((small << 8) | FT_DET_SIGN) >> 1;
|
|
m = ((big ^ small) & FT_DET_SIGN) ? m_big - m_small : m_big + m_small;
|
|
if (m == 0u)
|
|
return 0u;
|
|
*negative = big & FT_DET_SIGN;
|
|
normalized = ft_det_fast_normalize(m);
|
|
return ft_det_unpacked((int32_t)e_big - 126 - (int32_t)(normalized >> 32),
|
|
(uint32_t)normalized);
|
|
}
|
|
|
|
/** 2^y for y = +-Y, Y in Q25 (sign of `a_bits`), rounded to fp32 with FTZ. */
|
|
static inline uint32_t ft_det_fast_exp2_q25(uint32_t y_q25, uint32_t a_bits)
|
|
{
|
|
const int32_t k = (int32_t)(y_q25 >> 25);
|
|
const uint32_t frac = y_q25 << 7;
|
|
|
|
if (!(a_bits & FT_DET_SIGN))
|
|
return ft_det_pack(0u, ft_det_unpacked(k, ft_det_fast_exp2_frac(frac)));
|
|
return ft_det_pack(0u, ft_det_unpacked(-k - (int32_t)(frac != 0u),
|
|
ft_det_fast_exp2_frac(0u - frac)));
|
|
}
|
|
|
|
/**
|
|
* decay = exp(ssm_a * softplus(alpha + dt)) as ONE fixed-point pipeline (no intermediate
|
|
* fp32 rounding): 2^(a * S(t)), |a S| accumulated in Q25 (saturating; flushes to +0 when the
|
|
* result is below 2^-126). alpha may be any fp32 (bf16-widened on the card).
|
|
*/
|
|
FT_DET_SHARED uint32_t ft_det_gdn_gate(uint32_t alpha, uint32_t dt, uint32_t ssm_a)
|
|
{
|
|
const uint32_t ea = (ssm_a >> 23) & 0xFFu;
|
|
const uint32_t a_mant = (ssm_a << 8) | FT_DET_SIGN;
|
|
uint32_t negative = 0u, log_q31 = 0u, y_q25 = 0u, term;
|
|
uint64_t x, split;
|
|
int32_t ex;
|
|
|
|
if (((alpha >> 23) & 0xFFu) == 0xFFu || ((dt >> 23) & 0xFFu) == 0xFFu || ea == 0xFFu)
|
|
return ft_det_fast_gate_special(alpha, dt, ssm_a);
|
|
if (ea == 0u) /* a == 0 (DAZ) */
|
|
return FT_DET_ONE;
|
|
x = ft_det_fast_sum(alpha, dt, &negative);
|
|
ex = ft_det_unpacked_exponent(x);
|
|
if (x == 0u) {
|
|
log_q31 = 0x7FFFFFFFu; /* S(0) = 1 */
|
|
} else if (ex <= 5) { /* |t| < 93: 2^-|t| still matters */
|
|
split = ft_det_fast_log2e_split((uint32_t)x, ex);
|
|
if ((split >> 32) < 32u) {
|
|
const uint32_t k = (uint32_t)(split >> 32), frac = (uint32_t)split;
|
|
const uint32_t u = (frac == 0u) ? (k == 0u ? 0xFFFFFFFFu : (FT_DET_SIGN >> (k - 1u)))
|
|
: (ft_det_fast_exp2_frac(0u - frac) >> k);
|
|
|
|
log_q31 = ft_det_fast_log2_1p(u); /* u = 2^-|t|, Q32 */
|
|
}
|
|
}
|
|
if (x != 0u && !negative) { /* + |a| t, from t's 32-bit mantissa */
|
|
const uint32_t t_mant = ft_det_mulhu_round((uint32_t)x, 0xB8AA3B29u); /* 2^(ex-30) */
|
|
|
|
y_q25 = ft_det_fast_scale(ft_det_mulhu_round(a_mant, t_mant), (int32_t)ea + ex - 131);
|
|
}
|
|
term = ft_det_fast_scale(ft_det_mulhu_round(a_mant, log_q31), (int32_t)ea - 132);
|
|
y_q25 = (y_q25 + term < y_q25) ? 0xFFFFFFFFu : y_q25 + term;
|
|
return ft_det_fast_exp2_q25(y_q25, ssm_a);
|
|
}
|
|
|
|
/** 1 / (1 + d) for d in (0, 1) Q32 -> Q32: linear seed, FT_DET_FAST_NEWTON_STEPS steps. */
|
|
static inline uint32_t ft_det_fast_reciprocal_1p(uint32_t d)
|
|
{
|
|
uint32_t q = 0xF0F0F0F1u - ft_det_mulhu(d, 0x78787878u); /* 16/17 - 8/17 d */
|
|
int step;
|
|
|
|
for (step = 0; step < FT_DET_FAST_NEWTON_STEPS; step++) {
|
|
const int32_t error = (int32_t)(0u - q - ft_det_mulhu(d, q)); /* 1 - (1+d) q */
|
|
|
|
q += (uint32_t)(int32_t)(((int64_t)error * (int64_t)q) >> 32);
|
|
}
|
|
return q;
|
|
}
|
|
|
|
/** beta = sigmoid(b), the card path: u = 2^-|t|, 1/(1+u) by Newton; u/(1+u) for b < 0. */
|
|
FT_DET_SHARED uint32_t ft_det_gdn_beta(uint32_t b)
|
|
{
|
|
const uint32_t magnitude = b & 0x7FFFFFFFu, negative = b & FT_DET_SIGN;
|
|
uint32_t k, frac, mant_u, d, q;
|
|
int32_t eu;
|
|
uint64_t split;
|
|
|
|
if (magnitude > FT_DET_INF)
|
|
return b | FT_DET_QUIET;
|
|
if (magnitude >= FT_DET_ABS_128) /* includes +-Inf */
|
|
return negative ? 0u : FT_DET_ONE;
|
|
if (magnitude < 0x00800000u) /* +-0, denormal (DAZ) */
|
|
return FT_DET_HALF;
|
|
split = ft_det_fast_log2e_split((b << 8) | FT_DET_SIGN, (int32_t)(magnitude >> 23) - 127);
|
|
k = (uint32_t)(split >> 32);
|
|
frac = (uint32_t)split;
|
|
if (frac == 0u && k == 0u)
|
|
return FT_DET_HALF;
|
|
mant_u = (frac == 0u) ? FT_DET_SIGN : ft_det_fast_exp2_frac(0u - frac);
|
|
eu = -(int32_t)k - (int32_t)(frac != 0u); /* u = mant_u 2^(eu-31) */
|
|
d = (eu > -32) ? (mant_u >> (uint32_t)(-1 - eu)) : 0u; /* u, Q32 */
|
|
if (d == 0u)
|
|
return negative ? ft_det_pack(0u, ft_det_unpacked(eu, mant_u)) : FT_DET_ONE;
|
|
q = ft_det_fast_reciprocal_1p(d);
|
|
if (!negative)
|
|
return ft_det_pack(0u, ft_det_unpacked(-1, q));
|
|
q = ft_det_mulhu(mant_u, q); /* u / (1 + u) = q 2^(eu-31), q in (2^30, 2^32) */
|
|
if (!(q & FT_DET_SIGN)) {
|
|
q <<= 1;
|
|
eu--;
|
|
}
|
|
return ft_det_pack(0u, ft_det_unpacked(eu, q));
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- Gated-DeltaNet gates
|
|
* The reference composites: each step rounded to fp32 exactly as the host spells it. */
|
|
|
|
/** beta = sigmoid(b) through the reference sigmoid. */
|
|
static inline uint32_t ft_det_gdn_beta_reference(uint32_t b)
|
|
{
|
|
return ft_det_sigmoid(b);
|
|
}
|
|
|
|
/** g = ssm_a * softplus(alpha + dt), each step one fp32 rounding as the host does it. */
|
|
static inline uint32_t ft_det_gdn_g(uint32_t alpha, uint32_t dt, uint32_t ssm_a)
|
|
{
|
|
return ft_det_mul(ssm_a, ft_det_softplus(ft_det_add(alpha, dt)));
|
|
}
|
|
|
|
/** decay = exp(g). */
|
|
static inline uint32_t ft_det_gdn_decay(uint32_t g)
|
|
{
|
|
return ft_det_exp(g);
|
|
}
|
|
|
|
#endif /* FT_DET_MATH_H */
|