/* 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 . * * 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 #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 */