Skip to content

Commit 61fa0e7

Browse files
committed
docs(mlx_metal_kernel_opt): rewrite README and remove invalid report
1 parent f41b0b8 commit 61fa0e7

2 files changed

Lines changed: 48 additions & 445 deletions

File tree

Lines changed: 48 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -1,228 +1,77 @@
1-
# OpenEvolve Metal Kernel Optimization: Automated Discovery of Custom GPU Kernels for Transformer Attention
1+
# MLX Metal Kernel Optimization (Qwen3-0.6B-bf16)
22

3-
**Evolutionary Optimization of Apple Silicon Metal Kernels for Grouped Query Attention in Qwen3-0.6B**
3+
This example demonstrates evolutionary optimization of a custom Apple Silicon **Metal** attention kernel using OpenEvolve and MLX’s `metal_kernel` API. The target workload is **Grouped Query Attention (GQA)** for the MLX‑LM model `mlx-community/Qwen3-0.6B-bf16`.
44

5-
## Abstract
5+
## Target
66

7-
This work demonstrates the application of evolutionary code optimization to the automatic discovery of custom Metal GPU kernels for transformer attention mechanisms. Using OpenEvolve, we evolved a specialized Metal kernel for Grouped Query Attention (GQA) in Qwen3-0.6B that leverages Apple Silicon's unified memory architecture and vector processing capabilities. Our approach achieved measurable performance improvements over MLX's highly optimized `scaled_dot_product_attention` baseline across diverse inference workloads, with decode speed improvements averaging 12.5% and reaching up to 106% on specific benchmark tasks.
8-
9-
## 1. Introduction
10-
11-
### 1.1 Motivation
12-
13-
Modern transformer models rely heavily on optimized attention kernels for efficient inference. While frameworks like MLX provide highly optimized implementations, the rapid evolution of hardware architectures creates opportunities for specialized optimizations that general-purpose kernels cannot capture. This work explores whether evolutionary code optimization can automatically discover hardware-specific kernel optimizations that outperform expert-engineered baselines.
14-
15-
### 1.2 Target System
16-
17-
- **Model**: Qwen3-0.6B with Grouped Query Attention (40 query heads : 8 key-value heads)
18-
- **Hardware**: Apple M-series GPUs with unified memory architecture
19-
- **Framework**: MLX with custom Metal kernel integration
7+
- **Model**: `mlx-community/Qwen3-0.6B-bf16`
8+
- **Attention**: GQA **16 query heads : 8 KV heads** (2:1), **head_dim=128**, **hidden_size=2048**
9+
- **Dtype**: `bfloat16` (bf16) by default for this model
2010
- **Baseline**: `mx.fast.scaled_dot_product_attention`
21-
- **Evolution Target**: Metal shader source code implementing GQA attention computation
22-
23-
## 2. Methodology
24-
25-
### 2.1 Evolution Framework
26-
27-
We employ OpenEvolve to automatically optimize the Metal kernel source code responsible for computing attention. The evolutionary process operates on a single code block (EVOLVE-BLOCK) containing approximately 150 lines of Metal C++ shader code while preserving the surrounding MLX integration infrastructure.
28-
29-
**Evolution Configuration**:
30-
- **Population Size**: 25 programs
31-
- **Generations**: 25 iterations
32-
- **Models**: Gemini 2.5 Flash (60%) + Gemini 2.5 Pro (40%)
33-
- **Selection**: Multi-objective optimization balancing performance and correctness
34-
35-
### 2.2 Evaluation Methodology
36-
37-
Each evolved kernel undergoes comprehensive evaluation:
11+
- **Hardware**: Apple Silicon (Metal)
3812

39-
1. **Correctness Validation**: Numerical accuracy verification against MLX baseline
40-
2. **Performance Benchmarking**: 20 diverse inference scenarios covering:
41-
- Short context (16-64 tokens)
42-
- Long context (512-2048 tokens)
43-
- Code generation
44-
- Sustained dialogue
45-
- Technical documentation
46-
- Memory stress tests
13+
## Key files
4714

48-
3. **Safety Validation**: GPU command buffer error detection and Metal memory violation checking
15+
- `initial_program.py`: starting point (contains `create_metal_qwen3_optimization_hook()` and the EVOLVE‑BLOCK)
16+
- `evaluator.py`: correctness + benchmarking + safety checks for candidates
17+
- `qwen3_benchmark_suite.py`: benchmark definitions and subprocess runner
18+
- `mlx_lm_generate_with_hook.py`: wrapper to apply the attention hook **inside** the `mlx_lm.generate` subprocess
19+
- `run_benchmarks.py`: convenience benchmark runner (baseline vs optimized)
20+
- `config.yaml`: OpenEvolve config and optimization prompt
21+
- `run_evolve_experiment.sh`: convenience script for isolated runs (`output_dir` + `db_path`)
4922

50-
### 2.3 Optimization Constraints
23+
## Important: evaluation validity (before vs after)
5124

52-
**Preserved Elements**:
53-
- Kernel function signature and I/O specifications
54-
- Thread grid mapping and bounds checking
55-
- Overall algorithm correctness (attention semantics)
56-
- MLX integration interface
25+
Earlier versions of this example could produce misleading “best program” artifacts and invalid performance comparisons. The main issues and the fixes:
5726

58-
**Optimizable Elements**:
59-
- Memory access patterns and vectorization
60-
- Computation order and algorithmic efficiency
61-
- Apple Silicon specific optimizations
62-
- GQA-specific computation strategies
27+
| Area | Before | After |
28+
|------|--------|-------|
29+
| **Subprocess benchmark hook** | Benchmarks ran `python -m mlx_lm.generate ...` via `subprocess.run(...)`, so any monkey‑patch in the parent process was **not applied** in the child process (baseline and “optimized” could run the same attention). | Benchmarks can run via `mlx_lm_generate_with_hook.py --hook-program ...` so the patch is applied **inside the subprocess**. |
30+
| **bf16 correctness** | Correctness used `float32` inputs; candidates could pass tests but fail in real bf16 inference (Metal compilation/runtime errors). | Correctness covers **bf16**, and deterministic Metal compilation errors are treated as normal candidate failures. |
31+
| **Architecture alignment** | Docs/prompt/MockArgs assumed **40:8** heads and **hidden_size=5120** (incorrect for Qwen3‑0.6B). | Docs/prompt/MockArgs aligned to **16:8** and **hidden_size=2048**. |
6332

64-
## 3. Technical Contributions
33+
Because of these fixes, we intentionally avoid hard-coded performance claims here. **Rerun the benchmarks on your own machine** and record results in your environment.
6534

66-
### 3.1 Discovered Optimizations
35+
## Run evolution
6736

68-
The evolutionary process discovered several key optimizations:
37+
From this directory:
6938

70-
#### 3.1.1 Enhanced Vectorization
71-
```metal
72-
// Original: Scalar operations
73-
for (uint d = 0; d < HEAD_DIM; d++) {
74-
score += query_vec[d] * keys[k_base + d];
75-
}
76-
77-
// Evolved: Vector operations with optimal width
78-
vec<T, 8> query_vec_v[HEAD_DIM / 8]; // 16 vectors for 128-dim heads
79-
for (uint d_vec = 0; d_vec < HEAD_DIM / 8; d_vec++) {
80-
score += dot(query_vec_v[d_vec], ((device vec<T, 8>*)(keys + k_base))[d_vec]);
81-
}
39+
```bash
40+
export OPENAI_API_KEY="..." # or set GEMINI_API_KEY; see the runner script
41+
bash run_evolve_experiment.sh --foreground
8242
```
8343

84-
**Innovation**: Using 8-element vectors perfectly matches Apple Silicon's SIMD capabilities for 128-dimensional attention heads.
85-
86-
#### 3.1.2 Online Softmax Algorithm
87-
```metal
88-
// Pass 1: Find maximum for numerical stability
89-
T max_score = T(-INFINITY);
90-
for (uint key_pos = 0; key_pos < SEQ_LEN; key_pos++) {
91-
T score = compute_attention_score(query_vec, key_vec) * scale_val;
92-
max_score = max(max_score, score);
93-
}
94-
95-
// Pass 2: Combined softmax computation and value accumulation
96-
T sum_exp = T(0.0);
97-
vec<T, 8> output_acc_v[HEAD_DIM / 8];
98-
for (uint key_pos = 0; key_pos < SEQ_LEN; key_pos++) {
99-
T exp_score = exp(current_score - max_score);
100-
sum_exp += exp_score;
101-
// Fused accumulation
102-
output_acc_v[d_vec] += exp_score * ((device vec<T, 8>*)(values + v_base))[d_vec];
103-
}
104-
```
44+
This writes a new `openevolve_output_<timestamp>/` directory containing logs, checkpoints, best programs, and an isolated database.
10545

106-
**Innovation**: Reduced from three-pass to two-pass algorithm, fusing softmax normalization with value accumulation.
46+
If you prefer running the CLI directly:
10747

108-
#### 3.1.3 Memory Access Optimization
109-
```metal
110-
// Pre-computed base indices for coalesced access
111-
const uint q_base = batch_idx * (NUM_HEADS * SEQ_LEN * HEAD_DIM) +
112-
head_idx * (SEQ_LEN * HEAD_DIM) +
113-
query_pos * HEAD_DIM;
114-
const uint kv_head_idx = head_idx / HEADS_PER_KV; // Direct 5:1 mapping
48+
```bash
49+
export OPENAI_API_KEY="..."
50+
python -m openevolve.cli ./initial_program.py ./evaluator.py -c ./config.yaml -o ./openevolve_output
11551
```
11652

117-
**Innovation**: Leverages unified memory bandwidth through coalesced access patterns and direct GQA head mapping.
118-
119-
### 3.2 Apple Silicon Specialization
120-
121-
The evolved kernel exploits specific Apple Silicon features:
122-
- **Unified Memory**: Optimized bandwidth utilization patterns
123-
- **SIMD Width**: 8-element vectors matching GPU vector units
124-
- **Thread Group Size**: 32-thread groups optimal for Apple GPUs
125-
- **Register Allocation**: Balanced computation vs. memory bandwidth
126-
127-
## 4. Experimental Results
128-
129-
### 4.1 Performance Benchmarking
130-
131-
We evaluated the evolved kernel against MLX baseline across 20 comprehensive benchmark scenarios representing real-world inference patterns.
132-
133-
**Aggregate Performance Improvements**:
134-
- **Decode Speed**: +12.5% average improvement (σ = 38.3%)
135-
- **Prefill Speed**: +14.4% average improvement (σ = 17.6%)
136-
- **Total Throughput**: +10.4% average improvement (σ = 30.7%)
137-
- **Memory Usage**: +0.99% average reduction (σ = 1.7%)
138-
139-
### 4.2 Benchmark Category Analysis
140-
141-
| **Category** | **Benchmarks** | **Decode Improvement** | **Notable Results** |
142-
|--------------|----------------|------------------------|-------------------|
143-
| **Short Context** | 2 | -4.6% ± 3.8% | Mixed results on very short sequences |
144-
| **Long Context** | 6 | +8.1% ± 42.1% | High variance, strong improvements in some cases |
145-
| **Code Generation** | 1 | -16.5% | Performance regression |
146-
| **General Tasks** | 9 | +24.8% ± 35.4% | Strongest category with 106% peak improvement |
147-
| **Stress Tests** | 2 | +22.9% ± 31.5% | Robust performance under memory pressure |
53+
## Run benchmarks (baseline vs optimized)
14854

149-
### 4.3 Statistical Analysis
55+
To compare the MLX baseline against the best evolved program:
15056

151-
**Distribution of Improvements**:
152-
- **Significant Gains** (>25%): 7/20 benchmarks
153-
- **Moderate Gains** (5-25%): 3/20 benchmarks
154-
- **Neutral** (±5%): 4/20 benchmarks
155-
- **Regressions** (<-5%): 6/20 benchmarks
156-
157-
**Peak Performance**: Repetitive pattern generation achieved 106% decode speed improvement, demonstrating the kernel's effectiveness for certain workload characteristics.
158-
159-
### 4.4 Correctness Validation
160-
161-
All evolved kernels maintained numerical correctness:
162-
- **Accuracy**: 100% correctness score across all test cases
163-
- **Numerical Stability**: No NaN/Inf values detected
164-
- **Statistical Validation**: Output distributions within expected ranges
165-
- **Functional Equivalence**: Attention semantics preserved
166-
167-
## 5. Discussion
168-
169-
### 5.1 Performance Characteristics
170-
171-
The evolved kernel shows workload-dependent performance characteristics:
172-
173-
**Strengths**:
174-
- **Sustained Generation**: +46.6% improvement on dialogue tasks
175-
- **Long Sequences**: +73.9% improvement on extreme-length generation
176-
- **Memory Efficiency**: Consistent memory usage reduction
177-
178-
**Limitations**:
179-
- **Short Sequences**: Limited improvement due to setup overhead
180-
- **Code Generation**: -16.5% regression suggesting suboptimal patterns for this workload
181-
- **Variance**: High performance variance across different sequence patterns
182-
183-
### 5.2 Technical Insights
184-
185-
**Vectorization Impact**: The discovery of `vec<T, 8>` operations as optimal for 128-dimensional heads represents a significant finding, suggesting that hardware-specific vector widths are crucial for performance.
186-
187-
**Algorithm Innovation**: The two-pass online softmax represents a novel contribution, demonstrating that evolutionary approaches can discover algorithmic improvements beyond simple micro-optimizations.
188-
189-
**GQA Specialization**: Direct exploitation of the 5:1 query-to-KV head ratio through specialized indexing patterns shows the value of architecture-specific optimizations.
190-
191-
### 5.3 Evolutionary Process Analysis
192-
193-
**Convergence**: The system converged to the optimal solution within 25 generations, with significant improvements appearing by generation 10.
194-
195-
**Safety**: Zero Metal kernel compilation errors or GPU command buffer failures across all evolution attempts, demonstrating robust evolutionary constraints.
196-
197-
**Diversity**: The evolutionary process explored multiple optimization strategies including different vectorization patterns, memory layouts, and algorithmic approaches.
198-
199-
## 6. Related Work
200-
201-
This work extends prior research in automated kernel optimization:
202-
203-
- **AlphaTensor** [Fawzi et al., 2022]: Matrix multiplication algorithm discovery
204-
- **TensorIR** [Feng et al., 2023]: Tensor compiler optimization
205-
- **Ansor** [Zheng et al., 2020]: Automated tensor program optimization
206-
207-
Our approach differs by applying evolutionary optimization directly to GPU shader source code rather than higher-level tensor algebra, enabling discovery of hardware-specific optimizations that would be difficult to express in tensor IRs.
57+
```bash
58+
python run_benchmarks.py --mode compare --model mlx-community/Qwen3-0.6B-bf16 --output-dir results
59+
```
20860

209-
## 7. Limitations and Future Work
61+
## How to verify the validity fixes are active
21062

211-
### 7.1 Current Limitations
63+
When the hook is enabled, the optimized path should execute via the wrapper:
21264

213-
- **Workload Specificity**: Performance improvements are highly dependent on sequence patterns
214-
- **Model Scope**: Results specific to Qwen3-0.6B's 40:8 GQA configuration
215-
- **Hardware Scope**: Optimizations specific to Apple Silicon architecture
65+
- `mlx_lm_generate_with_hook.py --hook-program <best_program.py> --model ...`
21666

217-
### 7.2 Future Directions
67+
You can also sanity-check that correctness is exercising bf16 by running evolution on a machine where bf16 Metal compilation errors are expected for invalid kernels: such candidates should be rejected early by correctness gating rather than becoming “best programs”.
21868

219-
- **Multi-Architecture**: Extend to CUDA, ROCm, and other GPU architectures
220-
- **Model Generalization**: Apply to different attention patterns and model sizes
221-
- **Algorithmic Expansion**: Explore evolution of other transformer components
222-
- **Cross-Compilation**: Develop architecture-agnostic optimization strategies
69+
## Limitations & potential improvements (follow-up work)
22370

224-
## 8. Conclusion
71+
This example intentionally uses **end-to-end generation benchmarks** (`mlx_lm.generate`) to measure real workloads, but that comes with trade-offs:
22572

226-
We demonstrate that evolutionary code optimization can automatically discover hardware-specific GPU kernel optimizations that outperform expert-engineered baselines. The evolved Metal kernel achieved an average 12.5% decode speed improvement through novel vectorization patterns, algorithmic innovations, and Apple Silicon specializations. While performance gains are workload-dependent, the approach successfully identified genuinely novel optimizations that would be challenging to discover through manual optimization.
73+
- **Benchmark noise & overhead**: subprocess startup, model loading, and generation variability can dwarf small kernel deltas (especially for short prompts). A complementary **microbenchmark** that times only the attention kernel would provide a cleaner signal.
74+
- **Serial evaluation by default**: candidates are evaluated sequentially (`parallel_evaluations: 1`) to keep GPU memory predictable. More parallelism may be possible with careful isolation, but it needs engineering.
75+
- **Compile-time dominates early search**: bf16 compilation failures are common and deterministic; caching compilation outcomes or factoring compilation into a cheaper gating stage may speed up evolution.
22776

228-
This work establishes evolutionary optimization as a viable approach for automated GPU kernel discovery and suggests significant potential for applying similar techniques to other performance-critical computational kernels.
77+
We plan to open follow-up issues to track improvements to the benchmarking/evolution signal and workflow.

0 commit comments

Comments
 (0)