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/transformer/FixedTransformerKernels.lvt
namespace Livt.ML.Transformer
using Livt.IO
using Livt.ML.Storage
using Livt.Math.Arithmetic
using Livt.ML.Classifier
/**
* Serial correctness implementation over external memory; one instance serves all layers.
* Unscaled attention, affine RMSNorm, separately rounded GELU/gate and residuals.
* Does not allocate model-sized on-chip arrays. Two scratch rows use borrowed scheduled RAM, never function-local arrays.
*/
component FixedTransformerKernels<MAX_TOKENS: int, MAX_WIDTH: int, BITS: int,
FRACTION: int, PROB_FRACTION: int, EPSILON: int,
WEIGHTS: IWeightTensor, TABLES: IFixedTransformerTables, MEMORY: IRam<int>,
SCRATCH: IRam<int>> : IIncrementalTransformerKernels
{
static assert MAX_TOKENS > 0 && MAX_TOKENS <= 1024, "invalid token capacity"
static assert MAX_WIDTH > 0 && MAX_WIDTH <= 65536, "invalid vector capacity"
static assert BITS >= 2 && BITS <= 30, "invalid activation width"
static assert FRACTION >= 0 && FRACTION < BITS, "invalid activation fraction"
static assert PROB_FRACTION >= 0 && PROB_FRACTION < BITS - 1, "invalid probability width"
static assert EPSILON > 0, "epsilon must be positive"
static assert 2 * BITS + Bits.RequiredFor(MAX_WIDTH) <= 64, "unsafe accumulation"
static assert 2 * BITS + Bits.RequiredFor(MAX_TOKENS) <= 64, "unsafe context accumulation"
// Symmetric raw activation magnitude allowed by the configured bit width.
public const LIMIT: int = (1 << (BITS - 1)) - 1
// Borrowed model providers and activation memory; callers serialize access.
weights: WEIGHTS
tables: TABLES
memory: MEMORY
// Owned wide arithmetic, shape division, and streaming argmax workers.
math: WideArithmetic<BITS>
divider: UnsignedDivision<31>
argmax: StreamingArgMax
// Exclusive scratch: scores at 0, probabilities at MAX_TOKENS.
scratch: SCRATCH
/**
* Borrows providers, activation memory and exclusive scratch for the instance lifetime.
* Scratch must expose contiguous addresses 0..2*MAX_TOKENS-1 and must not alias activation
* memory. Serialize every kernel operation and all scratch access. Reads/writes complete
* through the scheduled IRam contract; storage latency changes cycles, not arithmetic.
* Reset caller and RAM transaction state together. Cells need no reset: each attention
* query writes every consumed slot before reading it, including after failure/reset.
*/
new(weights: WEIGHTS, tables: TABLES, memory: MEMORY, scratch: SCRATCH)
{
this.weights = weights
this.tables = tables
this.memory = memory
this.scratch = scratch
math = new WideArithmetic<BITS>()
divider = new UnsignedDivision<31>()
argmax = new StreamingArgMax()
}
/**
* Checks the symmetric raw activation range -LIMIT..LIMIT.
*/
static fn ValueValid[](x: int) bool
{
return x >= -LIMIT && x <= LIMIT
}
/**
* Checks a nonempty word span for address overflow and valid endpoints. Assumes the memory
* exposes a contiguous valid range.
*/
fn Span(base: int, count: int) bool
{
if (base < 0 || count <= 0 || base > 2147483647 - count) {
return false
}
return memory.IsValidAddress(base) && memory.IsValidAddress(base + count - 1)
}
/**
* Tests intersection of two half-open word spans whose endpoints were validated by Span.
*/
static fn Overlap[](a: int, na: int, b: int, nb: int) bool
{
return a < b + nb && b < a + na
}
/**
* Checks positive token and vector dimensions against MAX_TOKENS and MAX_WIDTH.
*/
static fn Shape[](length: int, width: int) bool
{
return length > 0 && length <= MAX_TOKENS && width > 0 && width <= MAX_WIDTH
}
/**
* Divides a signed wide intermediate by 2^fraction with ties to even, then clamps to the
* symmetric activation range. Requires a supported nonnegative shift.
*/
fn Round(value: logic[64], fraction: int) int
{
return math.Clamp(math.DivideEven(value, (1 << fraction) as logic[64]))
}
/**
* Copies length embedding rows selected by token IDs into a length-by-width target.
* Addresses count int32 words; target must not overlap the token span. Returns true after
* all writes; false may leave partial output.
*/
override fn Embed(tensor: int, tokens: int, target: int, length: int, width: int) bool
{
if (!Shape(length, width) || !Span(tokens, length) || !Span(target, length * width)) {
return false
}
if (Overlap(tokens, length, target, length * width) || !weights.Select(tensor)) {
return false
}
if (weights.Columns() != width) {
return false
}
for (var t = 0; t < length; t++) {
var id = memory.Read(tokens + t)
if (id < 0 || id >= weights.Rows()) {
return false
}
}
for (var t = 0; t < length; t++) {
var id = memory.Read(tokens + t)
for (var i = 0; i < width; i++) {
var x = weights.Read(id * width + i)
if (weights.Failed() || !ValueValid(x)) {
return false
}
memory.Write(target + t * width + i, x)
}
}
return true
}
/**
* Projects length token-major vectors through a rows-by-columns weight tensor. Source and
* target word spans must be disjoint. Returns true after all rows are written; false may
* leave partial output.
*/
override fn Linear(tensor: int, source: int, target: int, length: int, rows: int, columns: int) bool
{
var rowsValid = Shape(length, rows)
var columnsValid = Shape(length, columns)
if (!rowsValid || !columnsValid) {
return false
}
if (!Span(source, length * columns) || !Span(target, length * rows)) {
return false
}
if (Overlap(source, length * columns, target, length * rows) || !weights.Select(tensor)) {
return false
}
if (weights.Rows() != rows || weights.Columns() != columns) {
return false
}
for (var t = 0; t < length; t++) {
for (var r = 0; r < rows; r++) {
var sum: logic[64] = 0
for (var c = 0; c < columns; c++) {
var x = memory.Read(source + t * columns + c)
var w = weights.Read(r * columns + c)
var xValid = ValueValid(x)
var wValid = ValueValid(w)
if (weights.Failed() || !xValid || !wValid) {
return false
}
sum = math.Add(sum, math.Multiply(x as logic[64], w as logic[64]))
}
memory.Write(target + t * rows + r, Round(sum, FRACTION))
}
}
return true
}
/**
* Applies affine RMS normalization to length vectors of width elements, using a 1-by-width
* weight tensor. Source and target word spans must be disjoint. True means completed
* writes; false may leave partial output.
*/
override fn Norm(tensor: int, source: int, target: int, length: int, width: int) bool
{
if (!Shape(length, width) || !Span(source, length * width) || !Span(target, length * width)) {
return false
}
if (Overlap(source, length * width, target, length * width) || !weights.Select(tensor)) {
return false
}
if (weights.Rows() != 1 || weights.Columns() != width) {
return false
}
for (var t = 0; t < length; t++) {
var sum: logic[64] = 0
for (var i = 0; i < width; i++) {
var x = memory.Read(source + t * width + i)
if (!ValueValid(x)) {
return false
}
sum = math.Add(sum, math.Multiply(x as logic[64], x as logic[64]))
}
var mean = math.DivideFloor(sum, width as logic[64])
var root = math.Sqrt(math.Add(mean, EPSILON as logic[64]))
for (var i = 0; i < width; i++) {
var affineInput = memory.Read(source + t * width + i)
var gamma = weights.Read(i)
if (weights.Failed() || !ValueValid(gamma)) {
return false
}
var numerator = math.Multiply(affineInput as logic[64], (1 << FRACTION) as logic[64])
var normalized = math.Clamp(math.DivideEven(numerator, root))
var result = Round(math.Multiply(normalized as logic[64], gamma as logic[64]), FRACTION)
memory.Write(target + t * width + i, result)
}
}
return true
}
/**
* Writes count saturated elementwise sums. Target may equal either input base, but partial
* overlap is rejected. Addresses count int32 words. True means completed writes; false may
* leave partial output.
*/
override fn Add(a: int, b: int, target: int, count: int) bool
{
if (count <= 0 || count > MAX_TOKENS * MAX_WIDTH) {
return false
}
if (!Span(a, count) || !Span(b, count) || !Span(target, count)) {
return false
}
var aOverlaps = Overlap(a, count, target, count)
var bOverlaps = Overlap(b, count, target, count)
if ((a != target && aOverlaps) || (b != target && bOverlaps)) {
return false
}
for (var i = 0; i < count; i++) {
var x = memory.Read(a + i)
var y = memory.Read(b + i)
var xValid = ValueValid(x)
var yValid = ValueValid(y)
if (!xValid || !yValid) {
return false
}
memory.Write(target + i, math.Clamp(math.Add(x as logic[64], y as logic[64])))
}
return true
}
/**
* Writes GELU(a) multiplied by b for count fixed-point elements. All addresses count int32
* words; target must be disjoint from both inputs. True means completed writes; false may
* leave partial output.
*/
override fn Gate(a: int, b: int, target: int, count: int) bool
{
if (count <= 0 || count > MAX_TOKENS * MAX_WIDTH) {
return false
}
if (!Span(a, count) || !Span(b, count) || !Span(target, count)) {
return false
}
var aOverlaps = Overlap(a, count, target, count)
var bOverlaps = Overlap(b, count, target, count)
if (aOverlaps || bOverlaps) {
return false
}
for (var i = 0; i < count; i++) {
var x = memory.Read(a + i)
var y = memory.Read(b + i)
var xValid = ValueValid(x)
var yValid = ValueValid(y)
if (!xValid || !yValid) {
return false
}
var activated = tables.Gelu(x)
if (!ValueValid(activated)) {
return false
}
memory.Write(target + i, Round(math.Multiply(activated as logic[64], y as logic[64]), FRACTION))
}
return true
}
/**
* Computes unscaled attention over token-major Q/K/V, with heads groups of width features
* and nonzero key masks. Uses learned relative-position bias. Target must be disjoint from
* Q/K/V and masks; at least one key must be enabled. True means completed writes; false
* may leave partial output.
*/
override fn Attention(tensor: int, q: int, k: int, v: int, target: int, masks: int,
length: int, heads: int, width: int) bool
{
if (!scratch.IsValidAddress(0) || !scratch.IsValidAddress(2 * MAX_TOKENS - 1)) {
return false
}
if (!Shape(length, width)) {
return false
}
if (heads <= 0 || heads > (divider.Divide(MAX_WIDTH as uint, width as uint) as int)) {
return false
}
var features = heads * width
var count = length * features
if (!Span(q, count) || !Span(k, count) || !Span(v, count) || !Span(target, count) || !Span(masks, length)) {
return false
}
var qOverlaps = Overlap(q, count, target, count)
var kOverlaps = Overlap(k, count, target, count)
var vOverlaps = Overlap(v, count, target, count)
var masksOverlaps = Overlap(masks, length, target, count)
if (qOverlaps || kOverlaps || vOverlaps || masksOverlaps) {
return false
}
if (!weights.Select(tensor) || weights.Columns() != heads) {
return false
}
var any = false
for (var i = 0; i < length; i++) {
if (memory.Read(masks + i) != 0) {
any = true
}
}
if (!any) {
return false
}
for (var h = 0; h < heads; h++) {
for (var t = 0; t < length; t++) {
var maximum = -LIMIT
for (var j = 0; j < length; j++) {
var sum: logic[64] = 0
for (var d = 0; d < width; d++) {
var x = memory.Read(q + t * features + h * width + d)
var y = memory.Read(k + j * features + h * width + d)
var xValid = ValueValid(x)
var yValid = ValueValid(y)
if (!xValid || !yValid) {
return false
}
sum = math.Add(sum, math.Multiply(x as logic[64], y as logic[64]))
}
var bucket = tables.Bucket(j - t)
if (bucket < 0 || bucket >= weights.Rows()) {
return false
}
var bias = weights.Read(bucket * heads + h)
if (weights.Failed() || !ValueValid(bias)) {
return false
}
var score = Round(sum, FRACTION)
score = math.Clamp(math.Add(score as logic[64], bias as logic[64]))
scratch.Write(j, score)
if (memory.Read(masks + j) != 0 && score > maximum) {
maximum = score
}
}
var denominator: logic[64] = 0
for (var j = 0; j < length; j++) {
var exponent = 0
if (memory.Read(masks + j) != 0) {
exponent = tables.Exponent(maximum - scratch.Read(j))
if (exponent < 0) {
return false
}
}
scratch.Write(MAX_TOKENS + j, exponent)
denominator = math.Add(denominator, exponent as logic[64])
}
if (denominator == 0) {
return false
}
for (var j = 0; j < length; j++) {
var numerator = math.Multiply(scratch.Read(MAX_TOKENS + j) as logic[64], (1 << PROB_FRACTION) as logic[64])
var probability = math.DivideEven(numerator, denominator) as int
scratch.Write(MAX_TOKENS + j, probability)
if (probability < 0 || probability > (1 << PROB_FRACTION)) {
return false
}
}
for (var d = 0; d < width; d++) {
var contextSum: logic[64] = 0
for (var j = 0; j < length; j++) {
var value = memory.Read(v + j * features + h * width + d)
if (!ValueValid(value)) {
return false
}
contextSum = math.Add(contextSum, math.Multiply(scratch.Read(MAX_TOKENS + j) as logic[64], value as logic[64]))
}
memory.Write(target + t * features + h * width + d, Round(contextSum, PROB_FRACTION))
}
}
}
return true
}
/**
* Computes one query against length token-major cached K/V vectors. Width is features per
* head; position is the query token position. Causal mode excludes keys beyond position;
* zero masks exclude keys. Tensor -1 disables bias, otherwise buckets holds 2*MAX_TOKENS-1
* entries centered on relative position zero. Target must not overlap any consumed input
* span. True means completed writes; false may leave partial output.
*/
override fn CachedAttention(tensor: int, q: int, k: int, v: int, target: int, masks: int,
length: int, heads: int, width: int, position: int, causal: bool, buckets: int) bool
{
if (!scratch.IsValidAddress(0) || !scratch.IsValidAddress(2 * MAX_TOKENS - 1)) {
return false
}
if (!Shape(length, width) || position < 0 || position >= MAX_TOKENS || tensor < -1) {
return false
}
if (heads <= 0 || heads > (divider.Divide(MAX_WIDTH as uint, width as uint) as int)) {
return false
}
var features = heads * width
var count = length * features
if (!Span(q, features) || !Span(k, count) || !Span(v, count) || !Span(target, features) || !Span(masks, length)) {
return false
}
var qOverlaps = Overlap(q, features, target, features)
var kOverlaps = Overlap(k, count, target, features)
var vOverlaps = Overlap(v, count, target, features)
var masksOverlaps = Overlap(masks, length, target, features)
if (qOverlaps || kOverlaps || vOverlaps || masksOverlaps) {
return false
}
if (tensor >= 0) {
if (!Span(buckets, 2 * MAX_TOKENS - 1)) {
return false
}
if (Overlap(buckets, 2 * MAX_TOKENS - 1, target, features)) {
return false
}
if (!weights.Select(tensor) || weights.Columns() != heads) {
return false
}
}
var any = false
for (var i = 0; i < length; i++) {
if ((!causal || i <= position) && memory.Read(masks + i) != 0) {
any = true
}
}
if (!any) {
return false
}
for (var h = 0; h < heads; h++) {
var maximum = -LIMIT
for (var j = 0; j < length; j++) {
scratch.Write(j, 0)
if ((!causal || j <= position) && memory.Read(masks + j) != 0) {
var sum: logic[64] = 0
for (var d = 0; d < width; d++) {
var x = memory.Read(q + h * width + d)
var y = memory.Read(k + j * features + h * width + d)
var xValid = ValueValid(x)
var yValid = ValueValid(y)
if (!xValid || !yValid) {
return false
}
sum = math.Add(sum, math.Multiply(x as logic[64], y as logic[64]))
}
var score = Round(sum, FRACTION)
if (tensor >= 0) {
var bucket = memory.Read(buckets + j - position + MAX_TOKENS - 1)
if (bucket < 0 || bucket >= weights.Rows()) {
return false
}
var bias = weights.Read(bucket * heads + h)
if (weights.Failed() || !ValueValid(bias)) {
return false
}
score = math.Clamp(math.Add(score as logic[64], bias as logic[64]))
}
scratch.Write(j, score)
if (score > maximum) {
maximum = score
}
}
}
var denominator: logic[64] = 0
for (var j = 0; j < length; j++) {
var exponent = 0
if ((!causal || j <= position) && memory.Read(masks + j) != 0) {
exponent = tables.Exponent(maximum - scratch.Read(j))
if (exponent < 0) {
return false
}
}
scratch.Write(MAX_TOKENS + j, exponent)
denominator = math.Add(denominator, exponent as logic[64])
}
if (denominator == 0) {
return false
}
for (var j = 0; j < length; j++) {
var numerator = math.Multiply(scratch.Read(MAX_TOKENS + j) as logic[64], (1 << PROB_FRACTION) as logic[64])
var probability = math.DivideEven(numerator, denominator) as int
scratch.Write(MAX_TOKENS + j, probability)
if (probability < 0 || probability > (1 << PROB_FRACTION)) {
return false
}
}
for (var d = 0; d < width; d++) {
var contextSum: logic[64] = 0
for (var j = 0; j < length; j++) {
if ((!causal || j <= position) && memory.Read(masks + j) != 0) {
var value = memory.Read(v + j * features + h * width + d)
if (!ValueValid(value)) {
return false
}
contextSum = math.Add(contextSum, math.Multiply(scratch.Read(MAX_TOKENS + j) as logic[64], value as logic[64]))
}
}
memory.Write(target + h * width + d, Round(contextSum, PROB_FRACTION))
}
}
return true
}
/**
* Projects one columns-element vector through rows weight rows without storing logits.
* Returns the lowest row index among equal rounded/clipped maxima, or -1 on invalid shape,
* storage, or weights. Does not write activation memory.
*/
override fn ProjectArgMax(tensor: int, source: int, rows: int, columns: int) int
{
if (rows <= 0 || rows > 65536 || columns <= 0 || columns > MAX_WIDTH) {
return -1
}
if (!Span(source, columns) || !weights.Select(tensor)) {
return -1
}
if (weights.Rows() != rows || weights.Columns() != columns) {
return -1
}
// The earlier row bound is 65536. Smaller configured widths prove safety
// at compile time; larger configurations retain an exact scheduled guard.
static if (MAX_WIDTH > 32767) {
if (rows > (divider.Divide(2147483647 as uint, columns as uint) as int)) {
return -1
}
}
argmax.Reset()
for (var row = 0; row < rows; row++) {
var sum: logic[64] = 0
for (var c = 0; c < columns; c++) {
var x = memory.Read(source + c)
var w = weights.Read(row * columns + c)
var xValid = ValueValid(x)
var wValid = ValueValid(w)
if (weights.Failed() || !xValid || !wValid) {
return -1
}
sum = math.Add(sum, math.Multiply(x as logic[64], w as logic[64]))
}
argmax.Accept(row, Round(sum, FRACTION))
}
return argmax.GetIndex()
}
}