jburtoft's picture
CHANGELOG and comment wording
6da9fa8
|
Raw History Blame
11 kB
# Changelog
## v1.6 (2026-10)
* **Two images per producer pass in `attention_block` too** (new `pack2` argument; the encoder enables it for even
batches up to 256 tokens per image, e.g. 224 px). This kernel serves every model at LNC=2 and single-head-group models
such as ViT-S/16 at LNC=1. v1.5 had packing only in the LNC=1 all-group kernel. Q/K/V projections and RoPE run once
over 2N columns; attention stays per image. Output bit-identical to v1.5 (same rel_l2 vs fp32 on all 20 cases).
Kernel device cycles, ViT-L 224 px LNC=2: batch 4 -8.4% (resident weights) / -12.0% (restaged), batch 2 -8.4%.
Per instance at 224 px, batch 8:
| model | config | v1.5 img/s | v1.6 img/s | change |
|---|---|---|---|---|
| ViT-S/16 | LNC=1 DP=8 | 8,592.8 | **9,365.6** | +9.0% |
| ViT-S/16 | LNC=2 DP=4 | 4,667.6 | 5,061.6 | +8.4% |
| ViT-B/16 | LNC=2 DP=4 | 3,578.3 | 3,856.8 | +7.8% |
| ViT-L/16 | LNC=2 DP=4 | 1,514.8 | 1,589.7 | +4.9% |
| ViT-H+/16 | LNC=2 DP=4 | 507.3 | 554.0 | +9.2% |
Single core, batch 4: +5.9% to +10.8%. Batch 1 and 448 px unchanged (packing off). `VIT_ENC_PACK2=0` disables it.
## v1.5 (2026-10)
* **Two images per producer pass** (LNC=1 all-group attention, `attention_block_l1_ln`, new `pack2` argument, on by
default from the encoder when the batch is even and two images fit one 512-token tile, i.e. up to 256 tokens such as
224 px). The two images' Q/K/V projections and RoPE run as one pass over 2N columns; attention stays per image.
Kernel device cycles, ViT-L 224 px, batch 4: -6.2% (resident weights) / -14.8% (restaged), batch 2: -8.5%.
End to end (real HF weights, LNC=1): per instance (8 cores, batch 8) ViT-L/16 **1,838 -> 1,906 img/s (+3.7%)**,
ViT-H+/16 **672 -> 743 (+10.6%)**, ViT-B/16 **4,390 -> 4,738 (+7.9%)**. Single core batch 4: ViT-L +4.6%, ViT-H+
+12.6%, ViT-B +4.1%. Output bit-identical to v1.4 (same rel_l2 vs fp32 on all 12 cases). `VIT_ENC_PACK2=0` disables it.
A fused-attention-block technique (two images per producer pass).
* **Fix: inputs of 128 tokens or fewer** failed to compile in every attention kernel (`index 1 out of bounds`). They
now use a non-pipelined single-Q-group path (rel_l2 vs a float64 reference 1.9-2.5e-3). Longer inputs are
unchanged (bit-identical).
* **EXPERIMENTAL bf16 RoPE** (`VIT_ENC_ROPE_BF16=1`, off by default): RoPE tables and products in bf16. +0.6% to +3.3%
end to end on DINOv3; rel_l2 vs fp32 grows by up to 1.16x (V-JEPA2: 0.039 -> 0.041).
* **EXPERIMENTAL fused attention write-back** (`VIT_ENC_WB_FUSED=1`, off by default): one Vector multiply normalises and
casts O. -3.4% at 256 tokens / LNC=2, but +6.6% at 2048 tokens (Vector is the attention bottleneck), so off.
## v1.4 (2026-10)
* **QK^T PSUM banks:** for 1024 or fewer tokens per image (two key tiles or fewer), the QK^T matmul now rotates over
five PSUM banks across Q groups instead of reusing four, so it no longer waits for the Vector row-max evict of the
tile four back. Attention kernel device cycles vs v1.3: -2.1% (N=256 and N=1024, LNC=2), -1.1% (N=1024, LNC=1),
-4.7% (all-group kernel, N=256, LNC=1). At 2048 tokens the old layout is kept (the rotation collided with other banks
and measured +7%). `VIT_ENC_MM1_WIDE=0` restores the v1.3 layout.
End to end (single core, real HF weights) vs v1.3: DINOv3 ViT-L/16 -0.5% to +3.7%, ViT-H+/16 0.0% to +2.6%.
V-JEPA2 (2048 tokens): 23.80 ms/clip vs 23.81, output identical to v1.3. All 24 DINOv3 cases pass (rel_l2 vs fp32
0.56-1.75e-2).
* **EXPERIMENTAL FP8 MLP** (`VIT_ENC_MLP_FP8=1`, GELU-MLP models only, off by default): fc1 and fc2 run as e4m3
`double_row` matmuls, with power-of-two weight scaling and a per-layer GELU-output scale (`calibrate_fp8`). Measured
on DINOv3 ViT-L/16, single core: +7.3% (224 px, LNC=2) to +10.5% (448 px, LNC=1) end to end. On imagenette
(real weights), k-NN and linear-probe predictions were identical to fp32. But the CLS feature error rises to
rel_l2 5.8e-2 (bf16 path: 0.84e-2). **Calibrate before use**: trn2's e4m3 saturates at 240, and DINOv3's first
block produces GELU outputs near 2,000. Without calibration the default scale (1/32) is used, which is safe for
DINOv3 but coarse.
* Outputs are deterministic per compiled graph. Across separate compilations they can differ at bf16-ulp level
(rel ~1e-5), as in v1.3.
## v1.3 (2026-10)
Attention-path schedule improvements, plus a SwiGLU fallback fix. Output of every attention kernel is
**bitwise identical** to v1.2. Verified on trn2.3xlarge at LNC=1 and LNC=2, PyTorch Native Beta 6.
Device-cycle A/B, v1.2 vs v1.3 (median of 3 captures, same instance, container and inputs):
| Kernel / shape | LNC | change |
|---|---|---|
| `attention_block`, N=1024, B=2 | 2 | -1.9% |
| `attention_block`, N=2048, B=1 (V-JEPA2 clip) | 2 | **-5.2%** |
| `attention_block`, N=256, B=8 | 2 | -2.0% |
| `attention_block`, N=1024, B=2 | 1 | -3.3% |
| `attention_block_l1`, N=256, B=4 | 1 | **-11.1%** |
| `attention_block_l1_ln`, N=256, B=4 | 1 | **-10.2%** |
| `attention_block_l1_ln`, N=1024, B=2 | 1 | -4.5% |
End to end (real Hugging Face weights, single core, vs v1.0 on the same harness): DINOv3 ViT-L/16 +1.0% to
+12.7%, ViT-H+/16 +0.6% to +17.3%. Largest gains are at 224 px and batch 1, where attention's fixed costs
dominate. V-JEPA2 (16 frames, LNC=2): 24.78 -> 24.48 ms/clip vs v1.2, output identical to v1.2.
All 16 DINOv3 cases pass rel_l2 <= 5e-2 vs fp32 (0.56-0.98e-2).
Changes:
* **Q/K bias in one DMA.** `pack_weights` now returns the Q/K bias packed as `[2, 128, Fp/128]` fp32
(`pack_qk_bias`, exported). Each attention program loads its groups with one DMA per Q/K on the Scalar
HW-DGE queue, replacing one `[128, 1]` DMA plus one cast per 128-row group. The values are rounded to bf16
first, which keeps the output bit-identical. The kernels still accept the legacy `[2, Fp, 1]` layout.
* **Dead work removed in the attention core** (`attention_inner.py`, single-section path, N <= 2048):
the `mm2_sb` and `exp_partial_sum` zero-fills (never observed), and the `tensor_reduce` over the chained
exp-sum columns (the sum is already in one column; it is now read directly).
* **Chained row-max reduce**: the QK^T row-max accumulates across the 512-key tiles of a large tile and is
read out once, instead of once per tile followed by a `tensor_reduce`. Pattern from the upstream nkilib
`attention_cte` row-max chaining, re-implemented for this fork. It adds -3.5% at N=2048 and is neutral at
N <= 1024 (fewer key tiles to chain).
* **Fix (v1.2 regression):** the SwiGLU torch fallback (taken below 256 rows per core, e.g. ViT-H+/16 at
LNC=2, batch 1, 224 px) used the packed biases and failed to compile. It now uses unpacked copies. The
kernel path was unaffected.
## v1.2 (2026-10)
Applied the v1.1 packed-bias one-DMA technique to the SwiGLU FFN kernels (`swiglu_stream`,
`swiglu_stream_ln`). The silu bias `b1` and the up-projection bias `bg` are now host-packed to
`[128, I/128]` (`pack_b1`) and each loaded in a single DMA on the Scalar engine's HW-DGE queue,
replacing `I/128` `[128, 1]` column DMAs apiece. Output is **bitwise identical** to v1.1 at LNC=1 and
LNC=2 (`torch.equal`, verified on trn2.3xlarge). Device cycles (same stack, A/B): `swiglu_stream`
-2.5% (reproducible); `swiglu_stream_ln` within measurement noise (the LN epilogue dominates and the
bias DMAs already overlap compute). The wrappers still accept an unpacked `[I]` bias (packed on the
fly). Affects only the gated-MLP (SwiGLU) models; the GELU-MLP path is unchanged from v1.1.
## v1.1 (2026-10)
Reworked DMA/engine schedule for `mlp` (K4), `mlp_res_ln` (K4 + next LN) and `res_layernorm_fold`
(K1/K3). Output is **bitwise identical** to v1 at both LNC=1 and LNC=2. Based on an external
contribution by [jlongen-aws](https://huggingface.co/jlongen-aws) (PR #2 on
`jburtoft/vjepa2-neuron-kernels`), re-implemented here with the LNC-parametric path and the `min()`
remainder handling preserved.
Verified on trn2.3xlarge (A/B on one consistent stack: old vs new compiled and run in the same
container + inputs, device cycles from `neuron-explorer show-session`). LNC=2, V-JEPA2 ViT-L shape
(R=2048, H=1024, MLP hidden 4096):
* K4 `mlp`: -7.5% device cycles (reproducible, -7.8% +/- ~1% over 3 captures).
* K4+LN `mlp_res_ln`: -6.8%.
* K1 `res_layernorm_fold` (1 branch): -16.2%; K3 (2 branches): -18.3%.
* Full 24-layer encoder (synthetic ViT-L weights, includes the unchanged attention): -3.7% device
cycles, output `torch.equal` to v1.
* Real-weight V-JEPA2 (`facebook/vjepa2-vitl-fpc64-256`, 16 frames x 256 px, LNC=2): output bitwise
identical to v1 (`new_vs_v1_rel` 0.0), rel_l2 0.0389 vs fp32 CPU (unchanged from v1), -4.0% ms/clip
(speedup vs stock fp32 3.19x -> 3.33x).
* Per-kernel output is `torch.equal` to v1 at LNC=1 and LNC=2.
Changes:
* `mlp` / `mlp_res_ln`: fc1 bias is host-packed to `[128, H/128]` (`pack_b1`, now exported) and
loaded in one DMA on the Scalar engine's HW-DGE queue instead of `H/128` column DMAs; row block 0's
`x` is staged and transposed on the (idle) Tensor engine before the weights, later row blocks stream
under row block 0's matmuls. `mlp` still accepts an unpacked `[H]` bias (packed on the fly).
* `res_layernorm_fold`: every DMA uses `dge_mode.none` (the runtime pre-generates descriptors) and all
token tiles are made SBUF-resident before any compute, replacing the depth-4 ring. Per-tile statistic
columns (`[128, n_t]`) so one tile's stat chain does not wait on the previous tile's.
* `pack_block` now stores the GELU fc1 bias pre-packed; `pack_b1` is exported from `nki_kernels`.
The compute instructions and their order are unchanged, so results are bit-identical to v1.
## v1 (2026-10)
First release.
* Drop-in layers for Hugging Face Transformers: `NeuronDINOv3ViTModel` (DINOv3 ViT-S/16, S+/16, B/16,
L/16, H+/16) and `NeuronVJEPA2Encoder` (V-JEPA2 ViT-L, any batch size, up to 2048 tokens per clip).
* Model-agnostic encoder loop: `BlockWeights`, `pack_block`, `rope_tables`, `EncoderRunner`.
* Kernels: `attention_block` (per head group; batched; padded keys skipped; head_dim 64 or 128;
any head count), `attention_block_l1` and `attention_block_l1_ln` (LNC=1, all head groups, optional
residual + LayerNorm epilogue), `res_layernorm_fold`, `mlp` and `mlp_res_ln` (GELU MLP, optional
residual + next LayerNorm epilogue), `swiglu_stream` and `swiglu_stream_ln` (weight-streaming
SwiGLU FFN).
* RoPE pairing selectable at packing time: `rope="half"` (rotate-half) or `rope="interleaved"`.
* Verified on trn2.3xlarge, PyTorch Native Beta 6 (torch 2.13.0, torch-neuronx 2.13.3, NKI 0.7.0b1),
Transformers 4.57.6, LNC=1 and LNC=2. Every measured case passes rel_l2 <= 5e-2 vs the fp32 CPU model.
Known limitations: encoder (bidirectional) attention with RoPE only; LayerNorm (not RMSNorm);
<= 2048 tokens per image; inference only; hidden size 4096 (ViT-7B) not covered by the drop-in layer.