Table of Contents

Class EmbeddingPooler

Namespace
Qavren.Edge.Embeddings.Onnx
Assembly
Qavren.Edge.Embeddings.Onnx.dll

Allocation-free pooling over a session output span. Public because it is worth testing alone.

public static class EmbeddingPooler
Inheritance
EmbeddingPooler
Inherited Members

Remarks

The order is fixed and it matters: pool, then LayerNorm(Span<float>, float) when the preset asks for it, then L2Normalize(Span<float>). Layer-norming a unit vector is not the same operation as unit-normalising a layer-normed one, and only the second matches nomic-embed-text-v1.5's reference pipeline.

Methods

ClsPool(ReadOnlySpan<float>, int, int, Span<float>)

Copies row zero, the [CLS] position. bge-small-en-v1.5 pools this way.

L2Normalize(Span<float>)

Divides by the Euclidean norm, in place. A zero vector is left alone: the all-padding MeanPool(ReadOnlySpan<float>, ReadOnlySpan<long>, int, int, Span<float>) guard produces one, and dividing by a zero norm here would turn a defined zero vector into NaNs one call later.

LayerNorm(Span<float>, float)

Mean/variance normalisation over the pooled vector, matching PyTorch F.layer_norm(x, (dim,)) with no learned weight or bias: subtract the mean, divide by sqrt(variance + epsilon). Required by nomic-embed-text-v1.5 and by nothing else here; see PostPoolLayerNorm.

MeanPool(ReadOnlySpan<float>, ReadOnlySpan<long>, int, int, Span<float>)

The masked mean: sum(h[t] * mask[t]) / max(1, sum(mask)). The denominator's max(1, ...) is what keeps an all-padding row a defined zero vector rather than dimensions NaNs.