III · Layers / CHAPTER 11
layers/dense — MatVec microkernel
github.com/openfluke/welvet/layers/dense✅
Why it exists
Most FLOPs are W@x. One Dense stack owns FormatNone×34 and all quants × three backends so every composite proj shares one correctness surface — including native in-dtype SGD.
What it is
New / NewConfigured[T], Forward/Backward (dispatch on Exec.Backend), Place, ApplyGradSGD (→ weights.ApplySGD on the store). SIMD forward (v1.0.3): dtype switch by MatVec strategy — DotTile / DotI8 / lowp packed / expand-once→DotTile / WireF64+DotTileF64; fused Dot* for classic Q*, k/IQ, AffinePacked. Composites (MHA, SwiGLU, CNN im2col, RNN/LSTM/Mamba, residual·sequential·parallel) reuse Dense children via syncProjExec. BackwardSIMD still DecodeRow/saxpy (not the new expand wires).
forwardSIMDByWire (strategy groups)
| case | dtypes | kernel |
|---|---|---|
| narrow i8 | int4, int2, ternary, binary | expand → DotI8Tile |
| uint affine | uint4/2/3/5/6, uint16…uintptr | expand once → DotTile |
| lowp packed | f16, bf16, fp8, fp4 | convert tiles → DotTile |
| expand f32 | nf4, fp6, int3/5/6 | expand once → DotTile (exact) |
| stream f64 | float64, int16/32/64, int, complex* | WireF64 → DotTileF64 |
| SelectWire | float32, int8, uint8, … | DotTile / DotI8 / affine u8 |
Before → after (µs/op, Go Δ)
Full Go/C++/Rust columns: before wins Go 2 / C++ 31 / Rust 1; after Go 24 / C++ 9 / Rust 1. Highlight Go speedups (before→after):
| dtype | Go before | Go after | Go Δ | best after |
|---|---|---|---|---|
| float32 | 1023.1 | 929.5 | 1.10× | go |
| float64 | 9565.4 | 1017.4 | 9.40× | go |
| bfloat16 | 12538.9 | 5045.3 | 2.49× | cpp |
| int8 | 708.1 | 659.3 | 1.07× | cpp |
| int64 | 22271.0 | 1059.9 | 21× | go |
| int32 | 22495.4 | 964.1 | 23× | go |
| int16 | 22147.5 | 953.5 | 23× | go |
| int / complex* | ~30k | ~1.0k | ~29–31× | go |
| uint* / nf4 / fp6 / int3–6 | ~20–77k | ~3.7–10.6k | ~5–7× | go |
| uint8 | 4322.3 | 7575.0 | 0.57× | cpp |
C++ still owns int8/narrow, uint8, bf16/fp8. float32 DotTile remains Go’s race (929.5 vs C++ 1162 / Rust 1335). uint8 affine is a known Go follow-up.
Go example
cd welvet/examples/11-dense && source ../env.sh && go run .package main
import (
"fmt"
"github.com/openfluke/welvet/core"
"github.com/openfluke/welvet/layers/dense"
"github.com/openfluke/welvet/quant"
)
func main() {
init := make([]float32, 4*8)
l, err := dense.NewConfigured(8, 4, core.ActivationReLU, core.DTypeFloat32, quant.FormatNone, init)
if err != nil {
panic(err)
}
l.Exec.Backend = core.BackendCPUTiled
x := core.NewTensor[float32](1, 8)
pre, post, err := dense.Forward(l, x)
if err != nil {
panic(err)
}
gIn, gW, err := dense.Backward(l, post, x, pre)
_ = dense.ApplyGradSGD(l, gW, 1e-3)
fmt.Println(len(gIn.Data), len(gW.Data))
}
Output
8 32