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/norm/FixedRmsNorm.lvt

namespace Livt.ML.Norm

using Livt.Math.Arithmetic
using Livt.IO

/**
 * Affine RMS normalization over injected storage, with explicit fixed-point boundaries.
 */
component FixedRmsNorm<LENGTH: int, BITS: int, FRACTION: int, EPSILON: int, STORAGE: IRam<int>>
{
	static assert LENGTH > 0 && LENGTH <= 65536, "invalid RMS length"

	static assert BITS >= 2 && BITS <= 30, "invalid RMS activation width"

	static assert FRACTION >= 0 && FRACTION < BITS, "invalid RMS fraction"

	static assert EPSILON > 0, "RMS epsilon must be positive"

	static assert 2 * BITS + Bits.RequiredFor(LENGTH) <= 64, "unsafe RMS accumulation"

	// Symmetric raw activation magnitude allowed by the configured bit width.
	public const LIMIT: int = (1 << (BITS - 1)) - 1

	// Borrowed input, affine-weight, and output memory in int32 words.
	storage: STORAGE

	// Owned arithmetic worker used by serialized operations.
	math: WideArithmetic<BITS>

	/**
	 * Borrows the configured storage and owns the wide arithmetic worker. Load inputs and
	 * affine weights before Compute; serialize calls.
	 */
	new(storage: STORAGE)
	{
		this.storage = storage
		math = new WideArithmetic<BITS>()
	}

	/**
	 * Checks address overflow and endpoints for a positive word span. Assumes storage exposes
	 * a contiguous valid address range.
	 */
	fn Valid(base: int) bool
	{
		if (base < 0 || base > 2147483647 - LENGTH) {
			return false
		}

		return storage.IsValidAddress(base) && storage.IsValidAddress(base + LENGTH - 1)
	}

	/**
	 * Three disjoint LENGTH-element spans. False rejects invalid ranges or inputs.
	 */
	public fn Compute(inputBase: int, gammaBase: int, outputBase: int) bool
	{
		if (!Valid(inputBase) || !Valid(gammaBase) || !Valid(outputBase)) {
			return false
		}

		if ((inputBase < gammaBase + LENGTH &&
			gammaBase < inputBase + LENGTH) ||
			(inputBase < outputBase + LENGTH &&
			outputBase < inputBase + LENGTH) ||
			(gammaBase < outputBase + LENGTH &&
			outputBase < gammaBase + LENGTH)) {
			return false
		}

		var sum: logic[64] = 0

		for (var i = 0; i < LENGTH; i++) {
			var value = storage.Read(inputBase + i)
			var gamma = storage.Read(gammaBase + i)

			if (value < -LIMIT || value > LIMIT || gamma < -LIMIT || gamma > LIMIT) {
				return false
			}

			var square = math.Multiply(value as logic[64], value as logic[64])
			sum = math.Add(sum, square)
		}

		var mean = math.DivideFloor(sum, LENGTH as logic[64])
		var root = math.Sqrt(math.Add(mean, EPSILON as logic[64]))

		for (var i = 0; i < LENGTH; i++) {
			var value = storage.Read(inputBase + i)
			var gamma = storage.Read(gammaBase + i)
			var numerator = math.Multiply(value as logic[64], (1 << FRACTION) as logic[64])
			var normalized = math.Clamp(math.DivideEven(numerator, root))
			var affine = math.Multiply(normalized as logic[64], gamma as logic[64])
			var result = math.Clamp(math.DivideEven(affine, (1 << FRACTION) as logic[64]))
			storage.Write(outputBase + i, result)
		}

		return true
	}
}