Instructions to use jburtoft/gelu-erf-neuron with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Kernels
How to use jburtoft/gelu-erf-neuron with Kernels:
# !pip install kernels from kernels import get_kernel kernel = get_kernel("jburtoft/gelu-erf-neuron") - Notebooks
- Google Colab
- Kaggle
Exact-erf GELU NKI Kernel for AWS Trainium/Inferentia
Version 1.0.0: Fused exact-erf GELU forward + backward for PyTorch Native on Neuron. The transcendental (erf) is computed by a single hardware activation-table instruction on the Scalar/activation engine, replacing the compiler's multi-engine rational-erf lowering. Drop-in replacement for nn.GELU() (default, exact/erf), with a custom NKI backward. Integrates under torch.compile(backend="neuron") at 1 graph / 0 graph breaks.
What ships
Two NKI kernels + a functional wrapper + an nn.Module. All figures below are measured on trn2.3xlarge, PyTorch Native Beta 4 (SDK 2.31, NKI 0.5.0), unless stated otherwise.
| Component | Purpose |
|---|---|
gelu_fwd |
Exact (erf) GELU forward. [R, C] -> [R, C]. One nl.gelu activation-table op per 128-row tile. |
gelu_bwd |
Exact GELU backward. Needs the saved pre-activation x and incoming grad. One nl.gelu_dx gate (Scalar engine) + one Vector multiply per tile. |
gelu_erf |
Functional API: torch.autograd.Function wiring gelu_fwd (forward) and gelu_bwd (backward). |
GELUErf (nn.Module) |
Drop-in replacement for nn.GELU(). |
GELUErf is exact erf-GELU (not the tanh approximation). It applies to any model whose MLP uses nn.GELU() with default args (e.g. ViT-family, BERT-family). It was developed and measured on the DINOv3 ViT-L/16 MLP (hidden width 4096).
Quick start
Drop-in module replacement
import torch, torch.nn as nn
from kernels import get_kernel
# Personal (non-org) repo: pass trust_remote_code=True, and revision or version.
k = get_kernel("jburtoft/gelu-erf-neuron", trust_remote_code=True, revision="v1")
# Swap nn.GELU() for the NKI kernel in every MLP block of your model:
def swap_gelu(module):
n = 0
for m in module.modules():
if hasattr(m, "act") and isinstance(m.act, nn.GELU):
m.act = k.GELUErf()
n += 1
return n
model = model.to(torch.bfloat16).to("neuron")
swap_gelu(model)
compiled = torch.compile(model, backend="neuron", fullgraph=False, dynamic=False)
# forward + backward + optimizer step as usual; gradients flow through gelu_bwd
Functional API
import torch
from kernels import get_kernel
k = get_kernel("jburtoft/gelu-erf-neuron")
x = torch.randn(3216, 4096, device="neuron", dtype=torch.bfloat16, requires_grad=True)
y = k.gelu_erf(x) # forward via gelu_fwd
y.sum().backward() # backward via gelu_bwd
Raw kernels
from kernels import get_kernel
k = get_kernel("jburtoft/gelu-erf-neuron")
y = k.gelu_fwd(x2d) # x2d: [R, C] on device
dx = k.gelu_bwd(x2d, grad_out) # x2d = saved pre-activation, grad_out = dL/dy
Convention: gelu_fwd/gelu_bwd take 2-D [R, C] device tensors (partition dim tiled by 128, C = hidden free dim). The gelu_erf / GELUErf wrappers flatten leading dims to [R, C] and restore afterwards.
Correctness (measured, Beta 4 trn2.3xlarge, NKI 0.5.0)
Reference: CPU FP32 exact-erf GELU (torch.nn.functional.gelu, default) and its autograd derivative.
| Kernel | dtype | cos_sim | max_abs |
|---|---|---|---|
gelu_fwd |
FP32 | 1.000000 | 2.1e-6 |
gelu_bwd |
FP32 | 1.000000 | 3.1e-6 |
gelu_fwd |
BF16 | 0.999998 | 3.1e-2 |
gelu_bwd |
BF16 | 0.999995 | 4.0e-2 |
The FP32 cos_sim of 1.000000 confirms nl.gelu implements exact erf-GELU and nl.gelu_dx its exact derivative β no tanh substitution. Integrated into all 24 DINOv3 ViT-L MLP blocks, the 20-step training loss curve matched the native-nn.GELU baseline within 2.3e-3.
Performance (measured)
End-to-end (DINOv3 ViT-L/16 training, all 24 MLP blocks swapped)
Same-session A/B, torch.compile(backend="neuron"), BF16:
| Config | native nn.GELU |
GELUErf (NKI) |
Delta |
|---|---|---|---|
| Single-core, BS=16 (Beta 4) | 28.58 img/s | 30.30 img/s | +6.0% |
| FSDP2 DP=4, per-rank BS=32 (Beta 4) | 112.6 img/s | 121.9 img/s | +8.3% |
| Single-core, BS=16 (Beta 5, SDK 2.32) | β | β | +4.4% |
Integration: torch._dynamo.explain reports 1 graph, 0 graph breaks.
Standalone kernel profile ([3216, 4096], BF16, Beta 4)
Instruction-count comparison of the compiled op:
native nn.GELU lowering |
gelu_fwd (NKI) |
|
|---|---|---|
| MATMUL instrs | 1664 | 0 |
| RECIPROCAL instrs | 256 | 0 |
| Vector TENSOR_TENSOR instrs | 2816 | 0 |
| ACTIVATE instrs | 3584 | 26 |
| Vector-engine compute time | 2,501,177 ns | 1,047 ns |
The native lowering implements erf as a rational/polynomial expansion across the Tensor, Vector, and Scalar engines; the NKI kernel is one nl.gelu activation-table lookup per 128-row tile.
Engine-active share of the integrated training step, native β GELUErf: forward Vector-engine 75.3% β 63.1%; backward Vector-engine 49.7% β 36.1%. Forward MFU 10.0% β 10.7%; backward MFU 10.7% β 11.6%.
Standalone kernel bound (profiled)
| Metric | gelu_fwd |
gelu_bwd |
|---|---|---|
| total_time | 166.8 Β΅s | 218.5 Β΅s |
| dma_active_time_percent | 90.9% | 95.0% |
| MBU | 44.1% | 50.5% |
| Scalar ACTIVATE time | 93.9 Β΅s (hidden) | 93.9 Β΅s (hidden) |
| Vector TENSOR_TENSOR time | β | 59.6 Β΅s (hidden) |
| effective DMA throughput | 316 GB/s | 362 GB/s |
Both kernels are DMA-bound: the activation compute is fully overlapped under DMA, and all transfers run on a single DMA queue. Measured double-buffer, static-unroll, wider-free-dim, and dge_mode=hwdge variants were each bit-identical to the baseline (max_abs vs baseline = 0.0) and moved the end-to-end result by β0.08% (single-core) / +0.3% (replica-DP=4) β within noise.
Requirements
- Hardware: trn2 (measured on trn2.3xlarge).
- SDK: PyTorch Native Beta 4 (NKI 0.5.0, SDK 2.31). Also runs on Beta 5 (NKI 0.6.0, SDK 2.32) with no kernel changes.
- PyTorch: 2.11.0 (bundled with Beta 4).
- Kernels library:
pip install kernels.
Repository layout
build/torch-neuron/
βββ __init__.py # public API: GELUErf, gelu_erf, gelu_fwd, gelu_bwd
βββ metadata.json # HF kernels library metadata
βββ layers.py # GELUErf nn.Module (drop-in for nn.GELU)
βββ autograd.py # _FusedGELUErfFn autograd.Function + gelu_erf functional
βββ nki_kernels/
βββ __init__.py
βββ gelu_erf.py # gelu_fwd + gelu_bwd NKI kernels
Known limitations
- DMA-bound at the measured shape.
[3216, 4096]BF16: both kernels sit at the single-DMA-queue throughput ceiling (~316β362 GB/s) with compute fully hidden. Measured multi-buffering and DMA-routing variants produced no end-to-end change on this SDK/shape. - End-to-end gain is modest and workload-dependent. The measured +6.0% single-core / +8.3% FSDP2 on DINOv3 ViT-L reflects GELU being one op among the MLP matmuls the compiler already schedules well; the gain will differ on other models/shapes and should be re-measured for your workload.
- Exact-erf GELU only. For
nn.GELU(approximate="tanh")this kernel is not a drop-in.
Attribution
Original NKI kernels, developed and measured on DINOv3 ViT-L/16 training on trn2.3xlarge. Uses NKI 0.5.0 nl.gelu / nl.gelu_dx activation-table ops. Published under Apache 2.0.
License
Apache 2.0.
- Downloads last month
- -