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/LayerNorm64.lvt

namespace Livt.ML.Norm

using Livt.Math.FixedPoint
using Livt.ML.Storage

/**
 * Applies true learned LayerNorm to a fixed 64-value vector.
 *
 * Inputs use a caller-defined integer scale. The normalized intermediate,
 * output, gamma, and beta use Q8.8 scale 256. `epsilon` is expressed in squared
 * input units and should be derived when the activation scale is fixed. Callers
 * must bound inputs so the sum of 64 centered squares fits int32. Unconfigured
 * affine elements use gamma 256 and beta zero without reading unwritten RAM.
 */
component LayerNorm64
{
	// Compile-time dimensions, bounds, and numeric constants.
	public const VECTOR_SIZE: int = 64
	public const AFFINE_SCALE: int = 256
	const INPUT_OFFSET: int = 0
	const GAMMA_OFFSET: int = 64
	const BETA_OFFSET: int = 128
	const OUTPUT_OFFSET: int = 192
	const STORAGE_SIZE: int = 256

	// Owned RAM for input, learned affine values, and output.
	storage: Int32TensorRam2048

	// Marks affine lanes whose values have been written to storage.
	gammaConfigured: bool[64]

	// Owned arithmetic worker used by serialized operations.
	reciprocalSqrt: ReciprocalSqrtQ15

	/**
	 * Owns tensor RAM and the reciprocal-root helper. Unconfigured affine lanes use identity
	 * defaults at runtime; input cells must be written before Compute.
	 */
	new()
	{
		storage = new Int32TensorRam2048(STORAGE_SIZE)
		reciprocalSqrt = new ReciprocalSqrtQ15()
	}

	/**
	 * Stores one input element and ignores invalid indexes.
	 */
	public fn SetInput(index: int, value: int)
	{
		if (index < 0 || index >= VECTOR_SIZE) {
			return
		}

		storage.Write(INPUT_OFFSET + index, value)
	}

	/**
	 * Stores one Q8.8 learned gamma and beta pair.
	 */
	public fn SetAffine(index: int, gammaValue: int, betaValue: int)
	{
		if (index < 0 || index >= VECTOR_SIZE) {
			return
		}

		storage.Write(GAMMA_OFFSET + index, gammaValue)
		storage.Write(BETA_OFFSET + index, betaValue)
		gammaConfigured[index] = true
	}

	/**
	 * Copies complete Q8.8 gamma and beta vectors.
	 */
	public fn SetAffineValues(gammaValues: int[64], betaValues: int[64])
	{
		for (var index = 0; index < VECTOR_SIZE; index++) {
			storage.Write(GAMMA_OFFSET + index, gammaValues[index])
			storage.Write(BETA_OFFSET + index, betaValues[index])
			gammaConfigured[index] = true
		}
	}

	/**
	 * Computes LayerNorm; epsilon must be non-negative.
	 */
	public fn Compute(epsilon: int)
	{
		var sum = 0

		for (var index = 0; index < VECTOR_SIZE; index++) {
			var inputValue = storage.Read(INPUT_OFFSET + index)
			sum = sum + inputValue
		}

		var mean = sum / VECTOR_SIZE
		var squaredSum = 0

		for (var index = 0; index < VECTOR_SIZE; index++) {
			var inputValue = storage.Read(INPUT_OFFSET + index)
			var centered = inputValue - mean
			squaredSum = squaredSum + centered * centered
		}

		var variance = squaredSum / VECTOR_SIZE
		var boundedEpsilon = epsilon

		if (boundedEpsilon < 0) {
			boundedEpsilon = 0
		}

		var inverseStddev = reciprocalSqrt.Compute(variance + boundedEpsilon)

		for (var index = 0; index < VECTOR_SIZE; index++) {
			var inputValue = storage.Read(INPUT_OFFSET + index)
			var gammaValue = AFFINE_SCALE
			var betaValue = 0

			if (gammaConfigured[index]) {
				gammaValue = storage.Read(GAMMA_OFFSET + index)
				betaValue = storage.Read(BETA_OFFSET + index)
			}

			var centered = inputValue - mean
			var normalizationProduct = centered * inverseStddev

			state {
			}

			var normalized = normalizationProduct / 128
			var affineProduct = normalized * gammaValue

			state {
			}

			var outputValue = affineProduct / AFFINE_SCALE + betaValue
			storage.Write(OUTPUT_OFFSET + index, outputValue)
		}
	}

	/**
	 * Copies one complete vector, computes LayerNorm, and stores the outputs.
	 */
	public fn ComputeValues(values: int[64], epsilon: int)
	{
		for (var index = 0; index < VECTOR_SIZE; index++) {
			storage.Write(INPUT_OFFSET + index, values[index])
		}

		this.Compute(epsilon)
	}

	/**
	 * Returns one Q8.8 normalized output, or zero for an invalid index.
	 */
	public fn GetOutput(index: int) int
	{
		if (index < 0 || index >= VECTOR_SIZE) {
			return 0
		}

		return storage.Read(OUTPUT_OFFSET + index)
	}
}