mixle.engines.lns module¶
Logarithmic number system for mixle’s log-space compute – quantize by a fixed log constant ln(C).
mixle already works in log-space (log-densities, log-weights, log-sum-exp), so the natural quantization is
on the log value: store v as the integer k = round(ln(v) / s) with step s = ln(C), i.e.
v = C**k. Then the two hot operations become integer arithmetic:
multiplying probabilities = adding log-probs = adding the integers
k1 + k2– exact, no table.log-sum-exp (adding probabilities – the mixture / HMM / marginalization op) becomes
max(k1, k2) + LUT[|k1 - k2|]whereLUT[d] = round(log(1 + exp(-d*s)) / s)is a small precomputed integer table (the Gaussian logarithm). Noexp, nolog– integermax+ a gather. This is the transcendental reduction that dominates mixture scoring, and unlike a GEMM it has no BLAS to lose to: measured ~4x faster than float64logsumexpin pure numpy (more with a compiled integer kernel).
step is the precision dial – the fp1..fpN spectrum, but in the log domain where it is natural: each
unit is a factor of C = exp(step), so the relative precision of a stored value is ~``step/2`` and the
log-sum-exp error is bounded by ~``step``. Smaller step -> finer + wider integer range (int16 at step~0.1,
int32 at step~1e-3). The model’s log-parameters and the data terms are quantized by the SAME step, so the
whole score is integer arithmetic.
A true zero (log(0) = -inf) has no finite code, so it gets one reserved sentinel, LOG_ZERO_CODE,
and every arithmetic entry point (quantize, logadd, multiply, the compiled kernel) special
cases it explicitly before doing any int64 math – see the constants below and MXR-080-0138.
- class LogNumberSystem(step=1e-2)[source]
Bases:
objectQuantize log-space values to integers in units of
step = ln(C)and compute on the integers.- Parameters:
step (float)
- CODE_MIN = np.int64(-2305843009213693952)
- CODE_MAX = np.int64(2305843009213693952)
- classmethod from_relative_precision(rel)[source]
Build a system whose stored values are accurate to ~``rel`` relative (
step = ln(1+rel)).- Parameters:
rel (float)
- Return type:
LogNumberSystem
- max_logsumexp_error(n)[source]
Certified bound on the absolute log-sum-exp error for reducing
nquantized terms.Two error sources compound over the pairwise reduction tree (see
logsumexp()):input quantization – each leaf starts up to
step/2away from its true log-value.LUT rounding – each
logadd()looks upround(log1p(exp(-d*step)) / step), itself withinstep/2of the true correction (by construction: the table’s last entry is exactly 0, anddmaxis sized so the true correction is already< step/2there, so clipping a larger gap todmaxcosts less error than the in-table rounding it stands in for).
logsumexp(a, b) = log(exp(a) + exp(b))is monotonic and shift-equivariant in each argument (logsumexp(a+c, b+c) = logsumexp(a, b) + c), which gives an EXACT bound – not a first-order approximation – on how far alogadd()of already-approximate operands can land from the true value of the pair they represent: at most the WORSE of the two operands’ existing error. Errors don’t add across siblings, only the fresh per-node LUT rounding does. So afterdtree levels the worst-case error isstep/2(leaf quantization) plusdmore halves ofstep(one fresh LUT rounding per level on the critical path):(d + 1) * step / 2.Both reduction trees here (the numpy fallback and the compiled kernel) combine adjacent pairs and carry any odd leftover forward unchanged, so they are always balanced: the deepest element crosses exactly
d = ceil(log2(n))levels regardless of hownfactors, and this bound is a valid certificate for anyn– not just the 2-term case the previous constant1.5 * stepbound was actually sized for (equivalent to this formula atnin{3, 4}; already violated by measured error at, e.g.,n=8– see MXR-080-0139 and the regression tests).
- quantize(log_values)[source]
Round log-space values to integer multiples of
step(the stored representation).Exact
-inf(the log of a true zero) maps to the reservedLOG_ZERO_CODEsentinel – never through a float-to-int64 cast, whose behavior for a non-finite input this module does not rely on (see MXR-080-0138). NaN and+infhave no LNS code and raise. Any other value that would round outside[CODE_MIN, CODE_MAX]is rejected: clipping would invalidate the format’s quantization-error certificate.
- dequantize(k)[source]
Recover the float log-value
k * step; the reserved zero sentinel maps back to exactly-inf.
- logadd(k1, k2)[source]
Integer Gaussian logarithm:
logsumexpof two quantized log-values ->max + LUT[|diff|].LOG_ZERO_CODE(log of an exact zero) is absorbing – zero-plus-anything is the other operand, computed with no LUT lookup at all. Ordinary operands must already be canonical, i.e. inside[CODE_MIN, CODE_MAX]: D-0129 rejects a non-canonical public code rather than saturating it, so that the published rounding bound applies to every accepted value. That is what keeps the|k1 - k2|difference below from overflowing int64 (MXR-080-0138) – an overflowed, wrapped-negative difference would otherwise become the LUT index a few lines down. The derived sum does saturate to the range, since it is the library’s own value, not the caller’s. (The compiled_logaddclamps its operands instead of rejecting them: it is a boundscheck-disabled kernel reached through this validation, and clamping is its last-ditch memory-safety guard, not the public contract.)
- multiply(k1, k2)[source]
Integer code for the PRODUCT of two LNS values:
log(a*b) = log(a) + log(b)-> code add.Exact for ordinary operands (integer add, no LUT, no rounding).
LOG_ZERO_CODEis absorbing (anything times zero is zero); an ordinary sum that would leave[CODE_MIN, CODE_MAX]saturates to that boundary rather than silently wrapping through int64 overflow (MXR-080-0138). Prefer this over rawk1 + k2for any code that might be (or derive from) a quantized true zero.
- logsumexp(k, axis=-1)[source]
Integer log-sum-exp along
axisvia a pairwise tree oflogadd()(no exp/log).Uses the compiled one-pass tree kernel for the common 2-D last-axis reduction when available (bit-identical to the numpy tree, ~8x faster); falls back to vectorized numpy otherwise. Both trees combine adjacent pairs and carry an odd leftover element forward unchanged, so the deepest element always makes exactly
ceil(log2(m))logadd hops (m= reduced axis length) – seemax_logsumexp_error(), which bounds the resulting error in terms of that depth.
- integer_dtype(log_range)[source]
Smallest signed integer dtype that holds log-values spanning
[-log_range, log_range].Sized for ordinary codes only:
LOG_ZERO_CODEis representable in int64 alone, so a caller that needs to store exact zeros (rather than pass them straight throughquantize/dequantize) should not narrow the returned dtype below int64.