|
1 | | -# OpenEvolve Metal Kernel Optimization: Automated Discovery of Custom GPU Kernels for Transformer Attention |
| 1 | +# MLX Metal Kernel Optimization (Qwen3-0.6B-bf16) |
2 | 2 |
|
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`. |
4 | 4 |
|
5 | | -## Abstract |
| 5 | +## Target |
6 | 6 |
|
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 |
20 | 10 | - **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) |
38 | 12 |
|
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 |
47 | 14 |
|
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`) |
49 | 22 |
|
50 | | -### 2.3 Optimization Constraints |
| 23 | +## Important: evaluation validity (before vs after) |
51 | 24 |
|
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: |
57 | 26 |
|
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**. | |
63 | 32 |
|
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. |
65 | 34 |
|
66 | | -### 3.1 Discovered Optimizations |
| 35 | +## Run evolution |
67 | 36 |
|
68 | | -The evolutionary process discovered several key optimizations: |
| 37 | +From this directory: |
69 | 38 |
|
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 |
82 | 42 | ``` |
83 | 43 |
|
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. |
105 | 45 |
|
106 | | -**Innovation**: Reduced from three-pass to two-pass algorithm, fusing softmax normalization with value accumulation. |
| 46 | +If you prefer running the CLI directly: |
107 | 47 |
|
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 |
115 | 51 | ``` |
116 | 52 |
|
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) |
148 | 54 |
|
149 | | -### 4.3 Statistical Analysis |
| 55 | +To compare the MLX baseline against the best evolved program: |
150 | 56 |
|
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 | +``` |
208 | 60 |
|
209 | | -## 7. Limitations and Future Work |
| 61 | +## How to verify the validity fixes are active |
210 | 62 |
|
211 | | -### 7.1 Current Limitations |
| 63 | +When the hook is enabled, the optimized path should execute via the wrapper: |
212 | 64 |
|
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 ...` |
216 | 66 |
|
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”. |
218 | 68 |
|
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) |
223 | 70 |
|
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: |
225 | 72 |
|
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. |
227 | 76 |
|
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