thox-edge-ops

Kernel Hub Version License Backends Tests torch.compile

Fused inference kernels for small-model decode on THOX.ai edge hardware. Measured for v2 on x86_64 hosts and an NVIDIA desktop GPU (RTX 4060 Ti); Jetson Orin, Apple silicon and the Arm device class (ThoxAir, ThoxMini, ThoxClip) are targets: the Metal backend is in the source tree, but v2 has not been run on a Mac and no Metal variant is published for v2, and no aarch64 build is published for v2 (see Validation and Limitations). v2 adds the Gemma weight convention, two gated activations, rotary embedding and a fused per-head QK-norm plus RoPE prologue for Qwen3 and Gemma 3 class models, and a CPU backend that selects AVX2, FMA and F16C at run time from one baseline artifact.

Your AI. Your Data. Your Rules.(TM)

Quickstart

pip install -U kernels torch
import torch
from kernels import get_kernel

ops = get_kernel("Thox-ai/thox-edge-ops", version=2, trust_remote_code=["Thox-ai/thox-edge-ops"])
dev = "cuda" if torch.cuda.is_available() else "cpu"
dt = torch.float16 if dev == "cuda" else torch.bfloat16

x = torch.randn(1, 1152, device=dev, dtype=dt)
w = torch.zeros(1152, device=dev, dtype=dt)                     # Gemma stores zero-centred weights
y = ops.rms_norm(x, w, 1e-6, weight_offset=1.0)                 # (1 + w) convention

residual = torch.randn_like(x)
x, residual = ops.fused_add_rms_norm(x, residual, w, 1e-6, weight_offset=1.0)  # in place

h = ops.gelu_tanh_and_mul(torch.randn(1, 2 * 6912, device=dev, dtype=dt))

# Qwen3 / Gemma 3 attention prologue: per-head q_norm, k_norm, then RoPE, one launch per token
head, max_pos = 256, 4096
inv = 1.0 / (10000.0 ** (torch.arange(0, head, 2, device=dev).float() / head))
freqs = torch.outer(torch.arange(max_pos, device=dev).float(), inv)
cos_sin_cache = torch.cat([freqs.cos(), freqs.sin()], dim=-1).to(dt)
positions = torch.tensor([7], device=dev)
q = torch.randn(1, 8, head, device=dev, dtype=dt)
k = torch.randn(1, 4, head, device=dev, dtype=dt)
q_norm_weight = torch.zeros(head, device=dev, dtype=dt)
k_norm_weight = torch.zeros(head, device=dev, dtype=dt)
ops.fused_qk_norm_rope(positions, q, k, q_norm_weight, k_norm_weight, 1e-6, 1.0, head, cos_sin_cache)

trust_remote_code is required because Thox-ai is not on the kernels default trusted-publisher list; scope it to this repository as shown.

Operations

Function Computes Notes
rms_norm(x, weight, epsilon=1e-6, weight_offset=0.0, out=None) x * rsqrt(mean(x^2) + eps) * (weight + offset) over the last dim offset 0: Llama, Qwen, Mistral; offset 1: Gemma
fused_add_rms_norm(x, residual, weight, epsilon=1e-6, weight_offset=0.0) residual = x + residual; x = rms_norm(residual), both in place; returns (x, residual) residual bit-identical to eager addition
silu_and_mul(x, out=None) silu(x[..., :d]) * x[..., d:] for x of shape [..., 2d] SwiGLU MLPs
gelu_tanh_and_mul(x, out=None) gelu_tanh(x[..., :d]) * x[..., d:] Gemma MLPs (gelu_pytorch_tanh)
rotary_embedding(positions, query, key, head_size, cos_sin_cache, is_neox=True) in-place RoPE on the first rot_dim elements of every head NeoX (rotate-half) or GPT-J (interleaved) pairs
fused_qk_norm_rope(positions, query, key, q_weight, k_weight, epsilon, weight_offset, head_size, cos_sin_cache, is_neox=True) per-head RMSNorm of query and key (weights [head_size]), rounded, then RoPE, in place Qwen3 q_norm/k_norm (offset 0), Gemma 3 (offset 1)

All functions accept float32, float16 and bfloat16 tensors. Non-contiguous inputs are made contiguous by the Python wrapper. Invalid shapes, mismatched weight sizes and negative epsilon raise ValueError. cos_sin_cache is [max_position, rot_dim] with cos in the first half of each row and sin in the second; a token whose position lies outside the cache is left unrotated (pass check_positions=True to raise instead, at the cost of a device sync).

Layers

Layer Replaces Reads from the host module
layers.RMSNorm LlamaRMSNorm, Qwen2RMSNorm, Qwen3RMSNorm, MistralRMSNorm weight, variance_epsilon
layers.GemmaRMSNorm GemmaRMSNorm, Gemma2RMSNorm, Gemma3RMSNorm weight, eps
layers.SiluAndMul SwiGLU gate modules nothing
layers.GeluTanhAndMul Gemma gated GELU modules nothing

All layers are stateless, declare can_torch_compile = True and has_backward = False.

Use with transformers

from kernels import LayerRepository, Mode, kernelize, register_kernel_mapping

repo = LayerRepository(repo_id="Thox-ai/thox-edge-ops", layer_name="GemmaRMSNorm", version=2,
                       trust_remote_code=["Thox-ai/thox-edge-ops"])
register_kernel_mapping({"GemmaRMSNorm": {"cuda": repo, "mps": repo, "cpu": repo}})
model = kernelize(model, mode=Mode.INFERENCE)

Pass use_fallback=False to kernelize to fail loudly instead of keeping the original forward. Mixed-dtype checkpoints (fp32 norm weight with fp16 or bf16 activations) keep transformers' promotion semantics through the reference path.

Backends

Backend Targets Implementation
CUDA Jetson Orin (sm_87), Ampere, Ada, Hopper, Blackwell one block per row, warp-shuffle reductions, 128-bit vector access when aligned; one warp per head for the fused prologue
Metal Apple M1 to M4 (Metal 3.0) one threadgroup per row, simd_sum reductions, pipeline states cached per process; bf16 routed to the eager reference
CPU x86_64 (Hub artifacts); aarch64 builds from source baseline-ISA artifact; on x86_64 hosts with AVX2, FMA and F16C a second unit of intrinsic row kernels is selected at run time (THOX_EDGE_OPS_CPU_ISA=baseline forces the portable path)

Performance

Operator-level latency against eager PyTorch written the way transformers writes it, including Python dispatch. Not model throughput. Full tables, raw samples and the paper are in the source repository.

NVIDIA RTX 4060 Ti (CUDA, WSL2), float16. Geometric mean of median-latency ratios over the shapes below; decode = one token per call. 30 trials x 50 calls per configuration, eager and kernel interleaved.

Operator Decode speedup All shapes Range
rms_norm 6.90x 6.87x 6.83x to 7.03x
rms_norm_gemma 8.42x 8.47x 8.34x to 8.51x
fused_add_rms_norm 7.59x 7.64x 7.46x to 7.68x
silu_and_mul 1.76x 1.73x 1.30x to 1.83x
gelu_tanh_and_mul 1.80x 1.76x 1.06x to 2.42x
rotary_embedding 11.11x 11.40x 10.74x to 13.54x
fused_qk_norm_rope 24.43x 25.23x 24.28x to 29.89x

x86_64 CPU, AVX2 dispatch, float32. Geometric mean of median-latency ratios over the shapes below; decode = one token per call. 30 trials x 50 calls per configuration, eager and kernel interleaved.

Operator Decode speedup All shapes Range
rms_norm 2.42x 2.19x 1.70x to 2.91x
rms_norm_gemma 2.88x 2.31x 1.71x to 2.92x
fused_add_rms_norm 2.89x 2.46x 1.95x to 2.92x
silu_and_mul 1.30x 1.00x 0.69x to 1.50x
gelu_tanh_and_mul 1.82x 2.18x 1.62x to 5.21x
rotary_embedding 6.75x 7.17x 5.97x to 8.74x
fused_qk_norm_rope 2.86x 1.36x 0.81x to 2.89x

At decode sizes the GPU operators are launch bound, so the gain is the number of launches removed (about sixteen to one for the fused prologue). On the CPU host the bfloat16 gated activations at prefill sizes are slower than eager because ATen dispatches AVX-512 with sixteen lanes against the kernel's AVX2 eight; keep those on the decode path or route prefill to eager.

Numerics

  • Accumulation is fp32 for every dtype; outputs round once.
  • fused_add_rms_norm rounds x + residual to the input dtype before squaring, so the stored residual is bit-identical to eager x + residual (asserted with zero tolerance).
  • fused_qk_norm_rope rounds the per-head norm to the input dtype before rotating, matching the Qwen3 and Gemma 3 module order; it is asserted equal to rms_norm followed by rotary_embedding.
  • Test tolerances against the fp32 eager reference: 1e-5 (fp32), 2e-3 (fp16), 1.6e-2 (bf16).

torch.compile

Every op registers a fake implementation in the build-unique op namespace, so torch.compile(fullgraph=True) traces them with zero graph breaks.

Validation

Check Result
CUDA, NVIDIA RTX 4060 Ti (sm_89), torch 2.14.1+cu126, WSL2 438 / 438 tests pass
Hub artifact torch214-cu126-x86_64-linux loaded with get_kernel(version=2), RTX 4060 Ti 438 / 438 tests pass
Hub artifact torch213-cu130-x86_64-linux loaded with get_kernel(version=2), RTX 4060 Ti 438 / 438 tests pass
Hub artifact torch215-cpu-x86_64-linux loaded with get_kernel(version=2), x86_64 CPU 438 / 438 tests pass
kernel-builder ABI check (manylinux_2_28, Python ABI 3.9) and load check, all 23 published variants pass
CPU x86_64, AVX2 dispatch active, torch 2.15 438 / 438 tests pass
CPU x86_64, baseline ISA forced 438 / 438 tests pass
objdump scan of the CPU build every ymm instruction lies in the thox_cpu_avx2 namespace
kernelize swap with use_fallback=False, torch.compile(fullgraph=True) pass (CPU, CUDA)
Metal v2 Metal source not yet run on a Mac; no Metal variant is published for v2 (v1 keeps its Metal build)
Jetson Orin on-device run pending

Published variants

Branch v2, 23 builds from kernel-builder: CPU for torch 2.10 to 2.15, and CUDA 12.6, 12.8, 13.0, 13.2 and 13.4 builds matching each torch release's supported toolkits, all x86_64-linux. The kernels loader picks the one matching the installed torch and CUDA. Branch v1 keeps the v1 builds, including Metal.

Limitations

  • Inference only: no backward pass.
  • bfloat16 on Metal runs an eager fallback (Metal 3.0 has no bfloat type).
  • fused_add_rms_norm needs distinct, contiguous x and residual; anything else takes an out-of-place path.
  • Per-head norm weights are shared across heads ([head_size]), which is the Qwen3 and Gemma 3 layout; per-head-distinct weights are not supported.
  • No AVX-512 unit yet; on AVX-512 hosts prefill-size gated activations in bf16 favour eager.
  • On CPUs without AVX2 the portable path wins only for the norms and rotary at decode; its gated activations evaluate exp and tanh per element and are slower than eager, so keep SiluAndMul and GeluTanhAndMul on eager there.
  • Published Hub variants are x86_64-linux (CPU and CUDA). Metal and aarch64 are source-only in v2.

Offline and pinned deployment

# pyproject.toml
[tool.kernels.dependencies]
"Thox-ai/thox-edge-ops" = 2
kernels lock .        # writes kernels.lock with the resolved revision
kernels download .    # pre-fetches it into the HF cache

Architecture

flowchart LR
  A["Python API: rms_norm, fused_add_rms_norm, silu_and_mul, gelu_tanh_and_mul, rotary_embedding, fused_qk_norm_rope, layers"] --> B["torch.ops namespace, unique per build"]
  B --> C["CUDA kernels (sm_87 and up)"]
  B --> D["Metal kernels (Apple silicon)"]
  B --> E["CPU dispatcher"]
  E --> F["AVX2 + FMA + F16C unit"]
  E --> G["baseline at::vec unit (NEON on aarch64)"]
  A -. "unsupported dtype or device" .-> H["eager fp32 reference"]

Breaking changes ship as a new major version branch. v1 (three ops, no weight offset) remains on branch v1 for anyone who pinned it.

Research

Tommy Xaypanya. "thox-edge-ops v2: fused decode kernels for small models on THOX edge hardware." THOX.ai Research, v2.0.0 (revised public edition), November 5, 2026. Preprint, not peer reviewed.

The paper's Section 4 places these kernels in the THOX stack (edge hardware, ThoxOS and ThoxInux, MeshStack) and states the hardware qualification matrix this card follows: the x86_64 workstation and hub class is measured for v2; Jetson Orin, Apple silicon and the Arm device class are targets only for v2.

License

Apache-2.0. Copyright 2026 THOX.ai LLC.

Downloads last month
2
kernels
rmsnorm
rope
qk-norm
swiglu
gelu
fused-kernels
edge-ai
jetson
cuda
metal
cpu
inference
apache-2.0
Supported hardwares new
CUDA
7.58.08.68.78.99.010.010.311.012.012.1+PTX
GPU
B300
288GB
DGX Spark
GB10
128GB
Jetson
Jetson T5000
128GB
GPU
RTX PRO 6000 WS
96GB
GPU
RTX PRO 6000 Max-Q
96GB
GPU
RTX PRO 5000
48GB
GPU
RTX PRO 4500 WS
32GB
RTX
RTX 5090
32GB
RTX
RTX 5090 D
32GB
GPU
RTX PRO 4000
24GB
GPU
RTX PRO 4000 SFF
24GB
RTX
RTX 5090 Mobile
24GB
GPU
RTX PRO 2000
16GB
RTX
RTX 5080
16GB
RTX
RTX 5080 Mobile
16GB
RTX
RTX 5070 Ti
16GB
RTX
RTX 5060 Ti
16GB
RTX
RTX 5070
12GB
RTX
RTX 5070 Ti Mobile
12GB
RTX
RTX 5070 Mobile
8GB
RTX
RTX 5060
8GB
RTX
RTX 5060 Mobile
8GB
RTX
RTX 5050
8GB
RTX
RTX 5050 Mobile
8GB
NVIDIA SXM
B200
192GB
NVIDIA SXM
H200
141GB
GPU
H20
96GB
GPU
RTX 5880 Ada
48GB
GPU
RTX 2000 Ada
16GB
RTX
RTX 4080 SUPER
16GB
RTX
RTX 4070 Ti Super
16GB
RTX
RTX 4070 Super
12GB
GPU
H800
80GB
GPU
L40s
48GB
GPU
L20
48GB
RTX
RTX 4090D
24GB
RTX
RTX 5000 Ada
32GB
GPU
L4
24GB
GPU
RTX 4500 Ada
24GB
RTX
RTX 4000 Ada
20GB
RTX
RTX 4000 SFF Ada
20GB
RTX
RTX 4090 Mobile
16GB
RTX
RTX 4060 Ti
8GB
RTX
RTX 4090 Laptop
16GB
Jetson
Jetson Orin NX 16GB
16GB
GPU
RTX 3500 Ada Mobile
12GB
RTX
RTX 4080 Mobile
12GB
RTX
RTX 4070
12GB
RTX
RTX 4070 Ti
12GB
RTX
RTX 4080 Laptop
12GB
RTX
RTX 4070 Mobile
8GB
RTX
RTX 4060
8GB
RTX
RTX 4070 Laptop
8GB
RTX
RTX 4060 Laptop
8GB
Jetson
Jetson Orin NX 8GB
8GB
Jetson
Jetson Orin Nano 8GB
8GB
RTX
RTX 4050 Laptop
6GB
Jetson
Jetson Orin Nano 4GB
4GB
NVIDIA SXM
H100
80GB
GPU
A800
40GB
Jetson
Jetson AGX Orin 64GB
64GB
GPU
L40
48GB
GPU
RTX 6000 Ada
48GB
Jetson
Jetson AGX Orin 32GB
32GB
RTX
RTX 4090
24GB
RTX
RTX 3090 Ti
24GB
RTX
RTX 4080
16GB
RTX
RTX 3070 Ti Mobile
8GB
RTX
RTX 3050
8GB
RTX
RTX 3050 Mobile
4GB
GPU
RTX A2000 Embedded
4GB
GPU
GTX 1630
4GB
GPU
RTX A5000
8GB
GPU
A30
24GB
GPU
A10
24GB
GPU
RTX A5000 Max-Q
16GB
GPU
RTX A5000 Mobile
16GB
GPU
RTX A4000
16GB
GPU
A2
16GB
RTX
RTX 3080 Mobile
16GB
GPU
RTX A3000 Mobile
6GB
GPU
RTX A2000
6GB
RTX
RTX 3080 Ti
12GB
RTX
RTX 3060
12GB
GPU
RTX 2060 12GB
12GB
GPU
RTX A4000 Max-Q
8GB
GPU
RTX A4000 Mobile
8GB
GPU
RTX A2000 Max-Q
4GB
GPU
RTX A2000 Mobile
4GB
RTX
RTX 3070 Ti
8GB
RTX
RTX 3060 Mobile
6GB
GPU
RTX 2050 Mobile
4GB
GPU
A100
80GB
GPU
RTX A6000
48GB
GPU
A40
48GB
RTX
RTX 3090
24GB
GPU
T10
16GB
RTX
RTX 3080
12GB
RTX
RTX 3070
8GB
RTX
RTX 3060 Ti
8GB
GPU
RTX 2070 SUPER Mobile
8GB
GPU
GTX 1650 Ti Mobile
4GB
GPU
T40
24GB
GPU
RTX 2080 SUPER
8GB
GPU
RTX 2070 SUPER
8GB
GPU
RTX 2060 SUPER
8GB
GPU
RTX 2060
6GB
GPU
RTX 2060 Mobile
6GB
GPU
GTX 1660 Ti
6GB
GPU
GTX 1660 Ti Mobile
6GB
GPU
GTX 1660 SUPER
6GB
GPU
GTX 1660
6GB
GPU
GTX 1650 SUPER
4GB
GPU
GTX 1650
4GB
GPU
GTX 1650 Mobile
4GB
GPU
Quadro RTX 8000
48GB
GPU
RTX Titan
24GB
GPU
Quadro RTX 6000
24GB
GPU
RTX 2080 Ti
11GB
NVIDIA T4
T4
16GB
GPU
Quadro RTX 5000
16GB
GPU
RTX 2080
8GB
GPU
RTX 2070
8GB
OS
linux
Arch
x86_64
Kernel Builder
32b3dc8