Livt packages
Livt.ML
Version 1.6.1
Fixed-size quantized inference, tensor storage, attention, and transformer building blocks for Livt.
Published files (132)
src/activation/MaskedFixedSoftmax.lvt
namespace Livt.ML.Activation
using Livt.Math.Arithmetic
using Livt.ML.Storage
using Livt.IO
/**
* Stable masked fixed-point softmax. Probabilities are individually ties-even; no sum correction.
*/
component MaskedFixedSoftmax<CAPACITY: int, BITS: int, FRACTION: int,
EXP_FRACTION: int, PROB_FRACTION: int, STEP_BITS: int, CUTOFF: int,
TABLE: ILookupTable, STORAGE: IRam<int>>
{
static assert CAPACITY > 0 && CAPACITY <= 65536, "invalid softmax capacity"
static assert BITS >= 2 && BITS <= 30, "invalid softmax score width"
static assert FRACTION >= 0 && FRACTION < BITS, "invalid softmax score fraction"
static assert EXP_FRACTION >= 0 && EXP_FRACTION <= 30, "invalid exponent fraction"
static assert PROB_FRACTION >= 0 && PROB_FRACTION <= 30, "invalid probability fraction"
static assert STEP_BITS >= 0 && STEP_BITS <= 20, "invalid softmax table step"
static assert CUTOFF >= 0 && CUTOFF <= 1073741823, "invalid softmax cutoff"
// Symmetric raw activation magnitude allowed by the configured bit width.
public const LIMIT: int = (1 << (BITS - 1)) - 1
// Borrowed score/mask/output memory and initialized exponent table.
table: TABLE
storage: STORAGE
// Owned arithmetic worker used by serialized operations.
math: WideArithmetic<31>
/**
* Borrows the exponent table and word-addressed scratch storage; owns the wide arithmetic
* worker. Initialize table and input spans before Compute and serialize calls.
*/
new(table: TABLE, storage: STORAGE)
{
this.table = table
this.storage = storage
math = new WideArithmetic<31>()
}
/**
* Checks endpoint addresses and addition overflow for a word span. Compute supplies a
* positive length; storage must expose a contiguous address range.
*/
fn Valid(base: int, length: int) bool
{
if (base < 0 || base > 2147483647 - length) {
return false
}
return storage.IsValidAddress(base) && storage.IsValidAddress(base + length - 1)
}
/**
* Looks up exp(-delta) for a nonnegative score difference, rounding the table index to
* even. Returns zero beyond CUTOFF, or -1 for an unavailable entry.
*/
fn Exponent(delta: int) int
{
if (delta > CUTOFF) {
return 0
}
var index = math.DivideEven(delta as logic[64], (1 << STEP_BITS) as logic[64]) as int
if (index < 0 || index >= table.Length()) {
return -1
}
return table.Read(index)
}
/**
* Three disjoint length-element spans: Q scores, masks (zero=false), unsigned probabilities.
*/
public fn Compute(length: int, scores: int, masks: int, output: int) bool
{
if (length <= 0 || length > CAPACITY) {
return false
}
if (!Valid(scores, length) || !Valid(masks, length) || !Valid(output, length)) {
return false
}
if ((scores < masks + length &&
masks < scores + length) ||
(scores < output + length &&
output < scores + length) ||
(masks < output + length &&
output < masks + length)) {
return false
}
if (table.Length() <= 0 || table.Read(0) != (1 << EXP_FRACTION)) {
return false
}
var maximum = -LIMIT
var any = false
for (var i = 0; i < length; i++) {
var value = storage.Read(scores + i)
if (value < -LIMIT || value > LIMIT) {
return false
}
if (storage.Read(masks + i) != 0) {
any = true
if (value > maximum) {
maximum = value
}
}
}
if (!any) {
return false
}
var denominator: logic[64] = 0
for (var i = 0; i < length; i++) {
if (storage.Read(masks + i) != 0) {
var exponent = Exponent(maximum - storage.Read(scores + i))
if (exponent < 0 || exponent > (1 << EXP_FRACTION)) {
return false
}
denominator = math.Add(denominator, exponent as logic[64])
}
}
for (var i = 0; i < length; i++) {
var probability = 0
if (storage.Read(masks + i) != 0) {
var exponent = Exponent(maximum - storage.Read(scores + i))
var numerator = math.Multiply(exponent as logic[64], (1 << PROB_FRACTION) as logic[64])
probability = math.DivideEven(numerator, denominator) as int
}
storage.Write(output + i, probability)
}
return true
}
}