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/activation/StableSoftmax8.lvt

namespace Livt.ML.Activation

using Livt.Math.Arithmetic

/**
 * Computes a numerically stable softmax over up to eight signed scores.
 *
 * The maximum score is subtracted before a bounded exp(-x) lookup. `scoreStep`
 * defines how many input units correspond to one natural-log unit. Outputs use
 * Q1.15 scale 32768 and inactive lanes are cleared.
 */
component StableSoftmax8
{
	// Compile-time dimensions, bounds, and numeric constants.
	public const VECTOR_SIZE: int = 8
	public const OUTPUT_SCALE: int = 32768
	const LAST_LUT_INDEX: int = 15

	// Owned approximation table in the component numeric scale.
	expNegative: int[16]

	// Latest computed result, retained for the caller.
	output: int[8]

	// Owned arithmetic worker used by serialized operations.
	divider: UnsignedDivideInt32

	/**
	 * Loads the owned exp(-x) table and creates the integer divider used to normalize
	 * probabilities.
	 */
	new()
	{
		expNegative[0] = 32768
		expNegative[1] = 12055
		expNegative[2] = 4435
		expNegative[3] = 1631
		expNegative[4] = 600
		expNegative[5] = 221
		expNegative[6] = 81
		expNegative[7] = 30
		expNegative[8] = 11
		expNegative[9] = 4
		expNegative[10] = 1
		expNegative[11] = 1
		expNegative[12] = 0
		expNegative[13] = 0
		expNegative[14] = 0
		expNegative[15] = 0
		this.divider = new UnsignedDivideInt32()
	}

	/**
	 * Computes Q1.15 softmax weights for the first count scores.
	 */
	public fn ComputeCount(scores: int[8], count: int, scoreStep: int)
	{
		var activeCount = count

		if (activeCount < 0) {
			activeCount = 0
		}

		if (activeCount > VECTOR_SIZE) {
			activeCount = VECTOR_SIZE
		}

		for (var index = 0; index < VECTOR_SIZE; index++) {
			output[index] = 0
		}

		if (activeCount == 0) {
			return
		}

		var step = scoreStep

		if (step <= 0) {
			step = 1
		}

		var maxScore = scores[0]

		for (var index = 1; index < activeCount; index++) {
			if (scores[index] > maxScore) {
				maxScore = scores[index]
			}
		}

		var exponentials: int[8]
		var sum = 0

		for (var index = 0; index < activeCount; index++) {
			var distance = this.divider.Compute(maxScore - scores[index] + (step >> 1), step)

			if (distance > LAST_LUT_INDEX) {
				distance = LAST_LUT_INDEX
			}

			exponentials[index] = expNegative[distance]
			sum = sum + exponentials[index]
		}

		if (sum <= 0) {
			return
		}

		for (var index = 0; index < activeCount; index++) {
			output[index] = this.divider.Compute(exponentials[index] * OUTPUT_SCALE, sum)
		}
	}

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

		return output[index]
	}
}