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/convolution/QLinearConv1x8Same5x5Ram.lvt

namespace Livt.ML.Convolution

using Livt.ML.Numeric
using Livt.ML.Storage

/**
 * RAM-backed non-streaming QLinearConv from one 28x28 plane to eight planes.
 */
component QLinearConv1x8Same5x5Ram
{
	// Owned scheduled requantizer; processing holds input acceptance until completion.
	requantize: ScheduledRequantizeUInt8

	// Compile-time dimensions, bounds, and numeric constants.
	public const INPUT_SIZE: int = 784
	public const OUTPUT_SIZE: int = 6272
	public const CHANNELS: int = 8
	public const KERNEL_SIZE: int = 25

	// Input tensor storage; populate the required span before computation.
	inputValues: ByteTensorRam2048

	// Configured coefficients and numeric scaling parameters.
	weights: ByteTensorRam2048

	// Owned output tensor RAM; Compute writes the result before readback.
	outputValues: ByteTensorRam8192

	// Configured coefficients and numeric scaling parameters.
	biases: int[8]
	multipliers: int[8]
	divisors: int[8]
	inputZeroPoints: int[8]
	weightZeroPoints: int[8]
	outputZeroPoints: int[8]

	/**
	 * Allocates the owned input, weight, and output RAMs. Load inputs and configure all
	 * required parameters before Compute; construction does not initialize RAM contents.
	 */
	new()
	{
		requantize = new ScheduledRequantizeUInt8()
		this.inputValues = new ByteTensorRam2048(INPUT_SIZE)
		this.weights = new ByteTensorRam2048(200)
		this.outputValues = new ByteTensorRam8192(OUTPUT_SIZE)
	}

	/**
	 * Stores one flattened uint8 input value and ignores an invalid index.
	 */
	public fn SetInput(index: int, value: int)
	{
		if (index < 0) {
			return
		}

		if (index >= INPUT_SIZE) {
			return
		}

		this.inputValues.Write(index, value as byte)
	}

	/**
	 * Clears all learned parameters and output activations.
	 */
	public fn Clear()
	{
		for (var index = 0; index < 200; index++) {
			this.weights.Write(index, 0x00)
		}

		for (var index = 0; index < OUTPUT_SIZE; index++) {
			this.outputValues.Write(index, 0x00)
		}

		for (var channel = 0; channel < CHANNELS; channel++) {
			biases[channel] = 0
			multipliers[channel] = 0
			divisors[channel] = 1
			inputZeroPoints[channel] = 0
			weightZeroPoints[channel] = 0
			outputZeroPoints[channel] = 0
		}
	}

	/**
	 * Stores one signed-int8 kernel weight and ignores an invalid index.
	 */
	public fn SetWeight(channel: int, index: int, value: int)
	{
		if (channel < 0) {
			return
		}

		if (channel >= CHANNELS) {
			return
		}

		if (index < 0) {
			return
		}

		if (index >= KERNEL_SIZE) {
			return
		}

		this.weights.Write(channel * 25 + index, value as byte)
	}

	/**
	 * Stores bias, rational scale, and tensor zero points for one channel.
	 */
	public fn SetChannelParameters(
		channel: int, bias: int, multiplier: int, divisor: int,
		inputZeroPoint: int, weightZeroPoint: int, outputZeroPoint: int)
	{
		if (channel < 0) {
			return
		}

		if (channel >= CHANNELS) {
			return
		}

		biases[channel] = bias
		multipliers[channel] = multiplier
		divisors[channel] = divisor
		inputZeroPoints[channel] = inputZeroPoint
		weightZeroPoints[channel] = weightZeroPoint
		outputZeroPoints[channel] = outputZeroPoint
	}

	/**
	 * Computes eight quantized output planes from one row-major input plane.
	 */
	public fn Compute()
	{
		// Materialize each RAM value once for the arithmetic phase. Persistent
		// tensor storage remains RAM-backed, while the inner convolution loop
		// avoids repeatedly invoking a multi-cycle RAM access path.
		var inputCache: byte[784]
		var kernelCache: logic[25, 8]

		for (var index = 0; index < INPUT_SIZE; index++) {
			inputCache[index] = this.inputValues.Read(index)
		}

		for (var channel = 0; channel < CHANNELS; channel++) {
			for (var index = 0; index < KERNEL_SIZE; index++) {
				kernelCache[index] = this.weights.Read(channel * 25 + index)
			}

			for (var row = 0; row < 28; row++) {
				for (var column = 0; column < 28; column++) {
					var accumulator = biases[channel]

					for (var kernelRow = 0; kernelRow < 5; kernelRow++) {
						var inputRow = row + kernelRow - 2

						for (var kernelColumn = 0; kernelColumn < 5; kernelColumn++) {
							var inputColumn = column + kernelColumn - 2

							if (inputRow >= 0 && inputRow < 28 && inputColumn >= 0 && inputColumn < 28) {
								var inputValue = inputCache[inputRow * 28 + inputColumn] as int

								if (inputValue != inputZeroPoints[channel]) {
									var weightBits: logic[8] = kernelCache[kernelRow * 5 + kernelColumn]
									var centeredInput = inputValue - inputZeroPoints[channel]
									var centeredWeight = (weightBits as int) - weightZeroPoints[channel]
									accumulator = accumulator + centeredInput * centeredWeight
								}
							}
						}
					}

					var quantized = requantize.ApplyRational(
						accumulator, multipliers[channel], divisors[channel], outputZeroPoints[channel])
					this.outputValues.Write(channel * 784 + row * 28 + column, quantized as byte)
				}
			}
		}
	}

	/**
	 * Returns one flattened NCHW output value, or zero for an invalid index.
	 */
	public fn GetOutput(index: int) int
	{
		if (index < 0) {
			return 0
		}

		if (index >= OUTPUT_SIZE) {
			return 0
		}

		return this.outputValues.Read(index) as int
	}
}