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

namespace Livt.ML.Convolution

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

/**
 * RAM-backed non-streaming QLinearConv from eight 14x14 planes to sixteen.
 */
component QLinearConv8x16Same5x5Ram
{
	// Owned scheduled requantizer; processing holds input acceptance until completion.
	requantize: ScheduledRequantizeUInt8

	// Compile-time dimensions, bounds, and numeric constants.
	public const INPUT_SIZE: int = 1568
	public const OUTPUT_SIZE: int = 3136
	public const OUTPUT_CHANNELS: int = 16
	public const WEIGHTS_PER_OUTPUT: int = 200

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

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

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

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

	/**
	 * 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 ByteTensorRam4096(3200)
		this.outputValues = new ByteTensorRam4096(OUTPUT_SIZE)
	}

	/**
	 * Stores one flattened NCHW uint8 input 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 < 3200; index++) {
			this.weights.Write(index, 0x00)
		}

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

		for (var channel = 0; channel < OUTPUT_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 >= OUTPUT_CHANNELS) {
			return
		}

		if (index < 0) {
			return
		}

		if (index >= WEIGHTS_PER_OUTPUT) {
			return
		}

		this.weights.Write(channel * 200 + 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 >= OUTPUT_CHANNELS) {
			return
		}

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

	/**
	 * Computes sixteen quantized output planes from eight NCHW input planes.
	 */
	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[1568]
		var kernelCache: logic[200, 8]

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

		for (var outputChannel = 0; outputChannel < OUTPUT_CHANNELS; outputChannel++) {
			for (var index = 0; index < WEIGHTS_PER_OUTPUT; index++) {
				kernelCache[index] = this.weights.Read(outputChannel * 200 + index)
			}

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

					for (var inputChannel = 0; inputChannel < 8; inputChannel++) {
						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 < 14 && inputColumn >= 0 && inputColumn < 14) {
									var inputValue = inputCache[inputChannel * 196 + inputRow * 14 + inputColumn] as int

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

					var quantized = requantize.ApplyRational(
						accumulator, multipliers[outputChannel], divisors[outputChannel], outputZeroPoints[outputChannel])
					this.outputValues.Write(outputChannel * 196 + row * 14 + 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
	}
}