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 (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.