
Xpu Kernels
- 9 installs
- 720 repo stars
- Updated August 4, 2026
- huggingface/kernels
xpu-kernels skill documents Provides guidance for writing, optimizing, and benchmarking Triton kernels for Intel XPU GPUs (Battlemage/Arc Pro B50) using the Xe-Forge optimization framework.
About
xpu-kernels skill documents Provides guidance for writing, optimizing, and benchmarking Triton kernels for Intel XPU GPUs (Battlemage/Arc Pro B50) using the Xe-Forge optimization framework. Includes an LLM-driven trial-loop workflow (analyze, validate, benchmark, profile, finalize), XPU-specific patterns (tensor descriptors, G. name: xpu-kernels description: "Provides guidance for writing, optimizing, and benchmarking Triton kernels for Intel XPU GPUs (Battlemage/Arc Pro B50) using the Xe-Forge optimization framework. Includes an LLM-driven trial-loop workflow (analyze, validate, benchmark, profile, finalize), XPU-specific patterns (tensor descriptors, GRF mode, tile swizzling), KernelBench fused kernels, and Flash Atten
- Provides guidance for writing, optimizing, and benchmarking Triton kernels for Intel XPU GPUs (Battlemage/Arc Pro B50) u
- Platform-specific setup patterns for xpu-kernels.
- Evidence-backed steps from upstream SKILL.md.
- When-to-use criteria for xpu-kernels versus alternatives.
Xpu Kernels by the numbers
- 9 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,569 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
xpu-kernels capabilities & compatibility
- Capabilities
- xpu kernels quick start · xpu kernels when to use guidance · xpu kernels integration patterns
- Use cases
- research
What xpu-kernels says it does
disable-model-invocation: false
allowed-tools: "Read, Grep, Glob, Bash"
npx skills add https://github.com/huggingface/kernels --skill xpu-kernelsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9 |
|---|---|
| repo stars | ★ 720 |
| Last updated | August 4, 2026 |
| Repository | huggingface/kernels ↗ |
How do I use xpu-kernels correctly?
Provides guidance for writing, optimizing, and benchmarking Triton kernels for Intel XPU GPUs (Battlemage/Arc Pro B50) using the Xe-Forge optimization framework. Includes an LLM-driven trial-loop work
Who is it for?
Teams implementing xpu-kernels workflows from the catalog.
Skip if: Skip when requirements clearly match a different specialized stack.
When should I use this skill?
User asks about xpu-kernels, provides guidance for writing, optimizing, and benchmarking triton kernels for intel xpu g.
What you get
Working xpu-kernels setup with validated configuration and next steps.
Files
XPU Triton Kernels for Intel GPUs
This skill provides patterns and guidance for developing optimized Triton kernels targeting Intel XPU GPUs (Battlemage/Arc Pro B50). It integrates the Xe-Forge optimization framework — an LLM-driven loop that transforms PyTorch code into fast Triton kernels.
Quick Start
Optimize a Kernel (Xe-Forge Workflow)
The full optimization workflow analyzes a PyTorch baseline, generates Triton kernel variants in a branching trial tree, benchmarks each on XPU hardware, and finalizes the best result.
# 1. Analyze the baseline
python scripts/analyze_kernel.py test_kernels/70_Gemm_Sigmoid_Scaling_ResidualAdd_pytorch.py
# 2. Initialize trial tracking
python scripts/trial_manager.py init 70_Gemm_Sigmoid test_kernels/70_Gemm_Sigmoid_Scaling_ResidualAdd_pytorch.py
# 3. Validate a generated kernel (no GPU needed)
python scripts/validate_triton.py my_kernel.py
# 4. Benchmark correctness + performance
python scripts/benchmark.py test_kernels/70_Gemm_Sigmoid_Scaling_ResidualAdd_pytorch.py my_kernel.py
# 5. Profile with VTune (optional)
python scripts/xpu_profiler.py my_kernel.py
# 6. Finalize best trial
python scripts/trial_manager.py finalize 70_Gemm_Sigmoid optimized_triton.pySupported Hardware
| GPU | Architecture | XVEs | Mem BW | Key Feature | Verified |
|---|---|---|---|---|---|
| Battlemage G21 / Arc Pro B50 | Xe2 | 128 | ~500 GB/s | Tensor descriptors, GRF 256 | Yes |
See the Intel XPU Backend for Triton for supported hardware.
When This Skill Applies
Use this skill when:
- Optimizing PyTorch operations into Triton kernels for Intel XPU
- Writing GEMM, fused kernels, reductions, or Flash Attention for Intel GPUs
- Running the Xe-Forge optimization loop (analyze → validate → benchmark → profile → finalize)
- Benchmarking kernel performance against PyTorch baseline on XPU
Xe-Forge Optimization Workflow
Transform PyTorch code into optimized Triton kernels for Intel XPU. Kernels must be numerically equivalent and faster than baseline.
Configuration — Read config.yaml first
At the start of every session, read scripts/config.yaml. It controls:
- `max_trials` — hard cap on optimization trials; always run all of them (use this instead of hardcoded "10")
- `vtune_enabled` — if
false, skip ALL VTune profiling steps (Step 3.6 and profiler-related decisions) - `vtune_bin` — path to the VTune binary (also settable via
VTUNE_BINenv var)
Rules — Never Violate
1. ONLY create Triton kernel files (test_kernels/*_triton.py or trial files t<trial_id>.py). 2. NEVER create benchmark scripts, test scripts, helper utilities, or any other Python files. 3. NEVER write custom scripts to measure performance or test correctness — ONLY use scripts/benchmark.py. 4. If a tool fails, STOP and report the error. Do NOT work around it with custom scripts. 5. Generated kernels must be self-contained — all helper functions inline. 6. You MUST run all `max_trials` trials from config.yaml. Do NOT stop early due to plateau — LLM sampling can discover new ideas at any point. The only valid early stop is speedup > 5x.
Mandatory Tools
CRITICAL — Single-XPU serialization: There is only ONE XPU on this machine. You MUST NOT run multiple GPU workloads in parallel. benchmark.py and xpu_profiler.py must execute strictly one at a time — concurrent GPU jobs produce wrong results. CPU-only tools (analyze_kernel.py, validate_triton.py, trial_manager.py) are safe to parallelize with each other and with anything else.
| Tool | Command | Purpose |
|---|---|---|
| Analyze | python scripts/analyze_kernel.py <file> | Static analysis: operations, shapes, fusion opportunities |
| Validate | python scripts/validate_triton.py <file> | Syntax + constraint checks before GPU time |
| Benchmark | python scripts/benchmark.py <baseline> <triton> [--triton-baseline] [--baseline-us <cached>] | Correctness + performance via ai-bench |
| Profile | python scripts/xpu_profiler.py <file> | VTune GPU hardware counters + recommendations |
| Init trials | python scripts/trial_manager.py init <kernel_name> <baseline_file> [--triton-baseline] | Initialize trial tracking |
| Save trial | python scripts/trial_manager.py save <kernel_name> <file> [--parent <parent_id>] [--strategy "..."] | Save trial to tree |
| Record result | `python scripts/trial_manager.py result <kernel_name> <trial_id> --validation pass --correctness <pass\ | fail> --speedup <float> --baseline_us <float> --triton_us <float>` |
| Check status | python scripts/trial_manager.py status <kernel_name> | View trial tree |
| Best trial | python scripts/trial_manager.py best <kernel_name> | Get best trial |
| Baseline time | python scripts/trial_manager.py baseline-us <kernel_name> | Cached baseline time for --baseline-us |
| Finalize | python scripts/trial_manager.py finalize <kernel_name> <name>_triton.py | Copy best trial to output |
Workflow Steps
Step 1: Analyze
- Read the baseline source file. Identify shapes, dtypes, operations, fusion opportunities.
- If baseline is PyTorch: run
python scripts/analyze_kernel.py <pytorch_file>. - If baseline is Triton (
--triton-baseline): skipanalyze_kernel.py(it only supports PyTorch). Read the Triton file directly. - Read relevant knowledge base files: start with
references/correctness.yamlandreferences/xpu_optimizations.yaml. - Read
references/implementation_reference.mdfor templates and the Model class pattern.
Step 2: Initialize
python scripts/trial_manager.py init <kernel_name> <baseline_file> [--triton-baseline]Step 3: Trial Loop (always run all max_trials from config.yaml)
For each trial: 1. Write kernel — start from templates or modify previous trial. See references/implementation_reference.md. 2. Validate — python scripts/validate_triton.py <triton_file> (fix until passing; doesn't count as a trial). 3. Save — python scripts/trial_manager.py save <kernel_name> <triton_file> --parent <parent_id> --strategy "description". Omit --parent for the first trial (t0). 4. Benchmark (MANDATORY every trial):
- Trial t0:
python scripts/benchmark.py <baseline_file> <triton_file> [--triton-baseline](measures both baseline and triton). - Trials t1+: Get cached baseline via
python scripts/trial_manager.py baseline-us <kernel_name>, then runpython scripts/benchmark.py <baseline_file> <triton_file> [--triton-baseline] --baseline-us <cached_value>(skips baseline perf, saves time). - After `finalize`: Re-run
benchmark.pywithout--baseline-usfor final accurate comparison.
5. Record — python scripts/trial_manager.py result <kernel_name> <trial_id> --validation pass --correctness <pass|fail> --speedup <float> --baseline_us <float> --triton_us <float> (runtimes from benchmark output). 6. Profile (MANDATORY after t1, if `vtune_enabled` is true in config.yaml) — Run python scripts/xpu_profiler.py <triton_file> after your first benchmarked trial. Use its output to guide subsequent trial strategies. Run again if speedup plateaus after 2+ additional trials. Skip this step entirely if `vtune_enabled` is false. 7. Decide next action (use profiler output from step 6 to inform decisions):
- Speedup > 5x → stop (excellent), finalize
- Speedup improved → continue on this branch, try next optimization level
- Speedup regressed → branch back to best trial, try different strategy
- Correctness failed → fix on same branch
- Profiler says low occupancy (if vtune_enabled) → increase tile sizes, check
references/xpu_optimizations.yaml - Profiler says overhead kernels dominate (if vtune_enabled) → pre-pack to bf16, see
references/optimization_levels.yaml - Plateau → do NOT stop. Try a fundamentally different approach (different algorithm, tiling, fusion strategy). LLM sampling can discover new ideas.
- See
references/optimization_strategies.mdfor the full "try harder" decision tree
Step 4: Finalize
python scripts/trial_manager.py finalize <kernel_name> <name>_triton.pyReference Docs — Read During Step 1
| Doc | Contents |
|---|---|
references/implementation_reference.md | Code templates, Model class pattern, GEMM example |
references/optimization_strategies.md | Strategy reference, optimization levels, checklist |
references/workflow_details.md | Detailed workflow, decision tree, benchmarking/validation details |
references/correctness.yaml | Critical constraints to avoid bugs |
references/xpu_optimizations.yaml | XPU-specific patterns (tensor descriptors, GRF, swizzling) |
references/fusion_patterns.yaml | When to fuse vs split operations |
references/optimization_levels.yaml | Progressive optimization with "try harder" decision tree |
Existing Baselines Are Naive
The test_kernels/*.py Triton files (non-pytorch) are unoptimized baselines. They use manual pointer arithmetic, lack autotune, and miss XPU optimizations. Do NOT copy their patterns. Use references/implementation_reference.md instead.
Core XPU Kernel Patterns
Tensor Descriptors (Preferred on XPU)
Tensor descriptors produce better address generation and memory access codegen than block pointers on Intel XPU.
desc = tl.make_tensor_descriptor(
base=ptr, shape=[M, N],
strides=[stride_m, stride_n],
block_shape=[BLOCK_M, BLOCK_N],
)
block = tl.load(desc, [pid_m, pid_n], boundary_check=(0, 1))GRF Mode '256'
Use the large register file for compute-heavy kernels:
@triton.autotune(
configs=[triton.Config({'BLOCK_M': 256, 'BLOCK_N': 256}, num_warps=32)],
key=['M', 'N', 'K'],
)
@triton.jit(launch_metadata=lambda *args, **kwargs: {'grf_mode': '256'})
def kernel(...):
...Tile Swizzling
Use 1D grid with GROUP_SIZE_M for L2 locality:
grid = lambda META: (triton.cdiv(M, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']),)
# Inside kernel:
pid = tl.program_id(0)
num_pid_n = tl.cdiv(N, BLOCK_N)
group_id = pid // (GROUP_SIZE_M * num_pid_n)bf16 Inputs with fp32 Accumulation
a = tl.load(a_desc, [pid_m, k], boundary_check=(0, 1))
b = tl.load(b_desc, [k, pid_n], boundary_check=(0, 1))
acc += tl.dot(a.to(tl.bfloat16), b.to(tl.bfloat16), acc=acc) # fp32 accumulatorCritical XPU Constraints
- NO default values for
@triton.autotunemeta-parameters in kernel signature - 1D grid when using tile swizzling (GROUP_SIZE_M)
- `boundary_check` uses dimension indices
(0, 1), not booleans - Cast batch indices to
int64before stride multiplication - Prefer tensor descriptors over block pointers for all new XPU kernels
- Do NOT mix block pointer and tensor descriptor APIs on same operation
- Pre-zero output buffers when using atomic accumulation
- Model class must be compatible with ai-bench (
nn.Modulewithnn.Linear) - Match
get_inputs(),get_init_inputs(), and module-level constants from*_pytorch.py
Full constraint list: correctness.yaml
Performance Results
Measured on Intel Battlemage G21 / Arc Pro B50 (128 XVEs). All runtimes are median of benchmark trials.
KernelBench Level 2 — Fused Kernels (bf16)
Speedup is vs. PyTorch eager baseline. Includes GEMM+Sigmoid+Scaling, GEMM+GELU+Softmax, Conv+BatchNorm+ReLU, and other fused patterns.
Flash Attention Forward (fp16)
Baseline is the flash attention kernel from the Intel XPU Triton backend; speedup is vs. that kernel across multiple sequence lengths.
Full results: see the Xe-Forge repository.
Common Issues
| Issue | Symptom | Fix |
|---|---|---|
| Autotune BLOCK_D | Wrong results (max_abs 4-8+) | Never autotune BLOCK_D. Use triton.next_power_of_2(D) |
| Python min/max | Runtime error | tl.minimum()/tl.maximum() |
Project Structure
xpu-kernels/
├── SKILL.md # This file (skill definition + workflow)
├── manifest.txt # Files included in this skill
│
├── scripts/ # Standalone CLI tools
│ ├── analyze_kernel.py # PyTorch → operations, shapes, fusion opportunities
│ ├── validate_triton.py # Syntax + constraint checks
│ ├── benchmark.py # Correctness + performance via ai-bench
│ ├── trial_manager.py # Tree-structured trial management
│ ├── xpu_profiler.py # VTune GPU hardware counters
│ ├── config.py # Shared configuration loader
│ ├── config.yaml # Session config (max_trials, vtune)
│ └── requirements.txt # Python dependencies
│
└── references/ # Knowledge base + integration guides
├── correctness.yaml # Hard constraints for XPU Triton
├── xpu_optimizations.yaml # Tensor descriptors, GRF, swizzling
├── implementation_reference.md # Code templates, Model class pattern
├── implementation_reference.md # Code templates, Model class pattern
├── optimization_strategies.md # Strategy reference + "try harder" tree
├── optimization_levels.yaml # Progressive L1-L5 optimization levels
├── workflow_details.md # Detailed workflow and decision tree
├── fusion_patterns.yaml # When to fuse vs split
├── memory_patterns.yaml # Access patterns and coalescing
├── dtype_optimizations.yaml # Mixed precision choices
├── persistent_kernel_patterns.yaml # Stream K and persistent kernels
├── kernel-templates.md # Triton kernel templates for XPU
└── kernelbench-classification.md # KernelBench operator taxonomySee Also
Xe-Forge Tools
- analyze_kernel.py — Static analysis of PyTorch reference
- validate_triton.py — Pre-benchmark constraint checks
- benchmark.py — Correctness + performance measurement
- xpu_profiler.py — VTune GPU hardware counters
- trial_manager.py — Branching trial tree management
XPU Optimization References
- correctness.yaml — Critical constraints
- xpu_optimizations.yaml — Tensor descriptors, GRF, swizzling
- optimization_strategies.md — Strategy reference
- optimization_levels.yaml — Progressive L1-L5 levels
- implementation_reference.md — Code templates
Other References
- kernelbench-classification.md — KernelBench operator taxonomy
External Resources
- Xe-Forge Repository
- AI-Bench — Benchmark harness for correctness + performance
- Intel XPU Backend for Triton
- Triton Language Guide
# Files for xpu-kernels skill
SKILL.md
README.md
references/correctness.yaml
references/dtype_optimizations.yaml
references/fusion_patterns.yaml
references/huggingface-kernels-integration.md
references/implementation_reference.md
references/kernelbench-classification.md
references/memory_patterns.yaml
references/optimization_levels.yaml
references/optimization_strategies.md
references/persistent_kernel_patterns.yaml
references/workflow_details.md
references/xpu_optimizations.yaml
scripts/analyze_kernel.py
scripts/benchmark.py
scripts/benchmark_kernels.py
scripts/config.py
scripts/config.yaml
scripts/huggingface_kernels_example.py
scripts/requirements.txt
scripts/transformers_injection_example.py
scripts/trial_manager.py
scripts/validate_triton.py
scripts/xpu_profiler.py
XPU Kernels Skill
This skill was adapted from Xe-Forge — an LLM-driven optimization framework that transforms PyTorch code into fast Triton kernels for Intel XPU GPUs.
The skill includes Xe-Forge's CLI tools (scripts/), knowledge base (references/), and the optimization workflow, all integrated into the hf-kernels skill format.
Full Experience
For the complete Xe-Forge setup — including the ai-bench harness, test kernels, GEMM/reduction templates, annotated examples, and VTune profiling — clone the full project:
# Clone the repository
git clone https://github.com/IntelLabs/Xe-Forge
cd Xe-Forge
# Install for Intel XPU
uv sync --extra intelPrerequisites
- Python 3.10+
- PyTorch with XPU support
- Intel XPU Backend for Triton
- Intel XPU hardware (tested on Battlemage G21 / Arc Pro B50)
- Intel VTune Profiler 2025+ (optional — set `vtune_enabled: false` in `scripts/config.yaml` to skip)
Install Dependencies
pip install -r scripts/requirements.txtconstraints:
- id: outputs_must_match
name: "Outputs must match original"
severity: info
description: |
The verification tool will check that outputs match the original.
If it fails, try a different optimization approach.
- id: streamk_output_must_be_prezeroed
name: "Pre-zero output buffer when using atomic accumulation (Stream K)"
severity: critical
description: |
When partial tiles use tl.atomic_add to accumulate results, the output
tensor MUST be initialized to zero (torch.zeros, NOT torch.empty).
Otherwise partial sums will include garbage values.
WRONG:
```python
c = torch.empty((M, N), device=a.device, dtype=torch.float32)
first_wave[grid](a, b, c, ...) # atomic_add onto garbage
```
CORRECT:
```python
c = torch.zeros((M, N), device=a.device, dtype=torch.float32)
first_wave[grid](a, b, c, ...) # atomic_add safely onto zeros
```
- id: streamk_atomic_add_needs_mask
name: "Atomic adds on partial tiles must be masked for boundary safety"
severity: critical
description: |
When falling back to tl.atomic_add for partial tiles, you MUST apply
boundary masks (rm < M, rn < N) to avoid writing out-of-bounds.
```python
rm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
rn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
mask = (rm < M)[:, None] & (rn < N)[None, :]
tl.atomic_add(c_ptr_, acc, mask=mask, sem='relaxed')
```
- id: int64_cast_for_large_batch_offsets
name: "Cast batch/stride products to int64 to prevent pointer overflow"
severity: critical
description: |
When computing pointer offsets for batched operations, the product of
a batch index and a stride can exceed int32 range for large tensors.
Triton program_id returns int32 by default. You MUST cast to int64
before multiplying by strides.
WRONG (silent int32 overflow → wrong memory addresses):
```python
bid = tl.program_id(axis=1)
offset_a = bid * stride_az # int32 * int32 → overflow for large tensors
a_ptrs = a_ptr + offset_a + ...
```
CORRECT:
```python
bid = tl.program_id(axis=1)
offset_a = bid.to(tl.int64) * stride_az # safe for large tensors
a_ptrs = a_ptr + offset_a + ...
```
This applies whenever a program_id or loop index is multiplied by a
stride that could produce values > 2^31 (≈2 billion elements). Common
in batched GEMM, multi-head attention, and any kernel with a batch
dimension over large tensors.
- id: autotune_no_defaults
name: "Do not put default values on @triton.autotune meta-parameters"
severity: critical
description: |
When using @triton.autotune, the meta-parameters (BLOCK_M, BLOCK_N, etc.)
must NOT have default values in the kernel signature. Default values cause
a "Conflicting meta-parameters" error at runtime.
WRONG:
```python
@triton.autotune(configs=[...], key=['M', 'N', 'K'])
@triton.jit
def kernel(..., BLOCK_M: tl.constexpr = 128, ...):
...
```
CORRECT:
```python
@triton.autotune(configs=[...], key=['M', 'N', 'K'])
@triton.jit
def kernel(..., BLOCK_M: tl.constexpr, ...):
...
```
- id: model_class_pattern
name: "Model class must be compatible with ai-bench loading"
severity: critical
description: |
ai-bench creates Model via direct `__init__()` and uses standard
`load_state_dict()` for weight synchronization between reference
and optimized models.
The Model class should use standard nn.Module patterns:
```python
class Model(nn.Module):
def __init__(self, input_size, hidden_size, ...):
super().__init__()
self.gemm = nn.Linear(input_size, hidden_size)
self._packed = False
def _pack_weights(self):
device = torch.device("xpu")
w = self.gemm.weight.data.detach()
b = self.gemm.bias.data.detach()
self.weight_t = w.to(device, torch.float16).t().contiguous()
self.bias_xpu = b.to(device, torch.float16).contiguous()
self._packed = True
def forward(self, x):
if not self._packed:
self._pack_weights()
# ... launch triton kernel ...
```
- id: descriptor_no_boundary_check_arg
name: "Tensor descriptor .load() does NOT accept boundary_check"
severity: critical
description: |
Tensor descriptors are the preferred memory access API on XPU.
Unlike block pointers which use tl.load(ptr, boundary_check=(0, 1)),
tensor descriptors handle boundaries internally. The .load() method
takes only a coordinate list.
WRONG:
```python
desc = tl.make_tensor_descriptor(base=ptr, shape=(M, K), ...)
data = desc.load([row, col], boundary_check=(0, 1))
```
CORRECT:
```python
desc = tl.make_tensor_descriptor(base=ptr, shape=(M, K), ...)
data = desc.load([row, col])
```
# Dtype Optimization Patterns for Intel XPU
patterns:
- id: dtype_float64_to_float32
name: "Float64 to Float32 Accumulator"
stage: dtype_fix
description: "Replace float64 accumulators with float32"
rationale: |
float64 throughput is 16-32x slower than float32 on GPUs/XPUs.
This is the single biggest performance killer in many kernels.
Using float64 alone can cap performance at around 2 TFLOPS on Intel XPU.
pattern_before: |
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float64)
a = a_fp32.to(tl.float64)
b = b_fp32.to(tl.float64)
pattern_after: |
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
# No need to convert inputs - keep as float32
expected_speedup: "5-10x"
applies_to:
- gemm
- matmul
- reduction
examples:
- before: |
@triton.jit
def kernel(a_ptr, b_ptr, c_ptr, ...):
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float64)
for k in range(K):
a = tl.load(a_ptr + ...).to(tl.float64)
b = tl.load(b_ptr + ...).to(tl.float64)
acc = tl.dot(a, b, acc)
after: |
@triton.jit
def kernel(a_ptr, b_ptr, c_ptr, ...):
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
for k in range(K):
a = tl.load(a_ptr + ...)
b = tl.load(b_ptr + ...)
acc = tl.dot(a, b, acc)
- id: dtype_input_conversion
name: "Remove Unnecessary Type Conversions"
stage: dtype_fix
description: "Avoid converting inputs to higher precision unnecessarily"
rationale: |
Converting float16 inputs to float64 for computation wastes bandwidth
and compute. Use float32 accumulators with float16 inputs for best
performance on modern accelerators.
pattern_before: |
x = tl.load(x_ptr + offsets).to(tl.float64)
result = x * x # float64 computation
pattern_after: |
x = tl.load(x_ptr + offsets) # Keep as float16
x_fp32 = x.to(tl.float32) # Upcast to float32 only if needed
result = x_fp32 * x_fp32
expected_speedup: "2-4x"
applies_to:
- elementwise
- reduction
- id: dtype_prepack_bf16
name: "Pre-pack weights and inputs to bf16 before kernel launch"
stage: dtype_fix
description: |
Convert weights to bf16 at _pack_weights() time and inputs to bf16
before kernel launch, instead of loading fp32 and converting in-kernel.
rationale: |
Loading fp32 data and converting to bf16 inside the kernel wastes
memory bandwidth:
- fp32 load: 4 bytes per element from global memory
- In-kernel .to(tl.bfloat16): discards half the loaded data
- Net: 2x wasted bandwidth in the K-loop (the hottest path)
Pre-packing to bf16 means the kernel loads 2 bytes per element directly.
For a GEMM with K-loop iterations, this halves the memory traffic for
both A and B tiles — often the difference between 2x and 4x+ speedup.
pattern_before: |
# In _pack_weights():
self.weight_t = w.to(device).t().contiguous() # stored as fp32
# In forward():
x = x.to(device).contiguous() # fp32 input
# In kernel K-loop:
a = tl.load(a_block_ptr, boundary_check=(0, 1)) # loads 4B per element
a = a.to(tl.bfloat16) # converts to 2B — 2x waste
b = tl.load(b_block_ptr, boundary_check=(0, 1))
b = b.to(tl.bfloat16)
acc += tl.dot(a, b)
pattern_after: |
# In _pack_weights():
self.weight_t = w.to(device).t().contiguous().to(torch.bfloat16) # bf16
# In forward():
x = x.to(device, torch.bfloat16).contiguous() # bf16 input
# In kernel K-loop (no conversion needed):
a = tl.load(a_block_ptr, boundary_check=(0, 1)) # loads 2B directly
b = tl.load(b_block_ptr, boundary_check=(0, 1))
acc = tl.dot(a, b, acc=acc) # fused accumulate
expected_speedup: "1.5-2x (halves K-loop memory traffic)"
applies_to:
- gemm
- matmul
- attention
- inference
notes: |
- Keep bias and epilogue vectors in fp32 (small, precision-sensitive)
- Combine with grf_mode='256' and tl.dot(a, b, acc=acc) for best results
- Only for inference; training needs fp32 gradients
- Works with both block pointers and tensor descriptors
# Kernel Fusion Patterns for Intel XPU
# Fusion can reduce memory traffic by eliminating intermediate writes/reads,
# but it can also hurt due to GRF/register pressure, reduced occupancy, and
# losing access to vendor-tuned primitives (e.g., GEMM).
#
# Guidance:
# - Fuse bandwidth-bound elementwise chains aggressively (usually a win).
# - Be cautious fusing into GEMM unless you already need a custom GEMM.
# - Do NOT fuse if the intermediate is not materialized, is dead/redundant,
# or if fusion would replace a faster vendor primitive for a tiny epilogue.
constraints:
- id: fuse_only_if_intermediate_is_materialized
name: "Fuse only if it removes a materialized intermediate that is otherwise written/read"
severity: critical
description: |
Fusion is beneficial only if the unfused baseline materializes an intermediate tensor
(stores it to memory) and then reloads it in a separate kernel.
If the intermediate is already:
- handled by a fused library epilogue, OR
- kept in registers within a single kernel, OR
- dead / provably redundant for the workload,
then fusion provides little/no benefit and may harm performance.
- id: do_not_replace_vendor_gemm_for_tiny_epilogue
name: "Do not replace vendor GEMM solely to fuse a tiny epilogue"
severity: critical
description: |
If a vendor GEMM path exists and is faster, do not implement a custom Triton GEMM
only to fuse a small epilogue (e.g., ReLU / min-sub / clamp).
Prefer:
- vendor GEMM + separate epilogue kernel, OR
- vendor GEMM with native epilogue (if supported),
unless the epilogue is substantial OR you must use a custom GEMM due to layout/math.
- id: fusion_register_pressure_guard
name: "Avoid fusion that causes GRF/register pressure collapse"
severity: critical
description: |
Fusion increases live values and temporary tensors. If register pressure causes:
- occupancy collapse, OR
- GRF spills, OR
- much lower parallelism,
fusion can be slower than unfused.
Red flags:
- long activation chains with exp/log/tanh + multiple clamps
- large tiles (e.g., 256x256) + high num_warps + many fused ops
- reductions + heavy elementwise in same kernel on large blocks
If unsure, fuse only a light epilogue (bias + simple activation) and keep the rest separate.
- id: fuse_when_bandwidth_bound
name: "Prefer fusion when the baseline is bandwidth-bound"
severity: critical
description: |
Fusion helps most when the unfused path is bandwidth-bound:
- elementwise chains (add/mul/clip/relu/etc.)
- normalization epilogues (affine, bias, scale, clamp)
- pointwise ops following reductions where intermediates are large
Fusion helps least when:
- compute-bound kernel dominates (large GEMM),
- the intermediate is small,
- or the baseline already uses a fused primitive.
- id: skip_fusion_when_noop_or_redundant
name: "Skip fusion when the operation is provably no-op or redundant"
severity: critical
description: |
Do not fuse operations that are provably redundant for the workload:
- multiply by 1, add 0
- clamp/min/max where thresholds never trigger for the data range
- dead outputs not consumed
Extra fused instructions without reducing memory traffic can slow kernels down.
patterns:
- id: elementwise_chain_fusion
name: "Elementwise Chain Fusion (safe default)"
stage: fusion
description: "Fuse multiple elementwise ops into one kernel to reduce memory traffic"
rationale: |
Elementwise chains are typically bandwidth-bound. Each unfused step reads+writes the full
tensor again. Fusion usually wins unless the chain is very large or involves heavy
transcendentals that blow up register pressure.
pattern_before: |
y = a + b
y = y * scale
y = clamp(y, lo, hi)
y = relu(y)
pattern_after: |
@triton.jit
def fused_elementwise(a_ptr, b_ptr, out_ptr, ..., lo, hi, scale):
x = tl.load(a_ptr + offsets, mask=mask, other=0.0)
y = tl.load(b_ptr + offsets, mask=mask, other=0.0)
x = x + y
x = x * scale
x = tl.maximum(x, lo)
x = tl.minimum(x, hi)
x = tl.maximum(x, 0.0)
tl.store(out_ptr + offsets, x, mask=mask)
expected_speedup: "1.2-3x (bandwidth dependent)"
applies_to:
- all_elementwise
- normalization_epilogues
- id: gemm_activation_fusion
name: "GEMM + Activation Fusion (conditional)"
stage: fusion
description: "Fuse activation into GEMM ONLY when you already need a custom GEMM"
rationale: |
Fusion removes an intermediate write+read of the GEMM output. This can be beneficial
when you already use a custom GEMM kernel (special layout, custom math, no vendor path).
However, replacing a vendor GEMM with a custom fused GEMM for a tiny activation can be slower.
applies_when:
- uses_custom_gemm: true
- intermediate_materialized: true
- replaces_vendor_gemm: false
rejects_when:
- replaces_vendor_gemm: true
- epilogue_is_tiny: true
- num_pid_m_le_1: true
pattern_before: |
# Kernel 1: GEMM (custom)
@triton.jit
def gemm_kernel(a_ptr, b_ptr, c_ptr, ...):
acc = tl.dot(a, b)
tl.store(c_ptr + offsets, acc, mask=mask)
# Kernel 2: Activation (separate launch)
@triton.jit
def activation_kernel(c_ptr, out_ptr, ...):
c = tl.load(c_ptr + offsets, mask=mask, other=0.0)
out = tl.maximum(c, 0.0) # ReLU
tl.store(out_ptr + offsets, out, mask=mask)
pattern_after: |
# Single fused kernel (custom GEMM + activation epilogue)
@triton.jit
def fused_gemm_relu(a_ptr, b_ptr, out_ptr, ...):
acc = tl.dot(a, b)
acc = tl.maximum(acc, 0.0) # ReLU
tl.store(out_ptr + offsets, acc, mask=mask)
expected_speedup: "workload-dependent (positive if avoiding a large materialized intermediate; negative if replacing vendor GEMM)"
applies_to:
- custom_gemm_only
- mlp
- transformer
- id: gemm_bias_activation_fusion
name: "GEMM + Bias + Activation Chain (partial fusion recommended)"
stage: fusion
description: "Fuse bias + light activation into GEMM epilogue; split heavy chains if needed"
rationale: |
Bias add + simple activations are often good epilogues. Very heavy activation chains
(multiple exp/tanh/clamp) can increase register pressure and reduce occupancy.
Prefer partial fusion when needed:
- fuse bias + simple activation
- keep heavy post-processing in a separate kernel
applies_when:
- uses_custom_gemm: true
- intermediate_materialized: true
- replaces_vendor_gemm: false
rejects_when:
- replaces_vendor_gemm: true
- activation_chain_is_heavy: true
pattern_before: |
c = gemm(a, b)
c = c + bias
c = swish(c)
c = clamp(c, -1, 1)
c = tanh(c)
pattern_after: |
@triton.jit
def fused_gemm_bias_activation(..., bias_ptr, out_ptr, ...):
acc = tl.dot(a, b)
bias = tl.load(bias_ptr + offs_n, mask=mask_n, other=0.0)
acc = acc + bias[None, :]
# Light activation epilogue (example: ReLU or SiLU)
# ReLU:
acc = tl.maximum(acc, 0.0)
tl.store(out_ptr + offsets, acc, mask=mask)
# If you truly need heavy chains (swish+clamp+tanh), consider splitting:
# - fused GEMM+bias+swish
# - separate clamp+tanh kernel
expected_speedup: "workload-dependent"
applies_to:
- mlp
- gelu
- silu
- id: reduction_elementwise_fusion
name: "Reduction + Elementwise Fusion (usually good; watch pressure)"
stage: fusion
description: "Fuse reductions with subsequent broadcast elementwise ops"
rationale: |
Reductions followed by broadcast (softmax / layernorm / rmsnorm) often benefit from
fusion because it avoids writing the reduction results (max/sum/mean/var) to memory.
However, very large reductions or overly large blocks can increase register/shared
usage. If performance regresses, consider a 2-stage approach.
pattern_before: |
# Compute max for numerical stability
max_val = tl.max(x, axis=1)
x_stable = x - max_val[:, None]
# Compute exp
exp_x = tl.exp(x_stable)
# Sum for softmax denominator
sum_exp = tl.sum(exp_x, axis=1)
# Normalize
softmax = exp_x / sum_exp[:, None]
pattern_after: |
max_val = tl.max(x, axis=1, keep_dims=True)
x_stable = x - max_val
exp_x = tl.exp(x_stable)
sum_exp = tl.sum(exp_x, axis=1, keep_dims=True)
softmax = exp_x / sum_exp
expected_speedup: "1.2-3x (shape dependent)"
applies_to:
- softmax
- layernorm
- attention
- id: fusion_skip_when_dead_or_constant
name: "Skip fusion if the fused op is dead / constant / no-op"
stage: fusion
description: "Do not fuse operations that are provably redundant for the given workload"
rationale: |
If an op is effectively a no-op for the given constants/data distribution,
fusion adds extra instructions without reducing memory traffic meaningfully.
Examples:
- clamp range is so wide it never triggers
- min/max with a constant that is outside observed values
- multiply by 1, add 0
expected_speedup: "prevents regressions"
applies_to:
- all
- id: gemm_matrix_add_epilogue
name: "GEMM + matrix add (residual/bias matrix) epilogue fusion"
stage: fusion
description: "Load a full matrix D after the GEMM K-loop and add to accumulator before storing"
rationale: |
A common pattern is C = A @ B + D where D is a full [M, N] matrix (residual
connection, bias broadcast, etc.). Fusing the matrix add into the GEMM epilogue
avoids a separate kernel launch and an extra read+write of the [M, N] output.
The epilogue is lightweight (one descriptor load + one add), so register pressure
impact is minimal and this fusion is almost always a win when you already have a
custom GEMM kernel.
Tensor descriptors preferred on XPU; block pointers also supported.
pattern_before: |
# Separate: GEMM then add
c = matmul(a, b) # writes [M, N] to memory
c = c + d # reads [M, N] + [M, N], writes [M, N]
pattern_after: |
# Fused: add D inside GEMM epilogue (tensor descriptor variant)
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
off_k = 0
for _ in range(0, K, BLOCK_SIZE_K):
a = a_desc.load([pid_m * BLOCK_SIZE_M, off_k])
b = b_desc.load([off_k, pid_n * BLOCK_SIZE_N])
accumulator += tl.dot(a, b)
off_k += BLOCK_SIZE_K
# Epilogue: load D tile and add
d_desc = tl.make_tensor_descriptor(base=d_ptr, shape=(M, N),
strides=(stride_dm, stride_dn),
block_shape=(BLOCK_SIZE_M, BLOCK_SIZE_N))
d = d_desc.load([pid_m * BLOCK_SIZE_M, pid_n * BLOCK_SIZE_N])
c = accumulator + d
c_desc = tl.make_tensor_descriptor(base=c_ptr, shape=(M, N),
strides=(stride_cm, stride_cn),
block_shape=(BLOCK_SIZE_M, BLOCK_SIZE_N))
c_desc.store([pid_m * BLOCK_SIZE_M, pid_n * BLOCK_SIZE_N], c)
expected_speedup: "eliminates one full [M, N] read+write pass"
applies_to:
- gemm
- residual_add
- bias_matrix
- transformer
notes: |
- D can be the same dtype as the accumulator or a different dtype (Triton auto-casts)
- For batched variants, create d_desc with base=d_ptr + batch_offset
- This pattern extends to any lightweight elementwise epilogue (add, mul, clamp, relu)
applied after the K-loop and before the store
- id: algebraic_weight_folding
name: "Algebraic Weight Folding (BN/scale/affine into GEMM weights)"
stage: fusion
description: |
Fold per-channel linear transforms into GEMM weights at pack time,
eliminating the epilogue entirely. This is the most impactful fusion
for kernels with BatchNorm inference, per-channel scaling, or affine transforms.
rationale: |
BatchNorm inference is a per-channel linear operation:
BN(y) = gamma * (y - mean) / sqrt(var + eps) + beta
When GEMM output feeds through scaling then BN:
y = (x @ W^T + bias) * scale
output = BN(y) = gamma * (y - mean) * inv_std + beta
This entire chain is linear in W and bias. We can pre-compute:
alpha = gamma * scale * inv_std [N]
W_fused[n, :] = alpha[n] * W[n, :] [N, K]
b_fused[n] = alpha[n] * bias[n] - gamma[n] * inv_std[n] * mean[n] + beta[n] [N]
The kernel becomes a pure GEMM + bias — zero epilogue overhead, zero extra
vector loads, maximum compute efficiency.
applies_when:
- BatchNorm or LayerNorm in inference mode (fixed running stats)
- Per-channel scaling (multiply by [N] vector)
- Any per-channel affine transform: y = a*x + b
rejects_when:
- Training mode (statistics computed per-batch, not fixed)
- Non-linear epilogues (sigmoid, ReLU) — cannot be folded
- Operations depending on the M dimension (row-wise reductions)
pattern_before: |
# _pack_weights: just transpose
self.weight_t = w.to(device).t().contiguous()
self.bias_xpu = b.to(device)
self.scale_xpu = s.to(device)
self.gamma_xpu = gamma.to(device)
# ... 6 more buffers
# kernel epilogue: 6 vector loads + arithmetic
bias = tl.load(bias_ptr + offs_n, mask=mask_n, other=0.0)
acc = acc + bias[None, :]
scale = tl.load(scale_ptr + offs_n, mask=mask_n, other=1.0)
acc = acc * scale[None, :]
mean = tl.load(mean_ptr + offs_n, mask=mask_n, other=0.0)
var = tl.load(var_ptr + offs_n, mask=mask_n, other=1.0)
gamma = tl.load(gamma_ptr + offs_n, mask=mask_n, other=1.0)
beta = tl.load(beta_ptr + offs_n, mask=mask_n, other=0.0)
inv_std = 1.0 / tl.sqrt(var + eps)
acc = (acc - mean[None, :]) * inv_std[None, :]
acc = acc * gamma[None, :] + beta[None, :]
pattern_after: |
# _pack_weights: fold BN + scale into weights (one-time cost)
inv_std = 1.0 / torch.sqrt(rv + self.eps)
alpha = gamma * s * inv_std
w_fused = alpha.unsqueeze(1) * w
b_fused = alpha * b - gamma * inv_std * rm + beta
self.weight_t = w_fused.to(device).t().contiguous().to(torch.bfloat16)
self.bias_fused = b_fused.to(device)
# kernel epilogue: just bias add (everything else is folded)
bias = tl.load(bias_ptr + offs_n, mask=mask_n, other=0.0)
acc = acc + bias[None, :]
expected_speedup: "1.5-2x on top of Level 1 optimizations (eliminates epilogue entirely)"
applies_to:
- gemm
- batchnorm
- inference
- mlp
examples:
- file: test_kernels/39_Gemm_Scale_BatchNorm_triton.py
description: "Folds Linear + Scale + BatchNorm into pure GEMM. Level 1: 2.69x → Level 3: 5.28x"HuggingFace Kernels Integration Guide (XPU)
Complete guide for using and publishing kernels with the HuggingFace Kernels library (get_kernel) on Intel XPU.
Quick Start: See huggingface_kernels_example.py for a minimal working example.
Overview
The HuggingFace Kernels library enables dynamic loading of pre-compiled kernels from the Hugging Face Hub. This eliminates the need for local compilation and ensures compatibility across different Python, PyTorch, and backend versions.
Key Benefits:
- No local compilation — download pre-built binaries
- Version management — load specific kernel versions
- Multi-version support — multiple versions coexist in one Python process
- Automatic compatibility — matches your PyTorch configuration
XPU Note: Not all Hub kernels have XPU builds. Triton-based kernels (e.g., triton-layer-norm) are more likely to work on XPU than CUDA C kernels. Always check with has_kernel() first.
Installation
pip install kernels torch numpyRequirements:
- PyTorch >= 2.5 (XPU build)
- Intel XPU GPU
- Python 3.8+
Core API
get_kernel
Download and load a kernel from the Hub:
from kernels import get_kernel
kernel = get_kernel("kernels-community/triton-layer-norm")
# With specific version
kernel = get_kernel("kernels-community/triton-layer-norm", version=1)
# With specific revision
kernel = get_kernel("kernels-community/flash-attn", revision="v2.0.0")Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
repo_id | str | required | Hub repository (e.g., "kernels-community/activation") |
revision | str | "main" | Branch, tag, or commit hash |
version | int/str | None | Kernel version number (mutually exclusive with revision) |
Returns: ModuleType — the imported kernel module
has_kernel
Check if a kernel build exists for your environment:
from kernels import has_kernel
if has_kernel("kernels-community/triton-layer-norm"):
kernel = get_kernel("kernels-community/triton-layer-norm")
else:
print("No compatible build for this XPU/PyTorch version")get_local_kernel
Load a kernel from a local path (useful during development):
from kernels import get_local_kernel
kernel = get_local_kernel("/path/to/my-kernel")load_kernel & get_locked_kernel
For reproducible, offline-capable deployments using lockfiles:
from kernels import load_kernel, get_locked_kernel
kernel = load_kernel("lockfile.json")
kernel = get_locked_kernel("kernels-community/activation", lockfile="kernel.lock")Usage Examples
1. RMSNorm Kernel from Hub
Note: The actual function name may vary by kernel version. Use dir(kernel) to inspect, and check for rms_norm_fn, rms_norm, or rmsnorm.
import torch
from kernels import get_kernel, has_kernel
repo_id = "kernels-community/triton-layer-norm"
if has_kernel(repo_id):
layer_norm = get_kernel(repo_id)
# Inspect available functions
print([f for f in dir(layer_norm) if not f.startswith('_')])
# e.g. ['layer_norm', 'layer_norm_fn', 'rms_norm_fn', ...]
x = torch.randn(2, 1024, 2048, dtype=torch.bfloat16, device="xpu")
weight = torch.ones(2048, dtype=torch.bfloat16, device="xpu")
# Use the actual function name (rms_norm_fn in current version)
out = layer_norm.rms_norm_fn(x, weight, eps=1e-6)
print(f"Output shape: {out.shape}")
else:
print("No XPU-compatible build available")2. Integration with Transformers Models
import torch
from kernels import get_kernel, has_kernel
repo_id = "kernels-community/triton-layer-norm"
if has_kernel(repo_id):
rmsnorm_kernel = get_kernel(repo_id)
def patch_rmsnorm_with_hub_kernel(model):
"""Patch model's RMSNorm to use Hub kernel."""
patched = 0
for name, module in model.named_modules():
if 'RMSNorm' in type(module).__name__:
eps = getattr(module, 'variance_epsilon', None) or getattr(module, 'eps', 1e-6)
def make_forward(mod, epsilon):
def forward(hidden_states):
return rmsnorm_kernel.rms_norm(hidden_states, mod.weight, eps=epsilon)
return forward
module.forward = make_forward(module, eps)
patched += 1
return patched3. Integration with Diffusers Pipelines
import torch
from diffusers import LTXPipeline
from kernels import get_kernel, has_kernel
if has_kernel("kernels-community/triton-layer-norm"):
rmsnorm_kernel = get_kernel("kernels-community/triton-layer-norm")
def patch_rmsnorm(model):
for name, module in model.named_modules():
if type(module).__name__ == 'RMSNorm':
eps = getattr(module, 'eps', 1e-6)
has_weight = hasattr(module, 'weight') and module.weight is not None
if has_weight:
def make_forward(mod, epsilon):
def forward(x):
return rmsnorm_kernel.rms_norm(x, mod.weight, eps=epsilon)
return forward
module.forward = make_forward(module, eps)
pipe = LTXPipeline.from_pretrained("Lightricks/LTX-Video", torch_dtype=torch.bfloat16)
pipe.to("xpu")
patch_rmsnorm(pipe.transformer)4. Benchmark Hub Kernel vs PyTorch
import time
import torch
from kernels import get_kernel
kernel = get_kernel("kernels-community/triton-layer-norm")
sizes = [(2, 1024, 2048), (4, 4096, 4096)]
for shape in sizes:
x = torch.randn(shape, dtype=torch.bfloat16, device="xpu")
w = torch.ones(shape[-1], dtype=torch.bfloat16, device="xpu")
for _ in range(10):
kernel.rms_norm(x, w, eps=1e-6)
variance = x.pow(2).mean(-1, keepdim=True)
_ = x * torch.rsqrt(variance + 1e-6) * w
torch.xpu.synchronize()
iters = 100
start = time.perf_counter()
for _ in range(iters):
kernel.rms_norm(x, w, eps=1e-6)
torch.xpu.synchronize()
hub_ms = (time.perf_counter() - start) / iters * 1000
start = time.perf_counter()
for _ in range(iters):
variance = x.pow(2).mean(-1, keepdim=True)
_ = x * torch.rsqrt(variance + 1e-6) * w
torch.xpu.synchronize()
pt_ms = (time.perf_counter() - start) / iters * 1000
print(f"Shape {shape}: Hub={hub_ms:.3f}ms, PyTorch={pt_ms:.3f}ms, Speedup={pt_ms/hub_ms:.2f}x")XPU-Specific Notes
Kernel Compatibility
Not all Hub kernels have XPU builds:
| Kernel Type | XPU Support | Notes |
|---|---|---|
Triton-based (e.g., triton-layer-norm) | Likely | Triton compiles via Intel XPU backend |
CUDA C-based (e.g., flash-attn) | Check | Needs explicit XPU build |
| Custom CUDA ops | Unlikely | CUDA-only unless ported |
Always check availability first:
from kernels import has_kernel
if has_kernel("kernels-community/triton-layer-norm"):
print("XPU build available")
else:
print("No XPU build — use local Triton kernel instead")Fallback Strategy
When a Hub kernel is not available for XPU, fall back to the local Triton implementation:
from kernels import has_kernel, get_kernel
def get_rmsnorm_function():
"""Get best available RMSNorm implementation."""
if has_kernel("kernels-community/triton-layer-norm"):
kernel = get_kernel("kernels-community/triton-layer-norm")
return lambda x, w, eps: kernel.rms_norm(x, w, eps=eps)
else:
from your_local_kernels import triton_rmsnorm
return triton_rmsnormEnvironment Check
import torch
print(f"PyTorch: {torch.__version__}")
print(f"XPU available: {torch.xpu.is_available()}")
print(f"GPU: {torch.xpu.get_device_name()}")Publishing Kernels to Hub
Triton Kernel Project Structure
For Triton-based kernels (best XPU compatibility):
my-triton-kernel/
├── build.toml
├── kernel_src/
│ └── rmsnorm.py # Triton kernel source
└── torch-ext/
├── torch_binding.cpp
└── my_kernels/
└── __init__.pybuild.toml for Triton Kernels
[general]
name = "my_triton_kernels"
backends = ["cuda", "xpu"] # Include XPU backend
[torch]
src = ["torch-ext/torch_binding.cpp"]
[kernel.rmsnorm]
backend = "triton"
src = ["kernel_src/rmsnorm.py"]
depends = ["torch"]Build and Publish
pip install kernel-builder
kernel-builder build
huggingface-cli repo create your-org/your-kernel --type model
huggingface-cli upload your-org/your-kernel ./distOthers Load It
from kernels import get_kernel
rmsnorm = get_kernel("your-org/your-kernel")Available Community Kernels
Popular kernels from kernels-community:
| Kernel | Description | XPU? |
|---|---|---|
triton-layer-norm | LayerNorm, RMSNorm | Likely |
activation | GELU, SiLU, etc. | Check |
flash-attn | Flash Attention 2 | Check |
quantization | INT8/INT4 ops | Check |
Browse all kernels: https://huggingface.co/kernels-community
Caching and Offline Usage
import os
os.environ["HF_HUB_OFFLINE"] = "1"
# Will only use cached kernels
kernel = get_kernel("kernels-community/triton-layer-norm")Best Practices
1. Always check availability — has_kernel() before get_kernel() 2. Pin versions — get_kernel(repo, version=1) for reproducibility 3. Have a fallback — local Triton kernel when Hub build is unavailable 4. Use lockfiles in production — load_kernel("kernel.lock") 5. Test on your GPU — verify correctness after loading
See Also
Implementation Reference
Code templates and patterns for Triton kernel development on Intel XPU.
Template Selection
Start with a template that matches your kernel type. The core patterns are shown below:
- Basic GEMM (with tensor descriptors and tile swizzling)
- GEMM with fused epilogue
- Reduction operations
Core Implementation Pattern
Generated kernels must be self-contained and shareable. Define all helper functions inline within the kernel file.
import math
import torch
import torch.nn as nn
import triton
import triton.language as tl
# ============================================================================
# Helper functions (inline definitions for self-contained kernel)
# ============================================================================
# Constants
kAlpha = tl.constexpr(math.sqrt(2.0 / math.pi)) # For GeLU
kInvLn2 = tl.constexpr(1.4426950408889634) # For exp2-based ops
@triton.jit
def swizzle_tile(tile_id, M, N, K, BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, GROUP_SIZE_M):
"""Tile swizzling for L2 cache locality"""
grid_m = tl.cdiv(M, BLOCK_SIZE_M)
grid_n = tl.cdiv(N, BLOCK_SIZE_N)
width = GROUP_SIZE_M * grid_n
group_id = tile_id // width
group_size = tl.minimum(GROUP_SIZE_M, grid_m - group_id * GROUP_SIZE_M)
pid_m = group_id * GROUP_SIZE_M + (tile_id % group_size)
pid_n = (tile_id % width) // group_size
return pid_m, pid_n
@triton.autotune(
configs=[
# Large tiles for square GEMMs
triton.Config(
{'BLOCK_M': 256, 'BLOCK_N': 256, 'BLOCK_K': 32, 'GROUP_SIZE_M': 4, 'grf_mode': '256'},
num_warps=32, num_stages=2
),
triton.Config(
{'BLOCK_M': 256, 'BLOCK_N': 256, 'BLOCK_K': 64, 'GROUP_SIZE_M': 4, 'grf_mode': '256'},
num_warps=16, num_stages=3
),
# Medium tiles
triton.Config(
{'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 64, 'GROUP_SIZE_M': 4, 'grf_mode': '256'},
num_warps=8, num_stages=4
),
triton.Config(
{'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 64, 'GROUP_SIZE_M': 4, 'grf_mode': '256'},
num_warps=16, num_stages=3
),
# Skinny-M configs (for M < 256)
triton.Config(
{'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 64, 'GROUP_SIZE_M': 2, 'grf_mode': '256'},
num_warps=8, num_stages=4
),
triton.Config(
{'BLOCK_M': 32, 'BLOCK_N': 128, 'BLOCK_K': 64, 'GROUP_SIZE_M': 2, 'grf_mode': '256'},
num_warps=4, num_stages=5
),
],
key=['M', 'N', 'K'],
)
@triton.jit
def kernel(
# Pointers
a_ptr, b_ptr, c_ptr,
# Shapes (as constexpr for better codegen)
M: tl.constexpr, N: tl.constexpr, K: tl.constexpr,
# Strides
stride_am: tl.constexpr, stride_ak: tl.constexpr,
stride_bk: tl.constexpr, stride_bn: tl.constexpr,
stride_cm: tl.constexpr, stride_cn: tl.constexpr,
# Meta-parameters (NO defaults!)
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
BLOCK_K: tl.constexpr,
GROUP_SIZE_M: tl.constexpr,
):
"""Optimized GEMM kernel for Intel XPU using tensor descriptors."""
# Tile swizzling (1D grid)
pid = tl.program_id(0)
pid_m, pid_n = swizzle_tile(pid, M, N, K, BLOCK_M, BLOCK_N, BLOCK_K, GROUP_SIZE_M)
# Tensor descriptors (preferred on XPU — better codegen than block pointers)
a_desc = tl.make_tensor_descriptor(
base=a_ptr, shape=[M, K], strides=[stride_am, stride_ak],
block_shape=[BLOCK_M, BLOCK_K],
)
b_desc = tl.make_tensor_descriptor(
base=b_ptr, shape=[K, N], strides=[stride_bk, stride_bn],
block_shape=[BLOCK_K, BLOCK_N],
)
# Accumulator (fp32 for numerical stability)
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
# K-loop
off_m = pid_m * BLOCK_M
off_n = pid_n * BLOCK_N
for off_k in range(0, K, BLOCK_K):
a = a_desc.load([off_m, off_k])
b = b_desc.load([off_k, off_n])
a = a.to(tl.bfloat16)
b = b.to(tl.bfloat16)
acc += tl.dot(a, b)
# Store result
c_desc = tl.make_tensor_descriptor(
base=c_ptr, shape=[M, N], strides=[stride_cm, stride_cn],
block_shape=[BLOCK_M, BLOCK_N],
)
c_desc.store([off_m, off_n], acc)Model Class Wrapper (ai-bench compatible)
The Model class uses standard nn.Module patterns. ai-bench creates the model via __init__() and syncs weights using copy_model_weights().
class Model(nn.Module):
def __init__(self, input_size, hidden_size, scaling_factor):
super().__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.scaling_factor = scaling_factor
self.gemm = nn.Linear(input_size, hidden_size)
self._packed = False
def _pack_weights(self):
"""Pack weight transpose once on XPU for fast tl.dot access."""
device = torch.device("xpu")
w = self.gemm.weight.data.detach()
b = self.gemm.bias.data.detach()
self.weight_t = w.to(device, torch.float16).t().contiguous()
self.bias_xpu = b.to(device, torch.float16).contiguous()
self._packed = True
def forward(self, x):
device = torch.device("xpu")
x = x.to(device, torch.float16).contiguous()
if not self._packed:
self._pack_weights()
M, K = x.shape
N = self.weight_t.shape[1]
output = torch.empty((M, N), device=device, dtype=torch.float32)
grid = lambda META: (
triton.cdiv(M, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']),
)
kernel[grid](
x, self.weight_t, output,
M, N, K,
x.stride(0), x.stride(1),
self.weight_t.stride(0), self.weight_t.stride(1),
output.stride(0), output.stride(1),
)
return output
# ============================================================================
# Benchmark harness interface (must match *_pytorch.py)
# ============================================================================
batch_size = 1024
input_size = 8192
hidden_size = 8192
scaling_factor = 2.0
def get_inputs():
return [torch.rand(batch_size, input_size)]
def get_init_inputs():
return [input_size, hidden_size, scaling_factor]Example: GEMM Transformation
Input (test_kernels/14_Gemm_Divide_Sum_Scaling_pytorch.py):
x = torch.matmul(x, self.weight.T) # Gemm
x = x / 2 # Divide
x = torch.sum(x, dim=1, keepdim=True) # Sum
x = x * self.scaling_factor # ScalingStrategy: 1. Use tensor descriptors for GEMM (preferred on XPU) 2. Fuse divide into GEMM epilogue (light) 3. Keep sum + scaling in separate reduction kernel (avoid serializing over N)
Output: gemm_kernel (matmul + divide fused) + row_sum_kernel (sum + scaling).
See references/examples/gemm_activation_optimized.py for a similar pattern.
File Naming Convention
Spec YAML files live in modules/ai-bench/problems/specs/KernelBench/level*/. Auto-detection strips suffixes (_triton, _optimized, _opt, _pytorch) from filename and searches level1/, level2/, level3/. Override with --spec if needed.
Activation Helpers
# exp2-based sigmoid (faster on XPU)
sigmoid(x) = 1 / (1 + exp2(-x * 1.44269504))
# tanh via sigmoid
tanh(x) = 2*sigmoid(2x) - 1Factor into reusable @triton.jit helpers defined inline in your kernel file.
KernelBench Operator Classification & Skill Mapping
This document classifies KernelBench operators into categories and maps each to the appropriate kernel skill/pattern.
Classification Taxonomy
Level 1: Basic Operators (53 operators)
Category A: GEMM / Matrix Multiplication (18 operators)
| ID | Name | Sub-type | Key Skill |
|---|---|---|---|
| 1 | Square matrix multiplication | Dense GEMM | Tile Swizzle + Autotune |
| 2 | Standard matrix multiplication | Dense GEMM (M!=N) | Tile Swizzle + Autotune |
| 3 | Batched matrix multiplication | BMM | Batch-indexed GEMM |
| 4 | Matrix-vector multiplication | MatVec | 1D reduction pattern |
| 5 | Matrix-scalar multiplication | Elementwise | Scale kernel |
| 6 | Matmul with large K | Large-K GEMM | K-dimension blocking |
| 7 | Matmul with small K | Small-K GEMM | Fewer K-iterations |
| 8 | Matmul with irregular shapes | Non-square GEMM | Mask handling |
| 9 | Tall-skinny matmul | Tall-skinny GEMM | Tile shape tuning |
| 10 | 3D tensor-matrix mul | Batched GEMM | Reshape + GEMM |
| 11 | 4D tensor-matrix mul | Batched GEMM | Einsum decomposition |
| 12 | Diagonal matrix mul | Special GEMM | Elementwise pattern |
| 13 | Symmetric matrices | Dense GEMM | Standard GEMM |
| 14 | Upper triangular mul | Masked GEMM | Triangle mask |
| 15 | Lower triangular mul | Masked GEMM | Triangle mask |
| 16 | Transposed A | Transposed GEMM | Stride adjustment |
| 17 | Transposed B | Transposed GEMM | Stride adjustment |
| 18 | Both transposed | Transposed GEMM | Stride adjustment |
Key Pattern: Template 5 (GEMM with Tile Swizzle) Critical Optimization: Tile swizzle + L2 cache grouping + tensor descriptors
Category B: Elementwise / Activation Functions (14 operators)
| ID | Name | Sub-type | Key Skill |
|---|---|---|---|
| 19 | ReLU | Branching | tl.where(x > 0, x, 0) |
| 20 | LeakyReLU | Branching | tl.where(x > 0, x, alpha*x) |
| 21 | Sigmoid | Transcendental | 1/(1+exp(-x)) |
| 22 | Tanh | Transcendental | (exp(2x)-1)/(exp(2x)+1) |
| 23 | Softmax | Row reduction | Online softmax |
| 24 | LogSoftmax | Row reduction | Online softmax + log |
| 25 | Swish/SiLU | Transcendental | x * sigmoid(x) |
| 26 | GELU | Transcendental | 0.5*x*(1+erf(x/sqrt(2))) |
| 27 | SELU | Branching + exp | scale * where(x>0, x, alpha*(exp(x)-1)) |
| 28 | HardSigmoid | Clamp | clamp((x+3)/6, 0, 1) |
| 29 | Softplus | Transcendental | log(1+exp(x)) |
| 30 | Softsign | Division | x/(1+abs(x)) |
| 31 | ELU | Branching + exp | where(x>0, x, alpha*(exp(x)-1)) |
| 32 | HardTanh | Clamp | clamp(x, -1, 1) |
Key Pattern: Template 1 (Elementwise) Critical Optimization: Large BLOCK_SIZE (4096-16384), FP32 compute
Category C: Normalization (8 operators)
| ID | Name | Sub-type | Key Skill |
|---|---|---|---|
| 33 | BatchNorm | Multi-dim reduction | Welford algorithm |
| 34 | InstanceNorm | Per-instance reduction | Per-sample norm |
| 35 | GroupNorm | Group reduction | Grouped channels |
| 36 | RMSNorm | Row reduction | x * rsqrt(mean(x^2) + eps) |
| 37 | FrobeniusNorm | Full reduction | sqrt(sum(x^2)) |
| 38 | L1 Norm | Full reduction | sum(abs(x)) |
| 39 | L2 Norm | Full reduction | sqrt(sum(x^2)) |
| 40 | LayerNorm | Row reduction | (x-mean)/std * w + b |
Key Pattern: Template 3 (Row-wise Reduction) Critical Optimization: FP32 accumulation, proper reduction
Category D: Pooling (6 operators)
| ID | Name | Sub-type | Key Skill |
|---|---|---|---|
| 41 | Max Pooling 1D | Sliding window | Max reduction |
| 42 | Max Pooling 2D | 2D window | 2D index mapping |
| 43 | Max Pooling 3D | 3D window | Program_id flattening |
| 44 | Average Pooling 1D | Sliding window | Sum + divide |
| 45 | Average Pooling 2D | 2D window | 2D index mapping |
| 46 | Average Pooling 3D | 3D window | Program_id flattening |
Key Challenge: 3D grid mapping with Triton's program_id limits
Category E: Reduction (7 operators)
| ID | Name | Sub-type | Key Skill |
|---|---|---|---|
| 47 | Sum reduction | Sum | tl.sum() |
| 48 | Mean reduction | Mean | tl.sum() / count |
| 49 | Max reduction | Max | tl.max() |
| 50 | Min reduction | Min | tl.min() |
| 51 | Argmax | Index + max | Two-pass or manual |
| 52 | Argmin | Index + min | Two-pass or manual |
| 53 | Min (duplicate) | Min | tl.min() |
Key Pattern: Template 5 (Dimension Reduction) Key Challenge: Argmax/Argmin require manual implementation
Level 2: Fused Operators (20+ operators)
Combine multiple operations into single kernels.
| Category | Examples | Strategy |
|---|---|---|
| GEMM + Activation | Gemm_ReLU, Gemm_GELU | Fuse activation into GEMM epilogue |
| GEMM + Norm | Gemm_BatchNorm, Gemm_GroupNorm | Two-phase kernel |
| GEMM + Scale | Gemm_Scale, Gemm_Divide | Fuse into GEMM store |
| Multi-op fusion | Matmul_Sum_Max_AvgPool | Sequential fusion |
Key Pattern: Template 6 (Fused GEMM + Activation)
Level 3-4: Network Models / Transformers
Full models requiring multiple kernel types. Decompose into Level 1 operators.
Level 6-7: Advanced / Expert
| Operator | Type | Strategy |
|---|---|---|
| MinGPTNewGelu | Fused activation | GELU approximation kernel |
| ScaledDotProductAttention | Attention | Flash Attention pattern |
| GELU_And_Mul | Fused activation | gelu(x) * y |
| MoE_TopK_Softmax | MoE routing | Specialized kernel |
| Gemm_A8W8_Blockwise | Quantized GEMM | INT8 with block scaling |
Category → Skill Mapping
| Category | Skill File | Priority |
|---|---|---|
| GEMM | gemm-skill.md (planned) | P0 - Most impactful |
| Elementwise | elementwise-skill.md (planned) | P0 - Most common |
| Normalization | normalization-skill.md (planned) | P1 - Frequently used |
| Reduction | reduction-skill.md (planned) | P1 - Common pattern |
| Softmax | softmax-skill.md (planned) | P1 - Critical for attention |
| Pooling | pooling-skill.md (planned) | P2 - Moderate complexity |
| Attention | attention-skill.md (planned) | P2 - High complexity |
| Fused | fused-skill.md (planned) | P2 - Combination patterns |
Performance Expectations by Category
Based on kernel-agent test results:
| Category | Achievable Speedup | Difficulty | Notes |
|---|---|---|---|
| Elementwise | 1.0-3.0x | Low | Large blocks, memory-bound |
| Reduction (sum/mean) | 1.5-5.0x | Medium | Good parallelism |
| Pooling | 1.5-5.0x | Medium | Grid mapping challenge |
| LayerNorm/RMSNorm | 1.5-2.0x | Medium | Row-wise reduction |
| Dense GEMM | 0.8-1.2x | High | Tile swizzle critical |
| Batched GEMM | 0.6-0.9x | High | Memory bandwidth limited |
| BatchNorm | <0.1x | Very High | HIP sync issues |
| Argmax/Argmin | FAIL | Very High | Triton API limitation |
| Fused operators | 0.3-1.0x | Very High | Correctness challenges |
Recommended Skill Development Order
1. Phase 1 (Quick wins): Elementwise activations, Sum/Mean reduction 2. Phase 2 (Core): GEMM with tile swizzle, LayerNorm/RMSNorm 3. Phase 3 (Advanced): Softmax, Pooling, Attention 4. Phase 4 (Expert): Fused operators, BatchNorm, Quantized GEMM
# Intel XPU Memory Patterns Optimizations
# These patterns focus on memory access, layout, and avoiding hidden sync/overhead.
# They are intended to be used alongside your XPU-specific compute patterns.
constraints:
- id: no_device_to_host_scalar_sync
name: "Do NOT force device->host scalar sync in hot path"
severity: critical
description: |
Avoid .item(), float(tensor), int(tensor), printing device tensors, or any device->host scalar
extraction in forward()/kernel wrapper hot paths. This forces synchronization and kills perf.
WRONG:
```python
c = float(constant_tensor.item()) # syncs XPU -> host
```
CORRECT:
- Keep constants as Python floats / CPU tensors
- Pass scalar to kernel as an argument
- id: prefer_contiguous_inputs
name: "Prefer contiguous tensors for Triton kernels"
severity: critical
description: |
Triton kernels generally assume or strongly benefit from contiguous memory.
If inputs are strided/non-contiguous, do an explicit .contiguous() in wrapper
(outside the timed region if possible).
WRONG:
```python
# Non-contiguous input passed into kernel
kernel[grid](x_transposed, ...)
```
CORRECT:
```python
if not x.is_contiguous():
x = x.contiguous()
kernel[grid](x, ...)
```
- id: block_ptr_boundary_check_tuple
name: "Block pointer boundary_check must be tuple of ints"
severity: critical
description: |
tl.load(block_ptr) uses boundary_check=(dim0, dim1) where values are dimension indices.
Use (0,1) not booleans.
CORRECT:
```python
tl.load(ptr, boundary_check=(0, 1))
```
- id: no_tl_multiple_of_on_python_scalars
name: "Do NOT call tl.multiple_of / tl.max_contiguous on Python scalars or constexpr-like values"
severity: critical
description: |
tl.multiple_of / tl.max_contiguous are intended for Triton IR values (tensors/expressions),
not Python integers/constexpr-like scalars such as stride arguments.
WRONG (can error like: 'constexpr' object has no attribute 'shape'):
```python
tl.multiple_of(stride_xk, 1)
```
CORRECT (apply to tensor expressions):
```python
offs_n = col_start + tl.arange(0, BLOCK_N)
tl.multiple_of(offs_n, 8) # only if you KNOW it is aligned/multiple
```
patterns:
- id: mem_block_pointers
name: "Block pointers for structured tiles (fallback — prefer tensor descriptors on XPU)"
stage: memory_access
description: "Use tl.make_block_ptr + tl.load(boundary_check=...) for tiled loads/stores"
rationale: |
NOTE: On Intel XPU, tensor descriptors (tl.make_tensor_descriptor) are preferred
over block pointers — they produce better address generation codegen. See
xpu_optimizations.yaml (xpu_tensor_descriptor_note, xpu_descriptor_gemm_pattern).
Block pointers improve addressing and bounds handling over manual pointer math.
Use them as a fallback when tensor descriptors are not suitable (e.g., legacy code).
pattern_before: |
offs_m = pid_m * BM + tl.arange(0, BM)
offs_k = k0 + tl.arange(0, BK)
x_ptrs = x_ptr + offs_m[:, None] * stride_xm + offs_k[None, :] * stride_xk
x = tl.load(x_ptrs, mask=(offs_m[:, None] < M) & (offs_k[None, :] < K), other=0.0)
pattern_after: |
x_bp = tl.make_block_ptr(
base=x_ptr,
shape=(M, K),
strides=(stride_xm, stride_xk),
offsets=(pid_m * BM, k0),
block_shape=(BM, BK),
order=(1, 0),
)
x = tl.load(x_bp, boundary_check=(0, 1))
expected_speedup: "workload-dependent (often positive when indexing is heavy)"
applies_to: [gemm, conv, attention, all_memory_bound]
examples:
- before: |
k0 += BK
after: |
x_bp = tl.advance(x_bp, (0, BK))
- id: mem_skip_boundary_check_when_divisible
name: "Specialize away boundary checks when shapes are divisible"
stage: memory_access
description: "Avoid boundary_check/masks when M/N/K are known multiples of tile sizes"
rationale: |
Masks and boundary checks add overhead. If your benchmark/problem guarantees divisibility
(or you can specialize per shape), you can remove masks and use unconditional loads/stores.
pattern_before: |
x = tl.load(x_bp, boundary_check=(0, 1))
tl.store(o_bp, acc, boundary_check=(0, 1))
pattern_after: |
# If M % BM == 0 and K % BK == 0 (specialized case):
x = tl.load(x_bp)
tl.store(o_bp, acc)
expected_speedup: "1.05-1.20x"
applies_to: [gemm, large_fixed_shapes]
notes: |
- Only safe when you can guarantee divisibility or guard with a specialized kernel variant.
- id: mem_use_static_range_for_k_loop
name: "Use tl.static_range for K loops (when K is known / bounded)"
stage: memory_access
description: "Enable better unrolling/pipelining by using tl.static_range"
rationale: |
tl.static_range can help Triton generate better scheduled loops and reduce loop overhead.
Particularly useful in GEMM-like reductions and fixed-shape workloads.
pattern_before: |
for k in range(0, K, BK):
...
pattern_after: |
for k in tl.static_range(0, K, BK):
...
expected_speedup: "1.05-1.15x"
applies_to: [gemm, attention]
notes: |
Use only when K is a compile-time constant or you launch specialized kernels by K.
- id: mem_pointer_arithmetic_coalescing
name: "Prefer contiguous (coalesced) access in the innermost dimension"
stage: memory_access
description: "Make the fastest-changing index map to contiguous memory"
rationale: |
Coalesced loads/stores are critical. In most layouts, the last dimension is contiguous.
Make tl.arange(...) along the contiguous stride dimension when possible.
pattern_before: |
# Strided / scattered pattern (example)
offs = base + tl.arange(0, BN) * stride_n
x = tl.load(x_ptr + offs)
pattern_after: |
# Coalesced: make arange hit contiguous dimension
offs_n = col_start + tl.arange(0, BN)
x = tl.load(x_ptr + offs_n)
expected_speedup: "workload-dependent (can be large if fixing uncoalesced access)"
applies_to: [all_memory_bound]
- id: mem_cache_modifiers_optional
name: "Optional cache modifiers (backend-dependent)"
stage: memory_access
description: "Use cache_modifier/eviction_policy carefully; keep it optional"
rationale: |
cache modifiers can help (or hurt) depending on backend and access pattern.
Treat them as an autotune dimension rather than a hard rule.
pattern_before: |
x = tl.load(ptr)
pattern_after: |
# Example: try these as tunables if supported by your Triton backend
x = tl.load(ptr, cache_modifier=".ca")
# or:
x = tl.load(ptr, cache_modifier=".cg")
expected_speedup: "workload-dependent"
applies_to: [streaming_reads, bandwidth_bound]
notes: |
- Do not assume Intel XPU backend interprets modifiers the same as CUDA.
- Use only if you confirm correctness and performance.
- id: mem_alignment_hints_on_offsets
name: "Alignment hints on OFFSET tensors (not strides)"
stage: memory_access
description: "Use tl.multiple_of / tl.max_contiguous on tensor offset expressions"
rationale: |
Alignment hints can improve vectorization/coalescing when you truly have aligned access.
Apply hints to offset tensors (e.g., tl.arange expressions) and only when guaranteed.
pattern_before: |
offs_n = col_start + tl.arange(0, BN)
x = tl.load(x_ptr + offs_n)
pattern_after: |
offs_n = col_start + tl.arange(0, BN)
tl.max_contiguous(offs_n, BN)
# Only if you KNOW alignment/multiple is valid:
# tl.multiple_of(offs_n, 8)
x = tl.load(x_ptr + offs_n)
expected_speedup: "small but sometimes measurable"
applies_to: [gemm, bandwidth_bound]
notes: |
Do NOT apply tl.multiple_of blindly—incorrect assumptions can lead to wrong-code.
- id: mem_avoid_redundant_reads
name: "Load once, reuse in registers"
stage: memory_access
description: "Avoid re-loading the same values inside inner loops"
rationale: |
Redundant tl.load calls inside reduction loops can dominate memory traffic.
If values are reused (e.g., bias, scale), load once per tile and broadcast.
pattern_before: |
for k in ...:
b = tl.load(b_ptr + offs_n) # re-loaded each iteration (waste)
acc += ...
pattern_after: |
b = tl.load(b_ptr + offs_n) # load once
for k in ...:
acc += ...
acc += b[None, :]
expected_speedup: "1.05-1.30x (depends on redundancy)"
applies_to: [gemm_epilogues, fused_pointwise]
- id: mem_wrapper_one_time_device_moves
name: "One-time device moves in wrapper (avoid per-forward transfers)"
stage: memory_access
description: "Move parameters to XPU once, not every forward"
rationale: |
Per-forward .to('xpu') or .data = .to('xpu') costs time and can interfere with timing.
Use a one-time guard in the module.
pattern_before: |
def forward(self, x):
if self.weight.device.type != "xpu":
self.weight.data = self.weight.data.to("xpu")
...
pattern_after: |
def __init__(...):
self._moved_to_xpu = False
def forward(self, x):
if not self._moved_to_xpu:
self.weight.data = self.weight.data.to("xpu")
self._moved_to_xpu = True
expected_speedup: "huge for microbenchmarks; correctness/measurement improvement"
applies_to: [all]
notes: |
Ideally move model+inputs to XPU outside the timed region in the harness.
- id: mem_layout_transform_prepack
name: "Pre-pack / pre-transform weights for better access"
stage: memory_access
description: "Change weight layout to match access pattern (when allowed)"
rationale: |
If your kernel accesses W as W^T (K,N), storing W in a layout that makes K-contiguous
for the inner loop can improve coalescing/cache behavior. This is a higher-level change.
pattern_before: |
# W stored as [N, K], kernel reads as [K, N] view
w_ptr shape=(K, N), strides=(stride_wk, stride_wn)
pattern_after: |
# Prepack once (outside timed loop), store as [K, N] or blocked layout if acceptable
# so inner K loads are contiguous in memory for each program.
# (Exact layout depends on your kernel and framework constraints.)
expected_speedup: "workload-dependent (can be large for bandwidth-limited kernels)"
applies_to: [gemm, attention]
notes: |
- Only if you control weight storage and can amortize the prepack cost.
- Ensure KernelBench correctness expectations still hold.
- id: reduce_liveness_sink_load_and_prefetch
name: "Reduce variable liveness: prefetch early, load late (sink load closer to dot)"
stage: memory_access
description: "Replace long-lived live-in operand loads with prefetch + per-iteration load near dot/use"
rationale: |
On Intel XPU, keeping a large operand tile live across a long loop can reserve many registers,
increasing GRF pressure and causing spills. A better approach is:
1) prefetch the data earlier to pull it into L1, then
2) load the operand into registers only right before it is used (inside the loop),
reducing liveness and helping the register allocator.
This is especially relevant when a tensor is loaded outside a loop and used repeatedly
inside the loop (classic FlashAttention Q tile scenario).
pattern_before: |
# Load once, keep live across loop (long liveness)
q = tl.load(q_ptrs) # q stays live for the whole loop
for k0 in range(0, K, BLOCK_K):
k = tl.load(k_ptrs)
acc += tl.dot(q, k) # q used repeatedly
...
pattern_after: |
# Prefetch outside loop (warm cache)
# (pseudo; actual prefetch API may differ by backend/compiler lowering)
tl.prefetch(q_ptrs) # bring q closer (L1) without allocating regs long-term
for k0 in range(0, K, BLOCK_K):
# Load right before use (short liveness)
q = tl.load(q_ptrs) # q live only for a short window
k = tl.load(k_ptrs)
acc += tl.dot(q, k)
...
expected_speedup: "1.05-1.15x (selective; can regress if cache misses dominate)"
applies_to:
- attention
- flashattention
- dot_in_loop
- reduction_in_loop
- id: reduce_liveness_duplicate_load_for_multi_use
name: "Reduce liveness when operand has multiple uses (duplicate loads near each use)"
stage: memory_access
description: "If a large tensor has multiple distant uses, reload near each use to shorten live ranges"
rationale: |
If a large tensor is used in multiple regions far apart (e.g., two loops or two stages),
keeping it live across both regions can reserve registers too long. On XPU, reloading
from L1 can be cheaper than spilling registers to memory.
pattern_before: |
q = tl.load(q_ptrs) # q stays live a long time
for ...:
acc += tl.dot(q, k)
...
for ...:
acc2 += tl.dot(q, k2) # second use far away keeps q live across both loops
...
pattern_after: |
tl.prefetch(q_ptrs)
for ...:
q = tl.load(q_ptrs) # short liveness
acc += tl.dot(q, k)
...
for ...:
q = tl.load(q_ptrs) # reload for second stage
acc2 += tl.dot(q, k2)
...
expected_speedup: "0.95-1.10x (only when it avoids spills; otherwise may regress)"
applies_to:
- attention
- multi_stage_kernels
- dot_in_loop
- id: avoid_long_lived_large_tiles_across_control_flow
name: "Avoid long-lived tiles across control-flow splits (two-loop / masked attention patterns)"
stage: memory_access
description: "Prefer loading tiles inside each loop when control-flow creates long liveness (off-band/on-band)"
rationale: |
Control-flow splits (e.g., two loops for masked/unmasked regions) often extend the
lifetime of large tiles (Q) across both loops. On XPU, that increases GRF pressure.
Loading inside each loop reduces the live range and can prevent spills.
pattern_before: |
q = tl.load(Q_block_ptr) # q live across both loops
for start_n in range(lo, hi, BLOCK_N):
... qk += tl.dot(q, k) ...
for start_n in range(lo, hi, BLOCK_N):
... qk += tl.dot(q, k) ... # second loop extends q lifetime
pattern_after: |
tl.prefetch(Q_block_ptr)
for start_n in range(lo, hi, BLOCK_N):
q = tl.load(Q_block_ptr) # load per-iteration or per-loop (shorter liveness)
... qk += tl.dot(q, k) ...
for start_n in range(lo, hi, BLOCK_N):
q = tl.load(Q_block_ptr) # reload for second loop
... qk += tl.dot(q, k) ...
expected_speedup: "1.05-1.10x (most likely on masked/causal variants)"
applies_to:
- attention
- flashattention
- causal_mask
- id: trade_bandwidth_for_regs_when_spilling
name: "Prefer extra loads over GRF spills (XPU-first rule)"
stage: memory_access
description: "When register pressure is high, allow re-loads (from cache) to reduce spills"
rationale: |
When GRF pressure is high, the real cost is often spilling to memory.
Reloading a tile (especially if it hits in L1) can be cheaper than spilling
and enables other compiler optimizations (unrolling/scheduling).
pattern_before: |
# Keep many intermediates live to avoid re-loads
a = tl.load(...)
b = tl.load(...)
c = tl.load(...)
# long epilogue chain keeps many values live
y = f(g(h(a,b,c,...)))
pattern_after: |
# Shorten live ranges: recompute/reload where cheap
tl.prefetch(...)
a = tl.load(...) # load near use
y = f(a)
# if needed later, reload instead of keeping live
a2 = tl.load(...)
z = g(a2)
expected_speedup: "workload-dependent; key benefit is avoiding catastrophic regressions"
applies_to:
- flashattention
- long_epilogues
- register_heavy_kernels
- id: do_not_apply_if_cache_eviction_likely
name: "Do NOT sink loads if cache eviction is likely (small L1 / high conflict risk)"
stage: memory_access
description: "Guard against turning cheap L1 loads into expensive global loads"
rationale: |
The sink-load approach assumes the data remains in L1 after prefetch.
If cache conflicts/evictions are likely, sinking loads into the loop can increase
global memory traffic and regress performance.
pattern_before: |
# Always sink loads
tl.prefetch(ptrs)
for ...:
x = tl.load(ptrs) # becomes expensive if evicted
...
pattern_after: |
# Keep original load placement OR stage to a closer explicitly-managed buffer (if available)
x = tl.load(ptrs) # load once if it avoids repeated global misses
for ...:
use(x)
expected_speedup: "prevents regressions"
applies_to:
- cache_sensitive
- small_cache_targets
- very_large_working_sets
- id: mem_atomic_relaxed_for_accumulation
name: "Use sem='relaxed' for commutative atomic accumulation"
stage: memory_access
description: "Relaxed semantics for atomic_add when ordering doesn't matter"
rationale: |
When multiple programs accumulate partial sums into the same output
(e.g., Stream K partial tiles), the order of additions doesn't matter.
Using sem='relaxed' avoids unnecessary memory ordering overhead.
pattern_after: |
tl.atomic_add(c_ptr_, acc, mask=mask, sem='relaxed')
expected_speedup: "avoids synchronization overhead vs stricter semantics"
applies_to: [stream_k, split_accumulation, reduction]
- id: mem_atomic_fallback_from_descriptors
name: "Fall back from descriptors/block pointers to manual pointers for atomic stores"
stage: memory_access
description: "Use manual pointer arithmetic when atomic operations are needed"
rationale: |
Tensor descriptors (desc.store) and block pointer stores do not support
atomic operations. When partial-tile accumulation requires tl.atomic_add,
you must compute pointers manually. This is the standard pattern for
Stream K partial tiles.
pattern_before: |
# Cannot do this:
c_desc.atomic_add(...) # NOT SUPPORTED
pattern_after: |
rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
rn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
c_ptr_ = c_ptr + rm[:, None] * stride_cm + rn[None, :] * stride_cn
mask = (rm < M)[:, None] & (rn < N)[None, :]
tl.atomic_add(c_ptr_, acc, mask=mask, sem='relaxed')
expected_speedup: "N/A (required fallback when atomics are needed)"
applies_to: [stream_k, partial_tiles, split_accumulation]# Optimization Levels for Iterative Kernel Development
#
# Use this framework when optimizing a kernel. Don't stop at Level 1 —
# check the "try harder" decision tree to decide whether deeper optimization
# is warranted. Most production kernels should reach at least Level 2.
#
# Case study: kernel #39 (Gemm_Scale_BatchNorm)
# Level 1 alone: 2.69x speedup (tensor descriptors, tile swizzling, fused epilogue)
# Level 2 + 3: 5.28x speedup (bf16 pre-pack, BN folded into weights)
# See: test_kernels/39_Gemm_Scale_BatchNorm_triton.py
levels:
- id: level_1_baseline_xpu
name: "Level 1: Baseline XPU Optimizations"
description: |
Get the kernel working with standard XPU patterns. This is the minimum
viable optimization — necessary but rarely sufficient for production.
checklist:
- Tensor descriptors (preferred on XPU) or block pointers (not manual pointer arithmetic)
- bf16/fp16 dot inputs, fp32 accumulator
- 1D grid with tile swizzling (GROUP_SIZE_M) for GEMM kernels
- "@triton.autotune with XPU-optimized configs"
- Pre-packed weight transpose cached in _pack_weights()
- Fused light epilogues (bias, simple activations)
- Correct boundary_check=(0, 1) for block pointers (tensor descriptors handle boundaries internally); no default block params
typical_speedup: "1.5-3x vs PyTorch"
when_done: "Move to Level 2 — there is almost always more to gain."
- id: level_2_bandwidth_reduction
name: "Level 2: Bandwidth Reduction"
description: |
Reduce memory bandwidth consumption — the most common bottleneck after
Level 1. Loading fp32 data and converting in-kernel wastes 2x bandwidth.
checklist:
- "Pre-pack weights to bf16 at pack time (not in-kernel conversion)"
- "Pre-convert inputs to bf16 before kernel launch: x.to(device, torch.bfloat16)"
- "Use grf_mode='256' for large register file (XPU-specific)"
- "Use tl.dot(a, b, acc=acc) fused accumulate pattern"
- "Keep bias in fp32 (small, precision-sensitive)"
code_example: |
# BEFORE (Level 1 — wastes 2x bandwidth):
self.weight_t = w.to(device).t().contiguous() # fp32 on device
# kernel: a = tl.load(ptr).to(tl.bfloat16) # loads 4B, uses 2B
# AFTER (Level 2 — halves K-loop memory traffic):
self.weight_t = w.to(device).t().contiguous().to(torch.bfloat16) # bf16
# kernel: a = tl.load(ptr) # loads 2B directly
typical_speedup: "2-4x vs PyTorch"
when_done: |
Check Level 3 — if the epilogue contains linear transforms (BN, scale,
affine), they can often be eliminated entirely.
- id: level_3_algebraic_fusion
name: "Level 3: Algebraic Fusion"
description: |
Eliminate epilogue work by algebraically folding linear transforms into
GEMM weights at pack time. This is the highest-impact optimization for
kernels with BatchNorm, per-channel scaling, or affine transforms.
Key insight: BatchNorm inference is a per-channel linear operation:
BN(y) = gamma * (y - mean) / sqrt(var + eps) + beta
When the GEMM output feeds into BN (possibly through scaling), the entire
chain can be reduced to a single GEMM + bias:
alpha_n = gamma_n * scale_n / sqrt(var_n + eps)
W_fused[n,:] = alpha_n * W[n,:]
b_fused[n] = alpha_n * bias_n - gamma_n * mean_n / sqrt(var_n + eps) + beta_n
checklist:
- Identify linear per-channel transforms in the epilogue
(BatchNorm inference, scaling, affine, LayerNorm with fixed stats)
- Derive fused weight and bias formulas algebraically (on paper first)
- Implement folding in _pack_weights() (one-time cost, not in hot path)
- Verify numerical equivalence (bf16 folding introduces small diffs)
- The kernel becomes a pure GEMM + bias — maximum compute efficiency
applies_when:
- "BatchNorm in inference mode (running_mean/var are fixed)"
- "Per-channel scaling (multiply by [N] vector)"
- "Any per-channel affine transform (y = a*x + b where a,b are [N] vectors)"
does_not_apply_when:
- "BatchNorm in training mode (statistics are computed per-batch)"
- "Non-linear epilogues (sigmoid, ReLU, etc.) — these cannot be folded"
- "Operations that depend on the M dimension (row-wise reductions)"
typical_speedup: "3-6x vs PyTorch"
when_done: |
For most kernels, this is sufficient. Move to Level 4 only for
critical-path kernels where profiling shows remaining bottlenecks.
example:
file: test_kernels/39_Gemm_Scale_BatchNorm_triton.py
description: |
Kernel #39 folds Linear + Scale + BatchNorm into a pure GEMM.
Before (Level 1): 6 vector loads + arithmetic in epilogue → 2.69x
After (Level 3): zero epilogue, pure GEMM + bias → 5.28x
- id: level_4_expert
name: "Level 4: Expert Techniques"
description: |
Advanced patterns for squeezing the last 10-30% of performance.
Only pursue these for critical-path kernels after profiling confirms
the bottleneck. These techniques increase code complexity significantly.
checklist:
- Stream K decomposition (for non-square or non-tile-divisible GEMMs)
- Persistent kernels (fixed program count, iterate over tiles)
- Hardware capability queries (gpu_subslice_count for program count)
- Warp size sweeping (16 vs 32 on XPU)
- Shape-specific specialization (skip boundary checks when M % BLOCK_M == 0)
- Atomic partial tile accumulation
- Adaptive grid layouts (small vs large workloads)
typical_speedup: "5-10x+ vs PyTorch (shape-dependent)"
when_done: "Benchmark shows no further improvement — try a fundamentally different approach."
reference_files:
- kb/persistent_kernel_patterns.yaml
- kb/examples/stream_k_gemm_descriptors.py
# ============================================================================
# "Try Harder" Decision Tree
# ============================================================================
# Use this after completing Level 1 to decide whether to keep optimizing.
try_harder:
description: |
After each optimization level, measure speedup and consult this tree.
The biggest gains typically come from Level 2 (bandwidth) and Level 3
(algebraic fusion). Level 4 has diminishing returns for most workloads.
decisions:
- condition: "Speedup < 2x after Level 1"
diagnosis: "Likely bandwidth-bound — loading fp32 and converting wastes 2x BW"
action: "Apply Level 2: pre-pack to bf16, add grf_mode='large'. Run 'python skills/xpu_profiler.py' to confirm bandwidth bottleneck (look for high XVE Stalled %)."
priority: high
- condition: "Speedup 2-3x after Level 2"
diagnosis: "Epilogue may be adding unnecessary work"
action: |
Check: does the epilogue contain only linear per-channel transforms?
If yes → Apply Level 3: fold into weights algebraically.
If no (non-linear ops like sigmoid, tanh) → the epilogue is already minimal.
Consider Level 4 techniques or accept current speedup.
priority: high
- condition: "Speedup 3-5x after Level 3"
diagnosis: "Good performance — likely near hardware limits for this shape"
action: |
Run 'python skills/xpu_profiler.py' to identify remaining bottleneck.
If compute-bound (XVE Active > Stalled) → try larger tiles or Stream K.
If memory-bound (XVE Stalled > Active) → check for unnecessary copies or layout transforms.
Keep going — try a different approach if current strategy has plateaued.
priority: medium
- condition: "Speedup > 5x"
diagnosis: "Excellent — diminishing returns ahead"
action: "Stop unless this kernel is on the critical path. Focus effort elsewhere."
priority: low
- condition: "Speedup < 1x (slower than PyTorch)"
diagnosis: "Something is fundamentally wrong"
action: |
Check for: fp64 usage, N-loop serialization, missing tile swizzling,
very small tiles, or replacing a fast vendor GEMM with a slow custom one.
Re-read kb/correctness.yaml and kb/fusion_patterns.yaml constraints.
priority: critical
Optimization Strategies Reference
Optimization Levels (Iterative Deepening)
| Level | Focus | Typical Speedup |
|---|---|---|
| 1. Baseline XPU | Tensor descriptors, tile swizzling, @triton.autotune, fused epilogue | 1.5-3x |
| 2. Bandwidth | Pre-pack to bf16, grf_mode='256', tl.dot(a, b, acc=acc) | 2-4x |
| 3. Algebraic | Fold BN/scale/affine into weights (eliminate epilogue) | 3-6x |
| 4. Expert | Stream K, persistent kernels, warp sweeping | 5-10x+ |
"Try harder" decision tree (from references/optimization_levels.yaml):
- Speedup < 2x after Level 1 -> apply Level 2 (bandwidth is the bottleneck)
- Speedup 2-3x after Level 2 -> check Level 3 (can epilogue be algebraically eliminated?)
- Speedup 3-5x -> good for most workloads; Level 4 only for critical-path kernels
- Speedup > 5x -> diminishing returns, stop
Case study: Kernel #39 (Gemm_Scale_BatchNorm) went from 2.69x (Level 1) to 5.28x (Level 2+3) by pre-packing to bf16 and folding BN into GEMM weights.
GEMM Kernels
1. Use tensor descriptors (preferred on XPU) or block pointers (not manual pointer arithmetic) 2. Apply tile swizzling with GROUP_SIZE_M (1D grid required) 3. @triton.autotune with varied configs - sweep block sizes, warps, GRF mode 4. Large tiles for square matrices: 256x256, 32 warps, grf_mode='256' 5. Smaller tiles for skinny-M: BLOCK_M in {32, 64}, fewer warps 6. Mixed precision: bf16/fp16 inputs, fp32 accumulator 7. Pre-pack weight transposes: weight_t = weight.t().contiguous() once in _pack_weights() 8. Pre-pack to bf16: Convert weights AND inputs to bf16 before kernel launch (not in-kernel) - see references/dtype_optimizations.yaml 9. Algebraic weight folding: Fold BN/scale/affine into GEMM weights at pack time - see references/fusion_patterns.yaml
Fusion
1. Fuse light epilogues: bias + simple activation (ReLU, SiLU) 2. Be cautious with heavy chains: multiple exp/tanh/clamp can hurt register pressure 3. Split GEMM + reduction: Use 2D GEMM -> separate reduction kernel (don't serialize over N)
Reductions (Softmax, LayerNorm)
1. Multi-row tiling: Process multiple rows per program (BLOCK_SIZE_Y) 2. Query hardware limits: Use max_work_group_size to compute BLOCK_SIZE_Y 3. Power-of-2 blocks: BLOCK_SIZE_X = triton.next_power_of_2(n_cols) 4. Sweep warp_size: Try both 16 and 32 with different num_warps
Critical "DO NOT" List
- Do NOT put default values on
@triton.autotunemeta-parameters in kernel signature - Do NOT use 2D grid with tile swizzling (must be 1D)
- Do NOT repack weights inside forward() hot path
- Do NOT implement GEMM2 by looping all N tiles inside one program
- Do NOT mix block pointer and tensor descriptor APIs on same load/store
- Do NOT use fp64 unless absolutely required (5-10x slower)
KB Quick Index
- Starting a GEMM kernel? ->
references/xpu_optimizations.yaml - Fusing operations? ->
references/fusion_patterns.yaml - Memory access issues? ->
references/memory_patterns.yaml - Kernel crashes or wrong results? ->
references/correctness.yaml - Slow due to fp64? ->
references/dtype_optimizations.yaml - Advanced techniques? ->
references/persistent_kernel_patterns.yaml - Need more speedup? ->
references/optimization_levels.yaml - Looking for examples? ->
references/examples/index.yaml+references/examples/*.py
Common Patterns Checklist
When transforming PyTorch -> Triton:
- [ ] Identified operation type (GEMM, reduction, elementwise)
- [ ] Chosen memory access pattern (tensor descriptors preferred; block pointers as fallback)
- [ ] Applied tile swizzling (if GEMM)
- [ ]
@triton.autotunewith varied BLOCK_M/N/K, num_warps, grf_mode configs - [ ] NO default values on autotune meta-parameters in kernel signature
- [ ] Used 1D grid if swizzling
- [ ] Mixed precision: bf16/fp16 -> fp32 accumulator
- [ ] Fused light epilogues only
- [ ] Pre-packed weight transposes (cached in
_pack_weights()) - [ ] Model class compatible with ai-bench (standard nn.Module with nn.Linear)
- [ ] Matched
get_inputs(),get_init_inputs(), module-level constants from *_pytorch.py - [ ] Triton file name matches base kernel name (for spec YAML auto-detection)
- [ ] Validated with
python scripts/validate_triton.py <triton_file> - [ ] Benchmarked with
python scripts/benchmark.py <pytorch_file> <triton_file>
# Intel XPU Persistent Kernel & Stream K Patterns
# Stage: persistent_kernel, stream_k
#
# Persistent kernels are ADVANCED and should NOT be auto-applied by default.
# They help mainly when:
# - there are many tiles (launch/tail overhead),
# - the workload is bandwidth/reduction bound,
# - cache reuse across tiles is valuable,
# and they do NOT replace a faster vendor primitive (e.g., vendor GEMM).
#
# Stream K is a related advanced scheduling strategy that splits tiles'
# K-loop iterations evenly across programs to fix quantization inefficiency.
patterns:
- id: persistent_kernel_basic_tile_loop
name: "Persistent kernel: fixed program count loops over tiles"
stage: persistent_kernel
description: "Launch a fixed number of programs and iterate over all tiles with stride = NUM_PROGS"
rationale: |
Persistent kernels reduce kernel launch overhead and can improve cache reuse by having
fewer programs loop over many tiles. This can help Intel XPU when the baseline launches
huge grids (many tiles) and suffers from tail effects or overhead.
WARNING:
- Persistent kernels can increase GRF/register pressure and reduce occupancy.
- Do NOT use by default. Use only when gated conditions indicate it.
pattern_before: |
# Standard per-tile launch
grid = (tl.cdiv(M, BLOCK_M), tl.cdiv(N, BLOCK_N))
@triton.jit
def kernel(...):
pid_m = tl.program_id(0)
pid_n = tl.program_id(1)
# Process one tile
...
pattern_after: |
# Persistent kernel launch (1D grid)
# NUM_PROGS is a tuned constant (do NOT assume it equals SM/EU count)
grid = (NUM_PROGS,)
@triton.jit
def kernel(..., NUM_PROGS: tl.constexpr):
pid = tl.program_id(0)
tiles_m = tl.cdiv(M, BLOCK_M)
tiles_n = tl.cdiv(N, BLOCK_N)
num_tiles = tiles_m * tiles_n
tile = pid
while tile < num_tiles:
pid_m = tile // tiles_n
pid_n = tile % tiles_n
row_start = pid_m * BLOCK_M
col_start = pid_n * BLOCK_N
# Process tile (GEMM / reduction / etc.)
...
tile += NUM_PROGS
expected_speedup: "0.8-2.0x (highly shape/workload dependent; can regress)"
applies_to:
- gemm
- attention
- softmax
- layernorm
- large_workloads
- id: persistent_kernel_autotuned_num_progs
name: "Persistent kernel: autotune NUM_PROGS (required)"
stage: persistent_kernel
description: "Treat NUM_PROGS as a tunable meta-parameter instead of guessing hardware counts"
rationale: |
Intel XPU does not map cleanly to CUDA's SM model. Do not set NUM_PROGS = 'num_sm'.
Instead, autotune NUM_PROGS across a small set (e.g., 32/64/128/256) to find the best.
pattern_before: |
NUM_PROGS = gpu_sm_count # WRONG: not portable / not reliable on XPU
grid = (NUM_PROGS,)
pattern_after: |
@triton.autotune(
configs=[
triton.Config({"NUM_PROGS": 32, "grf_mode": "256"}, num_warps=4, num_stages=2),
triton.Config({"NUM_PROGS": 64, "grf_mode": "256"}, num_warps=4, num_stages=2),
triton.Config({"NUM_PROGS": 128, "grf_mode": "256"}, num_warps=4, num_stages=2),
triton.Config({"NUM_PROGS": 256, "grf_mode": "256"}, num_warps=4, num_stages=2),
],
key=["M", "N", "K"],
)
@triton.jit
def kernel(..., NUM_PROGS: tl.constexpr):
...
# Launch with 1D grid:
grid = lambda META: (META["NUM_PROGS"],)
expected_speedup: "prevents regressions; enables win when persistent is appropriate"
applies_to:
- gemm
- attention
- large_workloads
- id: streamk_two_wave_decomposition
name: "Stream K: two-wave SK + data-parallel decomposition"
stage: stream_k
description: "Split tiles into stream-K (wave 1) and standard blocking (wave 2)"
rationale: |
Pure stream-K distributes ALL tiles via iteration splitting, maximizing
atomic contention. A hybrid two-wave approach is more efficient:
Wave 1 (first_wave): Only "remainder" tiles that cause quantization
inefficiency. Programs share K-loop iterations, using atomic_add
for partial results. Program count = num_xe_core (subslice count).
Wave 2 (full_tiles): Remaining tiles are 1:1 (standard GEMM tiling),
no atomics needed. Grid size = blocking_tiles.
This minimizes atomic traffic while fixing utilization gaps.
pattern_after: |
num_xe_core = torch.xpu.get_device_capability(0)['gpu_subslice_count']
streamk_programs = num_xe_core
total_tiles = num_block_m * num_block_n
iters_per_tile = triton.cdiv(K, BLOCK_SIZE_K)
# Two-tile SK + DP heuristic
streamk_tiles = total_tiles % streamk_programs
if total_tiles - streamk_tiles > streamk_programs:
streamk_tiles += streamk_programs
blocking_tiles = total_tiles - streamk_tiles
streamk_iters = streamk_tiles * iters_per_tile
streamk_full_tiles = streamk_iters // streamk_programs
streamk_partial_tiles = streamk_iters % streamk_programs
# Wave 1: stream-K (fixed program count, iteration-split)
first_wave[(streamk_programs,)](a, b, c, ...,
streamk_full_tiles, streamk_partial_tiles, iters_per_tile)
# Wave 2: standard 1:1 tiling (offset by streamk_tiles)
full_tiles[(blocking_tiles,)](a, b, c, ..., streamk_tiles)
expected_speedup: "significant for non-divisible tile counts; minimal for well-fitting shapes"
applies_to: [gemm, large_workloads, variable_shapes]
notes: |
- Requires output to be pre-zeroed (torch.zeros) — see correctness.yaml
- Wave 1 uses atomic_add for partial tiles only; full tiles use direct store
- Wave 2 tile_ids offset by streamk_tiles to avoid overlap with wave 1
- id: streamk_mac_loop_partial_tile
name: "Stream K: MAC loop with arbitrary iteration range"
stage: stream_k
description: "Inner multiply-accumulate loop that handles [start_iter, end_iter) within a tile"
rationale: |
In stream-K, a program may own only a subset of a tile's K iterations.
The MAC loop must:
1. Derive tile_id from start_iter (tile_id = start_iter // iters_per_tile)
2. Compute K offset (remain_iters * BLOCK_SIZE_K)
3. Accumulate over the assigned range
4. Store via descriptor (full tile) or atomic_add (partial tile)
pattern_after: |
tile_id = start_iter // iters_per_tile
remain_iters = start_iter % iters_per_tile
pid_m, pid_n = swizzle_tile(tile_id, ...)
acc = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
off_k = remain_iters * BLOCK_SIZE_K
for _ in range(start_iter, end_iter):
a = a_desc.load([pid_m * BLOCK_SIZE_M, off_k])
b = b_desc.load([off_k, pid_n * BLOCK_SIZE_N])
acc += tl.dot(a, b)
off_k += BLOCK_SIZE_K
# Full tile → direct store; partial tile → atomic add
if remain_iters == 0 and end_iter % iters_per_tile == 0:
c_desc.store([pid_m * BLOCK_SIZE_M, pid_n * BLOCK_SIZE_N], acc)
else:
rm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
rn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
c_ptr_ = c_ptr + rm[:, None] * stride_cm + rn[None, :] * stride_cn
mask = (rm < M)[:, None] & (rn < N)[None, :]
tl.atomic_add(c_ptr_, acc, mask=mask, sem='relaxed')
applies_to: [gemm, stream_k]
- id: streamk_iteration_distribution
name: "Stream K: even iteration distribution (floor + remainder)"
stage: stream_k
description: "Distribute total stream-K iterations across programs with even split"
rationale: |
Each program gets floor(total_iters / num_programs) iterations, plus
one extra if pid < remainder. Programs iterate through their range,
snapping to tile boundaries so each mac_loop stays within one tile.
pattern_after: |
pid = tl.program_id(axis=0)
start_iter = pid * full_tiles + tl.minimum(pid, partial_tiles)
last_iter = (pid + 1) * full_tiles + tl.minimum(pid + 1, partial_tiles)
while start_iter < last_iter:
end_iter = start_iter + (iters_per_tile - start_iter % iters_per_tile)
end_iter = tl.minimum(end_iter, last_iter)
mac_loop(..., start_iter, end_iter, ...)
start_iter = end_iter
applies_to: [stream_k]
notes: |
- The while loop handles programs that span multiple tiles
- end_iter snaps to tile boundaries within the assigned rangeDetailed Workflow Reference
Analysis Phase
When given a PyTorch kernel (typically *_pytorch.py, but can be any user-specified path):
Note: The existingtest_kernels/*.pyTriton files (non-pytorch) are naive, unoptimized baselines. Do NOT treat them as examples of good Triton code. Usereferences/implementation_reference.mdandreferences/examples/instead.
1. Parse the PyTorch code to identify:
- Input/output shapes and dtypes
- Mathematical operations (matmul, activations, reductions)
- Operation fusion opportunities
- Memory access patterns
2. Consult the knowledge base (references/ directory):
xpu_optimizations.yaml: XPU-specific patterns (tensor descriptors, GRF mode, warp count, tile swizzling)fusion_patterns.yaml: When to fuse operationsmemory_patterns.yaml: Memory access best practicescorrectness.yaml: Critical constraints to avoid bugsdtype_optimizations.yaml: Data type choices
3. Use the skills to help:
python scripts/analyze_kernel.py <pytorch_file>- Extract operation structure- Review
references/examples/index.yamlfor similar patterns
Design Phase
1. Identify the kernel type: Pure GEMM, GEMM + epilogue, GEMM + reduction, complex fusion 2. Select optimization strategies from KB (memory, tiling, parallelism, fusion, dtypes) 3. Apply critical constraints (from references/correctness.yaml and references/xpu_optimizations.yaml):
- NO default values for
@triton.autotunemeta-parameters in kernel signature - Use 1D grid when applying tile swizzling (GROUP_SIZE_M)
- boundary_check uses dimension indices (0, 1), not booleans
- Cast batch indices to int64 before stride multiplication
- Do NOT mix block pointer and tensor descriptor APIs on same operation
- Pre-zero output buffers when using atomic accumulation
- Model class must be compatible with ai-bench (standard
nn.Modulewithnn.Linear)
Trial Loop Detail
For each trial:
a. Implement / Modify Kernel
Start from a template (references/implementation_reference.md) or modify the previous trial's code. See references/implementation_reference.md.
b. Validate Syntax
python scripts/validate_triton.py <triton_file>If validation fails, fix and retry - doesn't count as a new trial. Note: <triton_file> should be t<trial_id>.py.
c. Save Trial
python scripts/trial_manager.py save <kernel_name> <triton_file> --parent <parent_id> --strategy "description"For the first trial, omit --parent.
d. Benchmark
# Trial t0 — measures both baseline and triton:
python scripts/benchmark.py <baseline_file> <triton_file> [--triton-baseline]
# Trials t1+ — use cached baseline to save time:
python scripts/trial_manager.py baseline-us <kernel_name> # get cached value
python scripts/benchmark.py <baseline_file> <triton_file> [--triton-baseline] --baseline-us <cached_value>
# After finalize — re-run without --baseline-us for final accurate comparisone. Record Results
python scripts/trial_manager.py result <kernel_name> <trial_id> \
--validation pass --correctness <pass|fail> --speedup <float> \
--baseline_us <float> --triton_us <float>f. Decision Tree
| Condition | Action |
|---|---|
| Speedup > 5x | Stop - excellent result (the only valid early stop) |
| Speedup improved | Continue on this branch, try next optimization level |
| Speedup regressed | Branch back to best trial, try a different strategy |
| Correctness failed | Fix code on same branch |
| After t1 (if `vtune_enabled`) | Run python scripts/xpu_profiler.py <triton_file> — mandatory first profile |
| Speedup plateaued after 2+ more trials | Run profiler again (if vtune_enabled); try a fundamentally different approach |
| Plateau / diminishing returns | Do NOT stop. Try a fundamentally different approach (different algorithm, tiling, fusion strategy). LLM sampling can discover new ideas at any point. |
| Max trials reached | Stop — must run all max_trials from config.yaml |
g. Check Status
python scripts/trial_manager.py status <kernel_name>
python scripts/trial_manager.py best <kernel_name>Trial Manager Commands Reference
python scripts/trial_manager.py init <kernel_name> <baseline_file> [--triton-baseline]
python scripts/trial_manager.py save <kernel_name> <file> [--parent <parent_id>] [--strategy "..."]
python scripts/trial_manager.py result <kernel_name> <trial_id> [--validation pass] [--correctness pass] [--speedup 3.2] [--baseline_us 150.0] [--triton_us 47.0]
python scripts/trial_manager.py status <kernel_name>
python scripts/trial_manager.py best <kernel_name>
python scripts/trial_manager.py baseline-us <kernel_name>
python scripts/trial_manager.py finalize <kernel_name> <name>_triton.pyBenchmarking Details
scripts/benchmark.py uses ai-bench (modules/ai-bench/) for both correctness and performance:
1. Correctness - Compares outputs between PyTorch and Triton implementations
- Uses
check_correctness()with per-variant tolerances from YAML spec (defaults: rtol=1e-2, atol=1e-5) - Syncs model weights via
copy_model_weights() - Falls back to direct module loading when no spec file is available
2. Performance - Benchmarks both implementations on XPU hardware
- Reads the YAML spec file (auto-detected from
modules/ai-bench/problems/specs/KernelBench/level*/) - Reports speedup metrics (Triton vs PyTorch) per spec variant
Both checks must pass for the kernel to be considered complete.
Setup: External tools must be initialised: git submodule update --init
Profiling with VTune (scripts/xpu_profiler.py)
python scripts/xpu_profiler.py <triton_file> [--warmup 5] [--iters 20]Runs Intel VTune gpu-offload collection to capture both Level Zero API tasks and OA (Observation Architecture) hardware counters, then maps bottlenecks to KB optimization patterns.
Prerequisite: OA counters require observation_paranoid=0:
echo 0 | sudo tee /proc/sys/dev/xe/observation_paranoidWhen to Profile
- MANDATORY after the first benchmarked trial (t1) — always run at least once per session
- Run again if speedup plateaus after 2+ additional trials
- You're unsure which optimization level to try next
What It Reports
1. Platform info: GPU name, XVE count, max frequency 2. Host tasks: CPU-side overhead (JIT compilation, data copies, synchronization) 3. GPU computing tasks table (per-kernel): Time, instance count, XVE Active/Stalled/Idle %, occupancy %, memory bandwidth read/write 4. Primary kernel detail: Full OA hardware counter breakdown including:
- XVE execution: Active/Stalled/Idle percentages
- Occupancy limiters: Work Size Limit, SLM Use Limit, Barriers Use Limit (tells WHY occupancy is low)
- Memory bandwidth: Read/Write GB/s
- Cache hierarchy: L3 Busy/Stalled %, L3 Miss Ratio, LSC Miss Ratio, LSC→L3 Miss Ratio
- Register spill size, SLM bank conflicts, TLB misses
5. Optimization recommendations: Each grounded in a specific KB pattern:
- XVE Stalled > Active → memory bound →
references/xpu_optimizations.yaml (xpu_descriptor_gemm_pattern)+references/optimization_levels.yaml (level_2) - Low occupancy + Work Size limiter → grid too small →
references/xpu_optimizations.yaml (xpu_tile_swizzling) - Low occupancy + SLM limiter → tile too large →
references/xpu_optimizations.yaml (xpu_grf_mode) - High L3 Miss → poor reuse →
references/xpu_optimizations.yaml (xpu_descriptor_gemm_pattern, xpu_tile_swizzling) - Register spill > 0 → reduce liveness →
references/memory_patterns.yaml (reduce_liveness_sink_load_and_prefetch) - Overhead kernels dominate → pre-pack to bf16 →
references/optimization_levels.yaml (level_2_bandwidth_reduction) - Host time >> GPU time → sync in hot path →
references/memory_patterns.yaml (no_device_to_host_scalar_sync)
How to Use the Output
The profiler prints specific recommendations with references:
>> XVE Stalled (72%) > Active (28%): memory/dependency bound.
Use tensor descriptors for better address codegen, pre-pack to bf16 to halve bandwidth.
Reference: references/xpu_optimizations.yaml (xpu_descriptor_gemm_pattern, xpu_tile_swizzling) +
references/optimization_levels.yaml (level_2_bandwidth_reduction)Read the referenced file and apply the suggested pattern in your next trial.
Validation Details
scripts/validate_triton.py checks:
- Syntax correctness
- Autotune config issues (no default params)
- Grid/swizzling consistency
- boundary_check format
- Data type usage
Project Structure
xpu-kernels/
├── SKILL.md # Core rules and workflow (concise)
│
├── references/ # Knowledge base
│ ├── implementation_reference.md # Templates, code patterns, Model class
│ ├── optimization_strategies.md # Strategy reference, checklist, KB index
│ ├── workflow_details.md # This file — detailed workflow
│ ├── correctness.yaml # Correctness constraints
│ ├── xpu_optimizations.yaml # XPU-specific patterns
│ ├── optimization_levels.yaml # Progressive optimization checklist
│ ├── fusion_patterns.yaml # Kernel fusion guidelines
│ ├── memory_patterns.yaml # Memory access optimizations
│ ├── dtype_optimizations.yaml # Data type optimizations
│ └── persistent_kernel_patterns.yaml # Stream K and persistent kernel patterns
│
└── scripts/ # Standalone tools (DO NOT recreate)
├── analyze_kernel.py # PyTorch → operations, shapes, fusion opportunities
├── validate_triton.py # Syntax + constraint checks before benchmarking
├── benchmark.py # Correctness + performance via ai-bench
├── trial_manager.py # Tree-structured trial init/save/record/finalize
├── xpu_profiler.py # VTune GPU hardware counters + recommendations
├── config.yaml # max_trials, vtune_enabled, vtune_bin
└── config.py # Shared configuration loader for config.yaml#!/usr/bin/env python3
"""
Analyze PyTorch kernel to extract structure and guide Triton optimization.
Usage:
python scripts/analyze_kernel.py <pytorch_file>
"""
import ast
import re
import sys
from pathlib import Path
from typing import Dict, List, Set, Tuple
class KernelAnalyzer(ast.NodeVisitor):
"""AST visitor to analyze PyTorch model operations."""
def __init__(self):
self.operations = []
self.shapes = {}
self.dtypes = set()
self.has_matmul = False
self.has_linear = False
self.activations = []
self.reductions = []
self.elementwise = []
def visit_Call(self, node):
"""Visit function calls to identify operations."""
# torch.matmul
if isinstance(node.func, ast.Attribute):
if hasattr(node.func.value, "id") and node.func.value.id == "torch":
op_name = node.func.attr
self.operations.append(op_name)
if op_name == "matmul":
self.has_matmul = True
elif op_name in ["sum", "mean", "max", "min"]:
self.reductions.append(op_name)
elif op_name in ["sigmoid", "tanh", "relu", "gelu", "silu"]:
self.activations.append(op_name)
elif op_name == "clamp":
self.elementwise.append("clamp")
# torch.nn.functional
elif hasattr(node.func.value, "attr"):
if node.func.value.attr == "functional":
op_name = node.func.attr
self.operations.append(f"F.{op_name}")
if op_name in ["gelu", "relu", "silu", "softmax", "sigmoid"]:
self.activations.append(op_name)
self.generic_visit(node)
def visit_BinOp(self, node):
"""Visit binary operations (*, /, +, -)."""
op_map = {
ast.Mult: "multiply",
ast.Div: "divide",
ast.Add: "add",
ast.Sub: "subtract",
}
op_type = type(node.op)
if op_type in op_map:
self.elementwise.append(op_map[op_type])
self.generic_visit(node)
def visit_Assign(self, node):
"""Visit assignments to track nn.Linear."""
if isinstance(node.value, ast.Call):
if hasattr(node.value.func, "attr") and node.value.func.attr == "Linear":
self.has_linear = True
self.generic_visit(node)
def analyze_pytorch_kernel(filepath: Path) -> Dict:
"""Analyze PyTorch kernel file and extract optimization hints."""
with open(filepath, "r") as f:
source = f.read()
tree = ast.parse(source)
analyzer = KernelAnalyzer()
analyzer.visit(tree)
# Extract shape information from module-level variables
shapes = {}
for line in source.split("\n"):
if "=" in line and any(
dim in line
for dim in ["batch_size", "in_features", "out_features", "hidden_size", "input_size"]
):
match = re.match(r"(\w+)\s*=\s*(\d+)", line.strip())
if match:
shapes[match.group(1)] = int(match.group(2))
# Determine kernel type
kernel_type = "unknown"
if analyzer.has_matmul or analyzer.has_linear:
if analyzer.activations or analyzer.elementwise:
kernel_type = "gemm_epilogue"
elif analyzer.reductions:
kernel_type = "gemm_reduction"
else:
kernel_type = "gemm"
elif analyzer.reductions:
kernel_type = "reduction"
elif analyzer.elementwise:
kernel_type = "elementwise"
# Fusion analysis
fusion_opportunities = []
if analyzer.has_matmul or analyzer.has_linear:
if len(analyzer.activations) <= 2 and len(analyzer.elementwise) <= 3:
fusion_opportunities.append("Light epilogue fusion (GEMM + activation + elementwise)")
else:
fusion_opportunities.append("Heavy epilogue - consider partial fusion or split")
if analyzer.reductions:
fusion_opportunities.append(
"WARNING: GEMM + reduction - use 2D GEMM then separate reduction kernel"
)
# Memory pattern recommendation
memory_pattern = "block_pointers" # default
if "Stream K" in str(analyzer.operations) or len(analyzer.reductions) > 1:
memory_pattern = "tensor_descriptors"
return {
"kernel_type": kernel_type,
"operations": analyzer.operations,
"activations": analyzer.activations,
"reductions": analyzer.reductions,
"elementwise": analyzer.elementwise,
"shapes": shapes,
"fusion_opportunities": fusion_opportunities,
"memory_pattern": memory_pattern,
"has_gemm": analyzer.has_matmul or analyzer.has_linear,
}
def print_analysis(analysis: Dict, filepath: Path):
"""Pretty print the analysis results."""
print(f"\n{'=' * 70}")
print(f"Analysis: {filepath.name}")
print(f"{'=' * 70}\n")
print(f"Kernel Type: {analysis['kernel_type'].upper()}")
print(f"Memory Pattern: {analysis['memory_pattern']}")
print()
if analysis["shapes"]:
print("Shapes:")
for key, val in analysis["shapes"].items():
print(f" {key}: {val}")
print()
print("Operations:")
print(f" Total: {len(analysis['operations'])}")
if analysis["has_gemm"]:
print(f" ✓ GEMM/Linear")
if analysis["activations"]:
print(f" ✓ Activations: {', '.join(set(analysis['activations']))}")
if analysis["reductions"]:
print(f" ✓ Reductions: {', '.join(set(analysis['reductions']))}")
if analysis["elementwise"]:
print(f" ✓ Elementwise: {', '.join(set(analysis['elementwise']))}")
print()
if analysis["fusion_opportunities"]:
print("Fusion Opportunities:")
for opp in analysis["fusion_opportunities"]:
if "WARNING" in opp:
print(f" ⚠️ {opp}")
else:
print(f" → {opp}")
print()
# Recommendations
print("Recommended Optimizations:")
if analysis["has_gemm"]:
print(" 1. Use tensor descriptors (preferred on XPU) or block pointers")
print(" 2. Apply tile swizzling (GROUP_SIZE_M)")
# Tile size recommendations based on shape
batch_size = analysis["shapes"].get("batch_size", 0)
if batch_size and batch_size < 256:
print(" 3. Use smaller BLOCK_M (32-64) for skinny M")
else:
print(" 3. Try large tiles (256x256) with autotune")
print(" 4. Autotune: num_warps={4,8,16,32}, grf_mode='256'")
print(" 5. Mixed precision: bf16/fp16 inputs, fp32 accumulator")
print(" 6. Pre-pack weight transpose: weight_t = weight.t().contiguous()")
if "sigmoid" in analysis["activations"]:
print(" → Use exp2-based sigmoid (faster on XPU)")
if "tanh" in analysis["activations"]:
print(" → Implement tanh via sigmoid: tanh(x) = 2*sigmoid(2x) - 1")
if "gelu" in analysis["activations"]:
print(" → Use tanh-approximation GeLU with JIT helper")
if analysis["reductions"]:
if analysis["has_gemm"]:
print(" ⚠️ Split GEMM and reduction into separate kernels")
print(" (Don't serialize over N tiles inside one program)")
else:
print(" → Use multi-row tiling for reductions")
print(" → Query max_work_group_size for BLOCK_SIZE_Y")
print()
print("Relevant Reference Files:")
print(" • references/xpu_optimizations.yaml - Core XPU patterns")
if analysis["fusion_opportunities"]:
print(" • references/fusion_patterns.yaml - Fusion guidelines")
print(" • references/memory_patterns.yaml - Memory access patterns")
print(" • references/correctness.yaml - Critical constraints")
print()
# Template suggestion
if analysis["kernel_type"] == "gemm":
print("Suggested Template: See GEMM pattern in references/implementation_reference.md")
elif analysis["kernel_type"] == "gemm_epilogue":
print("Suggested Template: See GEMM with epilogue pattern in references/implementation_reference.md")
elif analysis["kernel_type"] in ["reduction", "gemm_reduction"]:
print("Suggested Template: See reduction pattern in references/implementation_reference.md")
print()
def main():
if len(sys.argv) != 2:
print("Usage: python scripts/analyze_kernel.py <pytorch_file>")
sys.exit(1)
filepath = Path(sys.argv[1])
if not filepath.exists():
print(f"Error: File not found: {filepath}")
sys.exit(1)
analysis = analyze_pytorch_kernel(filepath)
print_analysis(analysis, filepath)
if __name__ == "__main__":
main()
"""Shared config loader — reads config.yaml from project root."""
from pathlib import Path
import yaml
_CONFIG_DIR = Path(__file__).resolve().parent
_DEFAULTS = {
"max_trials": 10,
"vtune_enabled": True,
"vtune_bin": "/bin64/vtune",
}
def load_config() -> dict:
"""Load config.yaml, falling back to defaults for missing keys."""
config_path = _CONFIG_DIR / "config.yaml"
cfg = {}
if config_path.exists():
with open(config_path) as f:
cfg = yaml.safe_load(f) or {}
return {**_DEFAULTS, **cfg}
# Project configuration — edit these values to control optimization sessions.
max_trials: 10 # Maximum number of optimization trials (3-20)
vtune_enabled: true # Set to false to skip VTune profiling entirely
vtune_bin: "/bin64/vtune" # Path to VTune binary
transformers
safetensors
huggingface-hub
kernels
ai-bench[xpu] @ git+https://github.com/libxsmm/AI-bench.git
Related skills
FAQ
What does xpu-kernels do?
xpu-kernels skill documents Provides guidance for writing, optimizing, and benchmarking Triton kernels for Intel XPU GPUs (Battlemage/Arc Pro B50) using the Xe-Forge optimization framework.
When should I use xpu-kernels?
User asks about xpu-kernels, provides guidance for writing, optimizing, and benchmarking triton kernels for intel xpu g.
Is this skill safe to install?
Review the Security Audits panel on this page before installing in production.