v1.8: post-LayerNorm encoders (NeuronXLMRobertaModel; post-LN epilogue outputs + no-RoPE switch) and LNC=2 fix for odd head-group counts
Browse files- CHANGELOG.md +35 -0
- README.md +29 -4
- build/torch-neuron/__init__.py +9 -6
- build/torch-neuron/encoder.py +7 -0
- build/torch-neuron/layers.py +128 -0
- build/torch-neuron/nki_kernels/__init__.py +4 -3
- build/torch-neuron/nki_kernels/attention_block_l1_ln.py +70 -23
- build/torch-neuron/nki_kernels/mlp_res_ln.py +33 -2
CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
| 1 |
# Changelog
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
## v1.7 (2026-10)
|
| 4 |
|
| 5 |
* **Fix: `res_layernorm_fold` failed to compile at large batch** (`NCC_IBIR229`, state buffer allocation failed). The
|
|
|
|
| 1 |
# Changelog
|
| 2 |
|
| 3 |
+
## v1.8 (2026-10)
|
| 4 |
+
|
| 5 |
+
* **New: post-LayerNorm encoders (BERT / RoBERTa / XLM-R).** `NeuronXLMRobertaModel` is a drop-in forward for
|
| 6 |
+
`XLMRobertaModel`, `RobertaModel` and `BertModel` (GELU, absolute positions, no RoPE). It runs on the same kernels via
|
| 7 |
+
two new options:
|
| 8 |
+
- `attention_block_l1_ln_post` / `mlp_res_ln_post`: the existing fused attention and MLP epilogues also emit the
|
| 9 |
+
post-LN residual `LN(x_new) * gamma + beta` in fp32, computed from the mean and rstd they already have.
|
| 10 |
+
- `norope=1` on the attention kernel skips the rotation (and its table loads) for models without RoPE.
|
| 11 |
+
|
| 12 |
+
XLM-RoBERTa-Large, LNC=1, 8 processes (one per logical core) on one trn2.3xlarge, fully padded batches (inferences/s):
|
| 13 |
+
|
| 14 |
+
| seq | stock `torch.compile` | v1.8 `NeuronXLMRobertaModel` (`attention_mask=None`) | speedup | rel_l2 vs fp32 (stock / kernels) |
|
| 15 |
+
|---|---|---|---|---|
|
| 16 |
+
| 128 | 2,092.7 | **3,144.8** | 1.50x | 0.031 / 0.0066 |
|
| 17 |
+
| 256 | 944.4 (BS 16) | **1,869.5** | 1.98x | 0.051 / 0.0147 |
|
| 18 |
+
| 512 | 397.1 (BS 8) | **935.1** | 2.35x | 0.018 / 0.0143 |
|
| 19 |
+
|
| 20 |
+
Batch per core: 16 / 8 / 4. Passing an all-ones `attention_mask` costs 3-4% (a host-side check that no position is
|
| 21 |
+
masked: 3,022.7 / 1,789.7 / 902.8). A padded mask falls back to the stock forward (checked: rel_l2 vs fp32 1.2e-6).
|
| 22 |
+
|
| 23 |
+
Measured on the SDK 2.33 DLAMI (`torch_neuronx_2_14_0` venv: torch 2.14.0, torch-neuronx 2.14.3.0, neuronx-cc 2.28.7631,
|
| 24 |
+
NKI 0.7.0) with transformers 4.57.6. The kernel path needs LNC=1, a sequence length that is a multiple of 128 (up to 2048)
|
| 25 |
+
and no padding mask (all positions attended). Anything else falls back to the stock forward.
|
| 26 |
+
* **Fix: models with an odd number of head groups failed to compile at LNC=2 on neuronx-cc 2.28** (`NCC_EVRF066`: a
|
| 27 |
+
one-program attention grid on an LNC=2 build). That covers ViT-S/16, ViT-S+/16 and ViT-H+/16. At LNC=2 the host packing now pads
|
| 28 |
+
an odd group count with one all-zero head group, so every attention launch uses a two-program grid. The padded heads have
|
| 29 |
+
zero V and out-projection weights and add exactly 0. Models with an even group count (ViT-B, ViT-L, V-JEPA2) are unchanged.
|
| 30 |
+
Single core, LNC=2: ViT-S/16 224 BS4 1,148.8 img/s (rel_l2 0.0109), ViT-S+/16 1,097.9 (0.0148), ViT-H+/16 224 BS1/BS4
|
| 31 |
+
87.8 / 130.9, 448 BS1/BS2 32.6 / 33.9 (rel_l2 0.0059-0.0080). The ViT-H+ LNC=2 path is 1.18-1.34x faster than its LNC=1
|
| 32 |
+
fallback on the same core.
|
| 33 |
+
* **No change for existing models at existing shapes:** in the 28-case regression (DINOv3 S/S+/B/L/H+ at 224/448 and
|
| 34 |
+
V-JEPA2 at batch 1/2, LNC=1 and LNC=2), every case v1.7 runs gives output bitwise identical to v1.7, at the same speed
|
| 35 |
+
(within ±1%). That is 22 cases. The other 6 are the odd-group LNC=2 cases above, which v1.7 cannot compile on this
|
| 36 |
+
toolchain. Verified on the SDK 2.33 DLAMI.
|
| 37 |
+
|
| 38 |
## v1.7 (2026-10)
|
| 39 |
|
| 40 |
* **Fix: `res_layernorm_fold` failed to compile at large batch** (`NCC_IBIR229`, state buffer allocation failed). The
|
README.md
CHANGED
|
@@ -13,6 +13,8 @@ tags:
|
|
| 13 |
- vjepa2
|
| 14 |
- rope
|
| 15 |
- swiglu
|
|
|
|
|
|
|
| 16 |
---
|
| 17 |
|
| 18 |
# vit-encoder-neuron-kernels
|
|
@@ -22,15 +24,16 @@ Trainium2 (trn2). Each block is 2-3 coarse kernels instead of dozens of small op
|
|
| 22 |
the residual add and LayerNorm, and the MLP / SwiGLU FFN plus the next block's LayerNorm. The
|
| 23 |
residual stream stays in fp32 between kernels.
|
| 24 |
|
| 25 |
-
Drop-in layers are included for
|
| 26 |
|
| 27 |
| Layer | Replaces | Models |
|
| 28 |
|---|---|---|
|
| 29 |
| `NeuronDINOv3ViTModel` | `DINOv3ViTModel.forward` | DINOv3 ViT-S/16, S+/16, B/16, L/16, H+/16 |
|
| 30 |
| `NeuronVJEPA2Encoder` | `VJEPA2Encoder.forward` | V-JEPA2 ViT-L (video), up to 2048 tokens per clip |
|
|
|
|
| 31 |
|
| 32 |
The building blocks (`BlockWeights` / `pack_block` / `EncoderRunner`) are model-agnostic. Any
|
| 33 |
-
pre-norm ViT with the shape constraints below can use them, but only the
|
| 34 |
have been validated.
|
| 35 |
|
| 36 |
## Performance (trn2.3xlarge, bf16, `torch.compile(backend="neuron")`)
|
|
@@ -141,6 +144,25 @@ activation scale with `calibrate_fp8(runner, gelu_amax)`, because trn2's e4m3 sa
|
|
| 141 |
Which LNC to use: **LNC=1** (8 logical cores, one process per core) gives the most throughput per
|
| 142 |
instance. **LNC=2** (4 larger cores) gives the lowest latency per request.
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
## Kernels
|
| 145 |
|
| 146 |
| Kernel | What it does |
|
|
@@ -173,13 +195,16 @@ RoPE pairings via `rope=`:
|
|
| 173 |
* Inference only (no autograd). The layers refuse in training mode.
|
| 174 |
* ViT-7B-sized models (hidden 4096) are not covered by the drop-in layer. They need tensor parallelism.
|
| 175 |
The streaming SwiGLU kernel is slower than the compiler's FFN at that width.
|
| 176 |
-
* Validated end to end on DINOv3 ViT-B/L/H+
|
|
|
|
|
|
|
| 177 |
|
| 178 |
## Requirements
|
| 179 |
|
| 180 |
* trn2 (verified on trn2.3xlarge, LNC=1 and LNC=2).
|
| 181 |
* PyTorch Native (TorchNeuron) with NKI 0.7 and `nkilib`. Verified on Beta 6 (torch 2.13.0,
|
| 182 |
-
torch-neuronx 2.13.3, NKI 0.7.0b1, neuronx-cc 2.0.404056).
|
|
|
|
| 183 |
* Transformers with DINOv3 / V-JEPA2 (verified on 4.57.6); `kernels` for `get_kernel`.
|
| 184 |
|
| 185 |
## Repository layout
|
|
|
|
| 13 |
- vjepa2
|
| 14 |
- rope
|
| 15 |
- swiglu
|
| 16 |
+
- bert
|
| 17 |
+
- xlm-roberta
|
| 18 |
---
|
| 19 |
|
| 20 |
# vit-encoder-neuron-kernels
|
|
|
|
| 24 |
the residual add and LayerNorm, and the MLP / SwiGLU FFN plus the next block's LayerNorm. The
|
| 25 |
residual stream stays in fp32 between kernels.
|
| 26 |
|
| 27 |
+
Drop-in layers are included for these Hugging Face Transformers models:
|
| 28 |
|
| 29 |
| Layer | Replaces | Models |
|
| 30 |
|---|---|---|
|
| 31 |
| `NeuronDINOv3ViTModel` | `DINOv3ViTModel.forward` | DINOv3 ViT-S/16, S+/16, B/16, L/16, H+/16 |
|
| 32 |
| `NeuronVJEPA2Encoder` | `VJEPA2Encoder.forward` | V-JEPA2 ViT-L (video), up to 2048 tokens per clip |
|
| 33 |
+
| `NeuronXLMRobertaModel` (v1.8) | `XLMRobertaModel.forward` (also `RobertaModel` / `BertModel`) | XLM-RoBERTa-Large (validated); post-LN text encoders without RoPE |
|
| 34 |
|
| 35 |
The building blocks (`BlockWeights` / `pack_block` / `EncoderRunner`) are model-agnostic. Any
|
| 36 |
+
pre-norm ViT with the shape constraints below can use them, but only the models above
|
| 37 |
have been validated.
|
| 38 |
|
| 39 |
## Performance (trn2.3xlarge, bf16, `torch.compile(backend="neuron")`)
|
|
|
|
| 144 |
Which LNC to use: **LNC=1** (8 logical cores, one process per core) gives the most throughput per
|
| 145 |
instance. **LNC=2** (4 larger cores) gives the lowest latency per request.
|
| 146 |
|
| 147 |
+
### XLM-RoBERTa / BERT-family text encoders (v1.8)
|
| 148 |
+
|
| 149 |
+
```python
|
| 150 |
+
import os
|
| 151 |
+
os.environ.setdefault("NEURON_LOGICAL_NC_CONFIG", "1") # the post-LN path runs at LNC=1
|
| 152 |
+
import types, torch
|
| 153 |
+
from transformers import XLMRobertaModel
|
| 154 |
+
model = XLMRobertaModel.from_pretrained("FacebookAI/xlm-roberta-large", dtype=torch.float32,
|
| 155 |
+
add_pooling_layer=False).eval().to("neuron")
|
| 156 |
+
model.forward = types.MethodType(k.NeuronXLMRobertaModel.forward, model) # instance graft (k = get_kernel(...) as above)
|
| 157 |
+
model = torch.compile(model, backend="neuron")
|
| 158 |
+
ids = tokenizer(texts, padding="max_length", max_length=128, return_tensors="pt").input_ids # seq % 128 == 0
|
| 159 |
+
out = model(input_ids=ids.to("neuron")).last_hidden_state
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
The kernel path needs LNC=1, a sequence length that is a multiple of 128 (up to 2048) and no padding mask. Pass
|
| 163 |
+
`attention_mask=None` or all ones; masked batches fall back to the stock forward. Per instance, XLM-RoBERTa-Large at
|
| 164 |
+
seq 128 / 256 / 512 runs at 3,145 / 1,870 / 935 inferences/s, against 2,093 / 944 / 397 stock (see CHANGELOG v1.8).
|
| 165 |
+
|
| 166 |
## Kernels
|
| 167 |
|
| 168 |
| Kernel | What it does |
|
|
|
|
| 195 |
* Inference only (no autograd). The layers refuse in training mode.
|
| 196 |
* ViT-7B-sized models (hidden 4096) are not covered by the drop-in layer. They need tensor parallelism.
|
| 197 |
The streaming SwiGLU kernel is slower than the compiler's FFN at that width.
|
| 198 |
+
* Validated end to end on DINOv3 ViT-B/L/H+, V-JEPA2 ViT-L and (v1.8) XLM-RoBERTa-Large.
|
| 199 |
+
* Post-LN text encoders (v1.8, `NeuronXLMRobertaModel`): LNC=1 only, no padding mask, no token-type / relative
|
| 200 |
+
position variants beyond the standard BERT embeddings, GELU FFN.
|
| 201 |
|
| 202 |
## Requirements
|
| 203 |
|
| 204 |
* trn2 (verified on trn2.3xlarge, LNC=1 and LNC=2).
|
| 205 |
* PyTorch Native (TorchNeuron) with NKI 0.7 and `nkilib`. Verified on Beta 6 (torch 2.13.0,
|
| 206 |
+
torch-neuronx 2.13.3, NKI 0.7.0b1, neuronx-cc 2.0.404056) and (v1.8) on the **SDK 2.33 DLAMI**
|
| 207 |
+
`torch_neuronx_2_14_0` venv (torch 2.14.0, torch-neuronx 2.14.3.0, NKI 0.7.0, neuronx-cc 2.28.7631).
|
| 208 |
* Transformers with DINOv3 / V-JEPA2 (verified on 4.57.6); `kernels` for `get_kernel`.
|
| 209 |
|
| 210 |
## Repository layout
|
build/torch-neuron/__init__.py
CHANGED
|
@@ -3,22 +3,25 @@
|
|
| 3 |
Model layers (drop-in forwards for Hugging Face Transformers):
|
| 4 |
NeuronDINOv3ViTModel -- DINOv3ViTModel (S/S+/B/L/H+)
|
| 5 |
NeuronVJEPA2Encoder -- VJEPA2Encoder
|
|
|
|
| 6 |
|
| 7 |
Model-agnostic building blocks:
|
| 8 |
BlockWeights, pack_block, rope_tables, EncoderRunner -- pre-norm encoder loop on the kernels
|
| 9 |
-
kernels: attention_block, attention_block_l1, attention_block_l1_ln, res_layernorm_fold,
|
| 10 |
-
mlp, mlp_res_ln, swiglu_stream, swiglu_stream_ln
|
| 11 |
"""
|
| 12 |
|
| 13 |
from .encoder import LNC, BlockWeights, EncoderRunner, calibrate_fp8, pack_block, rope_tables
|
| 14 |
-
from .layers import NeuronDINOv3ViTModel, NeuronVJEPA2Encoder
|
| 15 |
from .nki_kernels import (
|
| 16 |
attention_block,
|
| 17 |
attention_block_l1,
|
| 18 |
attention_block_l1_ln,
|
|
|
|
| 19 |
lane_permutation,
|
| 20 |
mlp,
|
| 21 |
mlp_res_ln,
|
|
|
|
| 22 |
pack_out_weights,
|
| 23 |
pack_tables,
|
| 24 |
pack_v_weights,
|
|
@@ -29,9 +32,9 @@ from .nki_kernels import (
|
|
| 29 |
)
|
| 30 |
|
| 31 |
__all__ = [
|
| 32 |
-
"NeuronDINOv3ViTModel", "NeuronVJEPA2Encoder",
|
| 33 |
"BlockWeights", "pack_block", "rope_tables", "EncoderRunner", "LNC", "calibrate_fp8",
|
| 34 |
-
"attention_block", "attention_block_l1", "attention_block_l1_ln", "res_layernorm_fold",
|
| 35 |
-
"mlp", "mlp_res_ln", "swiglu_stream", "swiglu_stream_ln",
|
| 36 |
"pack_weights", "pack_tables", "pack_v_weights", "pack_out_weights", "lane_permutation",
|
| 37 |
]
|
|
|
|
| 3 |
Model layers (drop-in forwards for Hugging Face Transformers):
|
| 4 |
NeuronDINOv3ViTModel -- DINOv3ViTModel (S/S+/B/L/H+)
|
| 5 |
NeuronVJEPA2Encoder -- VJEPA2Encoder
|
| 6 |
+
NeuronXLMRobertaModel -- XLMRobertaModel / RobertaModel / BertModel (post-LN, no RoPE; v1.8)
|
| 7 |
|
| 8 |
Model-agnostic building blocks:
|
| 9 |
BlockWeights, pack_block, rope_tables, EncoderRunner -- pre-norm encoder loop on the kernels
|
| 10 |
+
kernels: attention_block, attention_block_l1, attention_block_l1_ln(_post), res_layernorm_fold,
|
| 11 |
+
mlp, mlp_res_ln(_post), swiglu_stream, swiglu_stream_ln
|
| 12 |
"""
|
| 13 |
|
| 14 |
from .encoder import LNC, BlockWeights, EncoderRunner, calibrate_fp8, pack_block, rope_tables
|
| 15 |
+
from .layers import NeuronDINOv3ViTModel, NeuronVJEPA2Encoder, NeuronXLMRobertaModel
|
| 16 |
from .nki_kernels import (
|
| 17 |
attention_block,
|
| 18 |
attention_block_l1,
|
| 19 |
attention_block_l1_ln,
|
| 20 |
+
attention_block_l1_ln_post,
|
| 21 |
lane_permutation,
|
| 22 |
mlp,
|
| 23 |
mlp_res_ln,
|
| 24 |
+
mlp_res_ln_post,
|
| 25 |
pack_out_weights,
|
| 26 |
pack_tables,
|
| 27 |
pack_v_weights,
|
|
|
|
| 32 |
)
|
| 33 |
|
| 34 |
__all__ = [
|
| 35 |
+
"NeuronDINOv3ViTModel", "NeuronVJEPA2Encoder", "NeuronXLMRobertaModel",
|
| 36 |
"BlockWeights", "pack_block", "rope_tables", "EncoderRunner", "LNC", "calibrate_fp8",
|
| 37 |
+
"attention_block", "attention_block_l1", "attention_block_l1_ln", "attention_block_l1_ln_post", "res_layernorm_fold",
|
| 38 |
+
"mlp", "mlp_res_ln", "mlp_res_ln_post", "swiglu_stream", "swiglu_stream_ln",
|
| 39 |
"pack_weights", "pack_tables", "pack_v_weights", "pack_out_weights", "lane_permutation",
|
| 40 |
]
|
build/torch-neuron/encoder.py
CHANGED
|
@@ -105,6 +105,11 @@ def pack_block(bw: BlockWeights, num_heads: int, rope: str, device) -> dict:
|
|
| 105 |
D = F_ // num_heads
|
| 106 |
gsz = 512 // D # heads per kernel program
|
| 107 |
nh_p = ((num_heads + gsz - 1) // gsz) * gsz # padded head count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
Fp = nh_p * D
|
| 109 |
ngrp = nh_p // gsz
|
| 110 |
g1, t1, g2, t2 = _f(bw.ln1_w), _f(bw.ln1_b), _f(bw.ln2_w), _f(bw.ln2_b)
|
|
@@ -167,6 +172,8 @@ def rope_tables(cos: torch.Tensor, sin: torch.Tensor, n_prefix: int, n_pad: int,
|
|
| 167 |
S[n_prefix:n_prefix + n_rot] = sin.float().cpu()
|
| 168 |
gsz = 512 // D
|
| 169 |
nh_p = ((num_heads + gsz - 1) // gsz) * gsz
|
|
|
|
|
|
|
| 170 |
t = _ab.pack_tables(C, S, nh_p, D, rope)
|
| 171 |
if _osr.environ.get("VIT_ENC_ROPE_BF16", "0") == "1": # EXPERIMENTAL bf16 RoPE (tables in bf16)
|
| 172 |
t = t.to(torch.bfloat16)
|
|
|
|
| 105 |
D = F_ // num_heads
|
| 106 |
gsz = 512 // D # heads per kernel program
|
| 107 |
nh_p = ((num_heads + gsz - 1) // gsz) * gsz # padded head count
|
| 108 |
+
if LNC == 2 and (nh_p // gsz) % 2 == 1:
|
| 109 |
+
# v1.8: at LNC=2 every attention launch must use a 2-program grid (newer neuronx-cc rejects a 1-program
|
| 110 |
+
# grid on an LNC=2 build: NCC_EVRF066). Pad an odd group count with one all-zero head group; its V and
|
| 111 |
+
# out-projection weights are zero, so it adds exactly 0 to the output.
|
| 112 |
+
nh_p += gsz
|
| 113 |
Fp = nh_p * D
|
| 114 |
ngrp = nh_p // gsz
|
| 115 |
g1, t1, g2, t2 = _f(bw.ln1_w), _f(bw.ln1_b), _f(bw.ln2_w), _f(bw.ln2_b)
|
|
|
|
| 172 |
S[n_prefix:n_prefix + n_rot] = sin.float().cpu()
|
| 173 |
gsz = 512 // D
|
| 174 |
nh_p = ((num_heads + gsz - 1) // gsz) * gsz
|
| 175 |
+
if LNC == 2 and (nh_p // gsz) % 2 == 1:
|
| 176 |
+
nh_p += gsz # same padding as pack_block (v1.8)
|
| 177 |
t = _ab.pack_tables(C, S, nh_p, D, rope)
|
| 178 |
if _osr.environ.get("VIT_ENC_ROPE_BF16", "0") == "1": # EXPERIMENTAL bf16 RoPE (tables in bf16)
|
| 179 |
t = t.to(torch.bfloat16)
|
build/torch-neuron/layers.py
CHANGED
|
@@ -218,3 +218,131 @@ class NeuronVJEPA2Encoder(torch.nn.Module):
|
|
| 218 |
x = getattr(self, _PACK)(_stack(hs, N), tabs[N], n, B)
|
| 219 |
out = self.layernorm(x.reshape(B, N, H)[:, :n])
|
| 220 |
return BaseModelOutput(last_hidden_state=out)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
x = getattr(self, _PACK)(_stack(hs, N), tabs[N], n, B)
|
| 219 |
out = self.layernorm(x.reshape(B, N, H)[:, :n])
|
| 220 |
return BaseModelOutput(last_hidden_state=out)
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
# ------------------------------------------------------------------------------------------------
|
| 224 |
+
# XLM-RoBERTa / RoBERTa / BERT (post-LayerNorm encoders, no RoPE) -- v1.8
|
| 225 |
+
# ------------------------------------------------------------------------------------------------
|
| 226 |
+
_POST = "_vit_enc_neuron_postln"
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def _bert_blocks(model):
|
| 230 |
+
"""BlockWeights per layer for a post-LN BERT-family encoder. Block i's "ln1" is the LayerNorm that produced
|
| 231 |
+
its input (embeddings.LayerNorm for block 0, else layer i-1's output.LayerNorm) and "ln2" its attention-output
|
| 232 |
+
LayerNorm, so the kernels' affine-free LN + folded affine reproduce the post-LN math exactly."""
|
| 233 |
+
out, prev = [], model.embeddings.LayerNorm
|
| 234 |
+
for lyr in model.encoder.layer:
|
| 235 |
+
a = lyr.attention
|
| 236 |
+
out.append(BlockWeights(
|
| 237 |
+
ln1_w=prev.weight, ln1_b=prev.bias, ln1_eps=prev.eps,
|
| 238 |
+
wq=a.self.query.weight, bq=a.self.query.bias, wk=a.self.key.weight, bk=a.self.key.bias,
|
| 239 |
+
wv=a.self.value.weight, bv=a.self.value.bias, wo=a.output.dense.weight, bo=a.output.dense.bias,
|
| 240 |
+
ln2_w=a.output.LayerNorm.weight, ln2_b=a.output.LayerNorm.bias, ln2_eps=a.output.LayerNorm.eps,
|
| 241 |
+
fc1_w=lyr.intermediate.dense.weight, fc1_b=lyr.intermediate.dense.bias,
|
| 242 |
+
fc2_w=lyr.output.dense.weight, fc2_b=lyr.output.dense.bias))
|
| 243 |
+
prev = lyr.output.LayerNorm
|
| 244 |
+
return out
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def _bert_ok(model, S):
|
| 248 |
+
c = model.config
|
| 249 |
+
return (_supported(c.hidden_size, c.num_attention_heads) and getattr(c, "hidden_act", "gelu") == "gelu"
|
| 250 |
+
and getattr(c, "position_embedding_type", "absolute") == "absolute" and S <= _MAX_TOKENS
|
| 251 |
+
and S % (128 * LNC) == 0 and LNC == 1)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
@torch._dynamo.disable
|
| 255 |
+
def _bert_freeze(model):
|
| 256 |
+
if model.training:
|
| 257 |
+
raise RuntimeError("NeuronXLMRobertaModel: these kernels have no autograd rule; call model.eval() first.")
|
| 258 |
+
c = model.config
|
| 259 |
+
dev = model.embeddings.word_embeddings.weight.device
|
| 260 |
+
heads, H = c.num_attention_heads, c.hidden_size
|
| 261 |
+
D = H // heads
|
| 262 |
+
runner = EncoderRunner([pack_block(bw, heads, "half", dev) for bw in _bert_blocks(model)])
|
| 263 |
+
f32 = lambda t: t.detach().float().to(dev)
|
| 264 |
+
lns = [model.embeddings.LayerNorm] + [l.output.LayerNorm for l in model.encoder.layer]
|
| 265 |
+
st = {"runner": runner, "D": D, "heads": heads, "H": H, "tabs": {},
|
| 266 |
+
"ln_in": [(f32(m.weight), f32(m.bias), float(m.eps)) for m in lns],
|
| 267 |
+
"ln_attn": [(f32(l.attention.output.LayerNorm.weight), f32(l.attention.output.LayerNorm.bias),
|
| 268 |
+
float(l.attention.output.LayerNorm.eps)) for l in model.encoder.layer]}
|
| 269 |
+
setattr(model, _POST, st)
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
@torch._dynamo.disable
|
| 273 |
+
def _bert_tab(st, S, dev):
|
| 274 |
+
if S not in st["tabs"]:
|
| 275 |
+
D = st["D"] # no RoPE: identity tables (the kernel skips them, norope=1)
|
| 276 |
+
st["tabs"][S] = rope_tables(torch.ones(S, D), torch.zeros(S, D), 0, S, st["heads"], D, "half", dev)
|
| 277 |
+
return st["tabs"][S]
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
@torch._dynamo.disable
|
| 281 |
+
def _bert_masked(attention_mask) -> bool:
|
| 282 |
+
"""True if any position is masked out. Runs outside the compiled graph (one small host check per call)."""
|
| 283 |
+
return not bool(torch.all(attention_mask == 1))
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
class NeuronXLMRobertaModel(torch.nn.Module):
|
| 287 |
+
"""Drop-in forward for ``XLMRobertaModel`` / ``RobertaModel`` / ``BertModel`` (post-LN, GELU, absolute positions).
|
| 288 |
+
|
| 289 |
+
Graft on an instance (``model.forward = types.MethodType(NeuronXLMRobertaModel.forward, model)``); do not use it
|
| 290 |
+
as a class-level KernelConfig replacement, because the fallback calls ``type(model).forward``.
|
| 291 |
+
|
| 292 |
+
Returns ``last_hidden_state``. Kernel path: LNC=1, sequence length a multiple of 128 and <= 2048, all positions
|
| 293 |
+
attended (``attention_mask`` all ones / no padding). Anything else (padding mask, LNC=2, other lengths,
|
| 294 |
+
off-device, training) falls back to the stock forward.
|
| 295 |
+
"""
|
| 296 |
+
|
| 297 |
+
can_torch_compile = True
|
| 298 |
+
has_backward = False
|
| 299 |
+
|
| 300 |
+
def forward(self, input_ids=None, attention_mask=None, token_type_ids=None, position_ids=None, **kwargs):
|
| 301 |
+
import torch.nn.functional as F
|
| 302 |
+
from transformers.modeling_outputs import BaseModelOutputWithPoolingAndCrossAttentions as Out
|
| 303 |
+
from .encoder import _ab1ln
|
| 304 |
+
from .nki_kernels.mlp import nki_mlp_k4_xp
|
| 305 |
+
from .nki_kernels.mlp_res_ln import nki_mlp_res_ln_post
|
| 306 |
+
from .nki_kernels.res_layernorm import nki_res_layernorm_fold
|
| 307 |
+
S = input_ids.shape[1] if input_ids is not None else -1
|
| 308 |
+
# The mask check runs outside the compiled graph (one host sync per call); pass attention_mask=None to skip it.
|
| 309 |
+
masked = attention_mask is not None and _bert_masked(attention_mask)
|
| 310 |
+
if (input_ids is None or input_ids.device.type != "neuron" or self.training or masked or position_ids is not None
|
| 311 |
+
or kwargs.get("inputs_embeds") is not None or not _bert_ok(self, S)):
|
| 312 |
+
# stock forward of the grafted instance's own class (XLMRobertaModel / RobertaModel / BertModel). The graft
|
| 313 |
+
# sets an INSTANCE attribute (types.MethodType), so the class attribute is still the stock forward.
|
| 314 |
+
return type(self).forward(self, input_ids=input_ids, attention_mask=attention_mask,
|
| 315 |
+
token_type_ids=token_type_ids, position_ids=position_ids, **kwargs)
|
| 316 |
+
if not hasattr(self, _POST):
|
| 317 |
+
_bert_freeze(self)
|
| 318 |
+
st = getattr(self, _POST)
|
| 319 |
+
R, H, D = st["runner"], st["H"], st["D"]
|
| 320 |
+
B = input_ids.shape[0]
|
| 321 |
+
# the kernels need the PRE-LayerNorm embedding sum (the embedding LN is block 0's "ln1"); same as embeddings()
|
| 322 |
+
e = self.embeddings
|
| 323 |
+
pos = e.position_ids[:, :S] if getattr(e, "padding_idx", None) is None else None
|
| 324 |
+
if pos is None:
|
| 325 |
+
m = input_ids.ne(e.padding_idx).int()
|
| 326 |
+
pos = (torch.cumsum(m, dim=1) * m).long() + e.padding_idx
|
| 327 |
+
tt = token_type_ids if token_type_ids is not None else torch.zeros_like(input_ids)
|
| 328 |
+
x = (e.word_embeddings(input_ids) + e.position_embeddings(pos) + e.token_type_embeddings(tt)).float()
|
| 329 |
+
x = x.reshape(B * S, H).contiguous()
|
| 330 |
+
tab = _bert_tab(st, S, x.device)
|
| 331 |
+
g0, b0, eps0 = st["ln_in"][0]
|
| 332 |
+
_, z1 = nki_res_layernorm_fold(x, x.new_zeros(1, x.shape[0], H, dtype=torch.bfloat16), eps0)
|
| 333 |
+
h = F.layer_norm(x, (H,), g0, b0, eps0)
|
| 334 |
+
nL = len(R.L)
|
| 335 |
+
from .encoder import _PACK2
|
| 336 |
+
pk2 = int(_PACK2 and B % 2 == 0 and 2 * S <= 512)
|
| 337 |
+
for li, p in enumerate(R.L):
|
| 338 |
+
ga, ba, _ = st["ln_attn"][li]
|
| 339 |
+
_, z2, h1 = _ab1ln.attention_block_l1_ln_post(
|
| 340 |
+
z1, p["wqk"], p["bqk"], tab, p["wvt"], p["bv"], p["wot"], p["bo_full"], h, p["scale"], D, S, p["eps2"],
|
| 341 |
+
ga, ba, B, R._res(_ab1ln, S), pk2, 1)
|
| 342 |
+
go, bo, eo = st["ln_in"][li + 1]
|
| 343 |
+
if li + 1 < nL:
|
| 344 |
+
_, z1, h = nki_mlp_res_ln_post(z2, p["w1t"], p["b1"], p["w2t"], p["b2"], h1, eo, go, bo)
|
| 345 |
+
else:
|
| 346 |
+
h = F.layer_norm(h1 + nki_mlp_k4_xp(z2, p["w1t"], p["b1"], p["w2t"], p["b2"]).float(), (H,), go, bo, eo)
|
| 347 |
+
out = h.reshape(B, S, H)
|
| 348 |
+
return Out(last_hidden_state=out, pooler_output=None)
|
build/torch-neuron/nki_kernels/__init__.py
CHANGED
|
@@ -10,16 +10,17 @@ from .attention_block import (
|
|
| 10 |
pack_weights,
|
| 11 |
)
|
| 12 |
from .attention_block_l1 import attention_block_l1
|
| 13 |
-
from .attention_block_l1_ln import attention_block_l1_ln
|
| 14 |
from .mlp import nki_mlp_k4_xp as mlp
|
| 15 |
from .mlp import pack_b1
|
| 16 |
from .mlp_res_ln import nki_mlp_res_ln as mlp_res_ln
|
|
|
|
| 17 |
from .res_layernorm import nki_res_layernorm_fold as res_layernorm_fold
|
| 18 |
from .swiglu_stream import nki_swiglu_stream as swiglu_stream
|
| 19 |
from .swiglu_stream import nki_swiglu_stream_ln as swiglu_stream_ln
|
| 20 |
|
| 21 |
__all__ = [
|
| 22 |
-
"attention_block", "attention_block_l1", "attention_block_l1_ln", "res_layernorm_fold",
|
| 23 |
-
"mlp", "mlp_res_ln", "pack_b1", "swiglu_stream", "swiglu_stream_ln",
|
| 24 |
"pack_weights", "pack_qk_bias", "pack_tables", "pack_v_weights", "pack_out_weights", "lane_permutation",
|
| 25 |
]
|
|
|
|
| 10 |
pack_weights,
|
| 11 |
)
|
| 12 |
from .attention_block_l1 import attention_block_l1
|
| 13 |
+
from .attention_block_l1_ln import attention_block_l1_ln, attention_block_l1_ln_post
|
| 14 |
from .mlp import nki_mlp_k4_xp as mlp
|
| 15 |
from .mlp import pack_b1
|
| 16 |
from .mlp_res_ln import nki_mlp_res_ln as mlp_res_ln
|
| 17 |
+
from .mlp_res_ln import nki_mlp_res_ln_post as mlp_res_ln_post
|
| 18 |
from .res_layernorm import nki_res_layernorm_fold as res_layernorm_fold
|
| 19 |
from .swiglu_stream import nki_swiglu_stream as swiglu_stream
|
| 20 |
from .swiglu_stream import nki_swiglu_stream_ln as swiglu_stream_ln
|
| 21 |
|
| 22 |
__all__ = [
|
| 23 |
+
"attention_block", "attention_block_l1", "attention_block_l1_ln", "attention_block_l1_ln_post", "res_layernorm_fold",
|
| 24 |
+
"mlp", "mlp_res_ln", "mlp_res_ln_post", "pack_b1", "swiglu_stream", "swiglu_stream_ln",
|
| 25 |
"pack_weights", "pack_qk_bias", "pack_tables", "pack_v_weights", "pack_out_weights", "lane_permutation",
|
| 26 |
]
|
build/torch-neuron/nki_kernels/attention_block_l1_ln.py
CHANGED
|
@@ -87,7 +87,8 @@ def sbuf_estimate(H: int, Fp: int, N: int, D: int, resident: bool) -> int:
|
|
| 87 |
|
| 88 |
@nki.jit
|
| 89 |
def _attention_block_l1_ln_kernel(h_hbm, wqk_hbm, bqk_hbm, tab_hbm, wvt_hbm, bv_hbm,
|
| 90 |
-
wot_hbm, bo_hbm, x_hbm, scale, head_dim, n_valid, eps, nimg=1, resident=0, pack2=0
|
|
|
|
| 91 |
NT, H = h_hbm.shape
|
| 92 |
N = NT // nimg
|
| 93 |
Fp = wqk_hbm.shape[2]
|
|
@@ -117,6 +118,9 @@ def _attention_block_l1_ln_kernel(h_hbm, wqk_hbm, bqk_hbm, tab_hbm, wvt_hbm, bv_
|
|
| 117 |
_kernel_assert(NT == nimg * N, "rows must be nimg x N")
|
| 118 |
|
| 119 |
_kernel_assert(x_hbm.shape == (NT, H), "x must be [nimg*N, H] fp32")
|
|
|
|
|
|
|
|
|
|
| 120 |
xnew_hbm = nl.ndarray((NT, H), dtype=nl.float32, buffer=nl.shared_hbm)
|
| 121 |
z_hbm = nl.ndarray((NT, H), dtype=nl.bfloat16, buffer=nl.shared_hbm)
|
| 122 |
inv_h = 1.0 / float(H)
|
|
@@ -158,13 +162,15 @@ def _attention_block_l1_ln_kernel(h_hbm, wqk_hbm, bqk_hbm, tab_hbm, wvt_hbm, bv_
|
|
| 158 |
r_wv.append(wv_sb_)
|
| 159 |
r_b.append(b_sb_)
|
| 160 |
r_bv.append(bv_bc_)
|
| 161 |
-
r_tab =
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
| 168 |
r_wo = ra.alloc_sbuf_tensor(shape=(_P_MAX, H), dtype=nl.bfloat16,
|
| 169 |
block_dim=[G * n_ochunks], num_free_tiles=[G * n_ochunks])
|
| 170 |
for oc in range(G * n_ochunks):
|
|
@@ -208,7 +214,9 @@ def _attention_block_l1_ln_kernel(h_hbm, wqk_hbm, bqk_hbm, tab_hbm, wvt_hbm, bv_
|
|
| 208 |
)
|
| 209 |
|
| 210 |
# rope tables: shared by every group.
|
| 211 |
-
if
|
|
|
|
|
|
|
| 212 |
tab = r_tab
|
| 213 |
elif PK == 2:
|
| 214 |
# pack2: the same per-image tables, side by side (one 512-wide tile)
|
|
@@ -316,20 +324,26 @@ def _attention_block_l1_ln_kernel(h_hbm, wqk_hbm, bqk_hbm, tab_hbm, wvt_hbm, bv_
|
|
| 316 |
nisa.nc_matmul(dst=ps[:, 0:tw],
|
| 317 |
stationary=w_sb[qk][c][:, cols:cols + _P_MAX],
|
| 318 |
moving=x_t[c][tt][:, 0:tw], accumulate=(c > 0))
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
for b in range(quad):
|
| 334 |
lh = j * quad + b
|
| 335 |
r0 = b * half
|
|
@@ -413,6 +427,11 @@ def _attention_block_l1_ln_kernel(h_hbm, wqk_hbm, bqk_hbm, tab_hbm, wvt_hbm, bv_
|
|
| 413 |
st.append(allocator.alloc_sbuf_tensor(shape=(_P_MAX, 1), dtype=nl.float32,
|
| 414 |
block_dim=[5], num_free_tiles=[5], align_to=4))
|
| 415 |
sink = allocator.alloc_sbuf_tensor(shape=(_P_MAX, H), dtype=nl.float32, align_to=4)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
n_ob = (H + _PSUM_FMAX - 1) // _PSUM_FMAX
|
| 417 |
for tb in range(n_grp):
|
| 418 |
k = tb % _RING
|
|
@@ -455,10 +474,21 @@ def _attention_block_l1_ln_kernel(h_hbm, wqk_hbm, bqk_hbm, tab_hbm, wvt_hbm, bv_
|
|
| 455 |
nisa.tensor_scalar(dst=z_t[k], data=row[k], op0=nl.add, operand0=neg_mu,
|
| 456 |
op1=nl.multiply, operand1=rstd)
|
| 457 |
nisa.dma_copy(dst=z_hbm[r0:r0 + _P_MAX, 0:H], src=z_t[k])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
return xnew_hbm, z_hbm
|
| 459 |
|
| 460 |
|
| 461 |
_ATTN_L1_LN = wrap_nki_to_torch_hop(_attention_block_l1_ln_kernel[1])
|
|
|
|
|
|
|
| 462 |
|
| 463 |
|
| 464 |
@nki_op("vit_enc::attention_block_l1_ln", mutates_args={})
|
|
@@ -489,3 +519,20 @@ def attention_block_l1_ln(
|
|
| 489 |
if bqk.dim() == 3 and bqk.shape[1] != 128:
|
| 490 |
bqk = _pack_qk_bias(bqk)
|
| 491 |
return _ATTN_L1_LN(h, wqk, bqk, tab, wvt, bv, wot, bo, x, scale, head_dim, n_valid, eps, nimg, resident, pack2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
@nki.jit
|
| 89 |
def _attention_block_l1_ln_kernel(h_hbm, wqk_hbm, bqk_hbm, tab_hbm, wvt_hbm, bv_hbm,
|
| 90 |
+
wot_hbm, bo_hbm, x_hbm, scale, head_dim, n_valid, eps, nimg=1, resident=0, pack2=0,
|
| 91 |
+
g_hbm=None, bt_hbm=None, norope=0):
|
| 92 |
NT, H = h_hbm.shape
|
| 93 |
N = NT // nimg
|
| 94 |
Fp = wqk_hbm.shape[2]
|
|
|
|
| 118 |
_kernel_assert(NT == nimg * N, "rows must be nimg x N")
|
| 119 |
|
| 120 |
_kernel_assert(x_hbm.shape == (NT, H), "x must be [nimg*N, H] fp32")
|
| 121 |
+
post = g_hbm is not None # post-LN mode: also emit h = LN(x_new) * g + bt (fp32)
|
| 122 |
+
if post:
|
| 123 |
+
hout_hbm = nl.ndarray((NT, H), dtype=nl.float32, buffer=nl.shared_hbm)
|
| 124 |
xnew_hbm = nl.ndarray((NT, H), dtype=nl.float32, buffer=nl.shared_hbm)
|
| 125 |
z_hbm = nl.ndarray((NT, H), dtype=nl.bfloat16, buffer=nl.shared_hbm)
|
| 126 |
inv_h = 1.0 / float(H)
|
|
|
|
| 162 |
r_wv.append(wv_sb_)
|
| 163 |
r_b.append(b_sb_)
|
| 164 |
r_bv.append(bv_bc_)
|
| 165 |
+
r_tab = None
|
| 166 |
+
if not norope:
|
| 167 |
+
r_tab = ra.alloc_sbuf_tensor(shape=(_P_MAX, _PSUM_FMAX), dtype=_ROPE_DT,
|
| 168 |
+
block_dim=[4, n_tt], num_free_tiles=[4, n_tt], align_to=4)
|
| 169 |
+
for s in range(n_tt):
|
| 170 |
+
for t_i in range(4):
|
| 171 |
+
_tw = min(_PSUM_FMAX, N - s * _PSUM_FMAX)
|
| 172 |
+
nisa.dma_copy(dst=r_tab[t_i][s][:, 0:_tw],
|
| 173 |
+
src=tab_hbm[t_i, 0:_P_MAX, s * _PSUM_FMAX:s * _PSUM_FMAX + _tw])
|
| 174 |
r_wo = ra.alloc_sbuf_tensor(shape=(_P_MAX, H), dtype=nl.bfloat16,
|
| 175 |
block_dim=[G * n_ochunks], num_free_tiles=[G * n_ochunks])
|
| 176 |
for oc in range(G * n_ochunks):
|
|
|
|
| 214 |
)
|
| 215 |
|
| 216 |
# rope tables: shared by every group.
|
| 217 |
+
if norope:
|
| 218 |
+
tab = None
|
| 219 |
+
elif resident and PK == 1:
|
| 220 |
tab = r_tab
|
| 221 |
elif PK == 2:
|
| 222 |
# pack2: the same per-image tables, side by side (one 512-wide tile)
|
|
|
|
| 324 |
nisa.nc_matmul(dst=ps[:, 0:tw],
|
| 325 |
stationary=w_sb[qk][c][:, cols:cols + _P_MAX],
|
| 326 |
moving=x_t[c][tt][:, 0:tw], accumulate=(c > 0))
|
| 327 |
+
if norope:
|
| 328 |
+
# no positional rotation (e.g. BERT/XLM-R): bias-add straight into the roped buffers
|
| 329 |
+
nisa.tensor_scalar(dst=roped[par][:, 0:tw], data=ps[:, 0:tw],
|
| 330 |
+
op0=nl.add, operand0=b_sb[qk][:, gq:gq + 1])
|
| 331 |
+
else:
|
| 332 |
+
nisa.tensor_scalar(dst=proj[par][:, 0:tw], data=ps[:, 0:tw],
|
| 333 |
+
op0=nl.add, operand0=b_sb[qk][:, gq:gq + 1])
|
| 334 |
+
if not norope:
|
| 335 |
+
nisa.tensor_tensor(dst=acc[:, 0:tw], data1=proj[0][:, 0:tw],
|
| 336 |
+
data2=tab[0][tt][:, 0:tw], op=nl.multiply)
|
| 337 |
+
nisa.tensor_tensor(dst=tmp[:, 0:tw], data1=proj[1][:, 0:tw],
|
| 338 |
+
data2=tab[2][tt][:, 0:tw], op=nl.multiply)
|
| 339 |
+
nisa.tensor_tensor(dst=roped[0][:, 0:tw], data1=acc[:, 0:tw],
|
| 340 |
+
data2=tmp[:, 0:tw], op=nl.add)
|
| 341 |
+
nisa.tensor_tensor(dst=acc[:, 0:tw], data1=proj[1][:, 0:tw],
|
| 342 |
+
data2=tab[1][tt][:, 0:tw], op=nl.multiply)
|
| 343 |
+
nisa.tensor_tensor(dst=tmp[:, 0:tw], data1=proj[0][:, 0:tw],
|
| 344 |
+
data2=tab[3][tt][:, 0:tw], op=nl.multiply)
|
| 345 |
+
nisa.tensor_tensor(dst=roped[1][:, 0:tw], data1=acc[:, 0:tw],
|
| 346 |
+
data2=tmp[:, 0:tw], op=nl.add)
|
| 347 |
for b in range(quad):
|
| 348 |
lh = j * quad + b
|
| 349 |
r0 = b * half
|
|
|
|
| 427 |
st.append(allocator.alloc_sbuf_tensor(shape=(_P_MAX, 1), dtype=nl.float32,
|
| 428 |
block_dim=[5], num_free_tiles=[5], align_to=4))
|
| 429 |
sink = allocator.alloc_sbuf_tensor(shape=(_P_MAX, H), dtype=nl.float32, align_to=4)
|
| 430 |
+
if post:
|
| 431 |
+
g_bc = allocator.alloc_sbuf_tensor(shape=(_P_MAX, H), dtype=nl.float32, align_to=4)
|
| 432 |
+
bt_bc = allocator.alloc_sbuf_tensor(shape=(_P_MAX, H), dtype=nl.float32, align_to=4)
|
| 433 |
+
nisa.dma_copy(dst=g_bc, src=g_hbm.ap(pattern=[[0, _P_MAX], [1, H]], offset=0))
|
| 434 |
+
nisa.dma_copy(dst=bt_bc, src=bt_hbm.ap(pattern=[[0, _P_MAX], [1, H]], offset=0))
|
| 435 |
n_ob = (H + _PSUM_FMAX - 1) // _PSUM_FMAX
|
| 436 |
for tb in range(n_grp):
|
| 437 |
k = tb % _RING
|
|
|
|
| 474 |
nisa.tensor_scalar(dst=z_t[k], data=row[k], op0=nl.add, operand0=neg_mu,
|
| 475 |
op1=nl.multiply, operand1=rstd)
|
| 476 |
nisa.dma_copy(dst=z_hbm[r0:r0 + _P_MAX, 0:H], src=z_t[k])
|
| 477 |
+
if post:
|
| 478 |
+
# h = (x_new - mu) * rstd * g + bt, fp32; reuse `sink` (its reduce output is dead here)
|
| 479 |
+
nisa.tensor_scalar(dst=sink, data=row[k], op0=nl.add, operand0=neg_mu,
|
| 480 |
+
op1=nl.multiply, operand1=rstd)
|
| 481 |
+
nisa.tensor_tensor(dst=sink, data1=sink, data2=g_bc, op=nl.multiply)
|
| 482 |
+
nisa.tensor_tensor(dst=sink, data1=sink, data2=bt_bc, op=nl.add)
|
| 483 |
+
nisa.dma_copy(dst=hout_hbm[r0:r0 + _P_MAX, 0:H], src=sink)
|
| 484 |
+
if post:
|
| 485 |
+
return xnew_hbm, z_hbm, hout_hbm
|
| 486 |
return xnew_hbm, z_hbm
|
| 487 |
|
| 488 |
|
| 489 |
_ATTN_L1_LN = wrap_nki_to_torch_hop(_attention_block_l1_ln_kernel[1])
|
| 490 |
+
_ATTN_L1_LN_POST = wrap_nki_to_torch_hop(_attention_block_l1_ln_kernel[1])
|
| 491 |
+
_ATTN_L1_LN_POST_NR = _ATTN_L1_LN_POST
|
| 492 |
|
| 493 |
|
| 494 |
@nki_op("vit_enc::attention_block_l1_ln", mutates_args={})
|
|
|
|
| 519 |
if bqk.dim() == 3 and bqk.shape[1] != 128:
|
| 520 |
bqk = _pack_qk_bias(bqk)
|
| 521 |
return _ATTN_L1_LN(h, wqk, bqk, tab, wvt, bv, wot, bo, x, scale, head_dim, n_valid, eps, nimg, resident, pack2)
|
| 522 |
+
|
| 523 |
+
|
| 524 |
+
@nki_op("vit_enc::attention_block_l1_ln_post", mutates_args={})
|
| 525 |
+
def attention_block_l1_ln_post(
|
| 526 |
+
h: torch.Tensor, wqk: torch.Tensor, bqk: torch.Tensor, tab: torch.Tensor, wvt: torch.Tensor, bv: torch.Tensor,
|
| 527 |
+
wot: torch.Tensor, bo: torch.Tensor, x: torch.Tensor, scale: float, head_dim: int, n_valid: int, eps: float,
|
| 528 |
+
g: torch.Tensor, bt: torch.Tensor, nimg: int = 1, resident: int = 0, pack2: int = 0, norope: int = 0,
|
| 529 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 530 |
+
"""Post-LN variant: (x_new fp32, z2 bf16 = LN0(x_new), h fp32 = LN(x_new) * g + bt). g/bt: [H] fp32."""
|
| 531 |
+
for t in (h, wqk, bqk, tab, wvt, bv, wot, bo, x, g, bt):
|
| 532 |
+
if t is not None and t.requires_grad:
|
| 533 |
+
raise RuntimeError("attention_block_l1_ln_post is inference-only; use torch.no_grad().")
|
| 534 |
+
if h.shape[0] // nimg > 2048:
|
| 535 |
+
raise NotImplementedError("attention_block_l1_ln_post supports N<=2048 only")
|
| 536 |
+
if bqk.dim() == 3 and bqk.shape[1] != 128:
|
| 537 |
+
bqk = _pack_qk_bias(bqk)
|
| 538 |
+
return _ATTN_L1_LN_POST(h, wqk, bqk, tab, wvt, bv, wot, bo, x, scale, head_dim, n_valid, eps, nimg, resident, pack2, g, bt, norope)
|
build/torch-neuron/nki_kernels/mlp_res_ln.py
CHANGED
|
@@ -54,7 +54,7 @@ def _kernel_assert(condition: bool, error_text: str):
|
|
| 54 |
|
| 55 |
|
| 56 |
@nki.jit
|
| 57 |
-
def _mlp_res_ln_kernel(x_hbm, w1t_hbm, b1_hbm, w2t_hbm, b2_hbm, a_hbm, eps):
|
| 58 |
"""gelu(x @ w1.T + b1) @ w2.T + b2, rows sharded, b1 AND both x blocks resident early.
|
| 59 |
|
| 60 |
Args:
|
|
@@ -91,6 +91,9 @@ def _mlp_res_ln_kernel(x_hbm, w1t_hbm, b1_hbm, w2t_hbm, b2_hbm, a_hbm, eps):
|
|
| 91 |
_kernel_assert(tuple(a_hbm.shape) == (R, K), "residual a must be [R, K] fp32")
|
| 92 |
xnew_hbm = nl.ndarray((R, K), dtype=nl.float32, buffer=nl.shared_hbm)
|
| 93 |
z_hbm = nl.ndarray((R, K), dtype=_BF16, buffer=nl.shared_hbm)
|
|
|
|
|
|
|
|
|
|
| 94 |
inv_k = 1.0 / float(K)
|
| 95 |
neg_inv_k = -inv_k
|
| 96 |
|
|
@@ -141,6 +144,11 @@ def _mlp_res_ln_kernel(x_hbm, w1t_hbm, b1_hbm, w2t_hbm, b2_hbm, a_hbm, eps):
|
|
| 141 |
# b2 broadcast ONCE over all K columns (fp32), plus LN per-lane scratch.
|
| 142 |
b2_bc = nl.ndarray((_P_MAX, K), dtype=nl.float32, buffer=nl.sbuf)
|
| 143 |
nisa.dma_copy(dst=b2_bc[0:_P_MAX, 0:K], src=b2_hbm.ap(pattern=[[0, _P_MAX], [1, K]], offset=0))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
acc_s = nl.ndarray((_P_MAX, 1), dtype=nl.float32, buffer=nl.sbuf)
|
| 145 |
neg_shift = nl.ndarray((_P_MAX, 1), dtype=nl.float32, buffer=nl.sbuf)
|
| 146 |
neg_mu = nl.ndarray((_P_MAX, 1), dtype=nl.float32, buffer=nl.sbuf)
|
|
@@ -221,7 +229,18 @@ def _mlp_res_ln_kernel(x_hbm, w1t_hbm, b1_hbm, w2t_hbm, b2_hbm, a_hbm, eps):
|
|
| 221 |
op0=nl.add, operand0=neg_mu[0:prs, 0:1],
|
| 222 |
op1=nl.multiply, operand1=rstd[0:prs, 0:1])
|
| 223 |
nisa.dma_copy(dst=z_hbm[r0:r0 + prs, 0:K], src=z_t[0:prs, 0:K])
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
return xnew_hbm, z_hbm
|
| 226 |
|
| 227 |
|
|
@@ -248,3 +267,15 @@ def nki_mlp_res_ln(
|
|
| 248 |
if b1.dim() == 1:
|
| 249 |
b1 = pack_b1(b1)
|
| 250 |
return _mlp_res_ln_kernel[LNC](x, w1t, b1, w2t, b2, a, eps)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
|
| 56 |
@nki.jit
|
| 57 |
+
def _mlp_res_ln_kernel(x_hbm, w1t_hbm, b1_hbm, w2t_hbm, b2_hbm, a_hbm, eps, g_hbm=None, bt_hbm=None):
|
| 58 |
"""gelu(x @ w1.T + b1) @ w2.T + b2, rows sharded, b1 AND both x blocks resident early.
|
| 59 |
|
| 60 |
Args:
|
|
|
|
| 91 |
_kernel_assert(tuple(a_hbm.shape) == (R, K), "residual a must be [R, K] fp32")
|
| 92 |
xnew_hbm = nl.ndarray((R, K), dtype=nl.float32, buffer=nl.shared_hbm)
|
| 93 |
z_hbm = nl.ndarray((R, K), dtype=_BF16, buffer=nl.shared_hbm)
|
| 94 |
+
post = g_hbm is not None # post-LN mode: also emit h = LN(x_new) * g + bt (fp32)
|
| 95 |
+
if post:
|
| 96 |
+
h_hbm = nl.ndarray((R, K), dtype=nl.float32, buffer=nl.shared_hbm)
|
| 97 |
inv_k = 1.0 / float(K)
|
| 98 |
neg_inv_k = -inv_k
|
| 99 |
|
|
|
|
| 144 |
# b2 broadcast ONCE over all K columns (fp32), plus LN per-lane scratch.
|
| 145 |
b2_bc = nl.ndarray((_P_MAX, K), dtype=nl.float32, buffer=nl.sbuf)
|
| 146 |
nisa.dma_copy(dst=b2_bc[0:_P_MAX, 0:K], src=b2_hbm.ap(pattern=[[0, _P_MAX], [1, K]], offset=0))
|
| 147 |
+
if post:
|
| 148 |
+
g_bc = nl.ndarray((_P_MAX, K), dtype=nl.float32, buffer=nl.sbuf)
|
| 149 |
+
bt_bc = nl.ndarray((_P_MAX, K), dtype=nl.float32, buffer=nl.sbuf)
|
| 150 |
+
nisa.dma_copy(dst=g_bc[0:_P_MAX, 0:K], src=g_hbm.ap(pattern=[[0, _P_MAX], [1, K]], offset=0))
|
| 151 |
+
nisa.dma_copy(dst=bt_bc[0:_P_MAX, 0:K], src=bt_hbm.ap(pattern=[[0, _P_MAX], [1, K]], offset=0))
|
| 152 |
acc_s = nl.ndarray((_P_MAX, 1), dtype=nl.float32, buffer=nl.sbuf)
|
| 153 |
neg_shift = nl.ndarray((_P_MAX, 1), dtype=nl.float32, buffer=nl.sbuf)
|
| 154 |
neg_mu = nl.ndarray((_P_MAX, 1), dtype=nl.float32, buffer=nl.sbuf)
|
|
|
|
| 229 |
op0=nl.add, operand0=neg_mu[0:prs, 0:1],
|
| 230 |
op1=nl.multiply, operand1=rstd[0:prs, 0:1])
|
| 231 |
nisa.dma_copy(dst=z_hbm[r0:r0 + prs, 0:K], src=z_t[0:prs, 0:K])
|
| 232 |
+
if post:
|
| 233 |
+
# h = (x_new - mu) * rstd * g + bt in fp32 (the post-LN residual for the next sublayer)
|
| 234 |
+
h_t = nl.ndarray((_P_MAX, K), dtype=nl.float32, buffer=nl.sbuf)
|
| 235 |
+
nisa.tensor_scalar(dst=h_t[0:prs, 0:K], data=row[0:prs, 0:K],
|
| 236 |
+
op0=nl.add, operand0=neg_mu[0:prs, 0:1],
|
| 237 |
+
op1=nl.multiply, operand1=rstd[0:prs, 0:1])
|
| 238 |
+
nisa.tensor_tensor(dst=h_t[0:prs, 0:K], data1=h_t[0:prs, 0:K], data2=g_bc[0:prs, 0:K], op=nl.multiply)
|
| 239 |
+
nisa.tensor_tensor(dst=h_t[0:prs, 0:K], data1=h_t[0:prs, 0:K], data2=bt_bc[0:prs, 0:K], op=nl.add)
|
| 240 |
+
nisa.dma_copy(dst=h_hbm[r0:r0 + prs, 0:K], src=h_t[0:prs, 0:K])
|
| 241 |
+
|
| 242 |
+
if post:
|
| 243 |
+
return xnew_hbm, z_hbm, h_hbm
|
| 244 |
return xnew_hbm, z_hbm
|
| 245 |
|
| 246 |
|
|
|
|
| 267 |
if b1.dim() == 1:
|
| 268 |
b1 = pack_b1(b1)
|
| 269 |
return _mlp_res_ln_kernel[LNC](x, w1t, b1, w2t, b2, a, eps)
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
@nki_op("vit_enc::nki_mlp_res_ln_post", mutates_args={})
|
| 273 |
+
def nki_mlp_res_ln_post(
|
| 274 |
+
x: torch.Tensor, w1t: torch.Tensor, b1: torch.Tensor, w2t: torch.Tensor, b2: torch.Tensor,
|
| 275 |
+
a: torch.Tensor, eps: float, g: torch.Tensor, bt: torch.Tensor,
|
| 276 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 277 |
+
"""Post-LN variant: (x_new fp32, z bf16 = LN0(x_new), h fp32 = LN(x_new) * g + bt). g/bt: [K] fp32."""
|
| 278 |
+
_no_grad(x, w1t, b1, w2t, b2, a, g, bt)
|
| 279 |
+
if b1.dim() == 1:
|
| 280 |
+
b1 = pack_b1(b1)
|
| 281 |
+
return _mlp_res_ln_kernel[LNC](x, w1t, b1, w2t, b2, a, eps, g, bt)
|