Skip to content

Commit a2fa299

Browse files
Merge pull request #449 from SKaiNET-developers/feature/native-macos-accelerate-simd
Native macos accelerate simd
2 parents c1e6b07 + 6dbd889 commit a2fa299

5 files changed

Lines changed: 497 additions & 2 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Native macOS SIMD acceleration via Apple Accelerate framework
2+
3+
## Problem
4+
5+
The `skainet-backend-cpu` module on Kotlin/Native macOS (macosArm64) uses plain scalar loops
6+
for all tensor operations (`DefaultCpuOps`). On JVM, the same module uses the JDK Vector API
7+
for SIMD-accelerated matmul, elementwise ops, and reductions (`DefaultCpuOpsJvm`), which gives
8+
a significant performance advantage.
9+
10+
When running LLM inference benchmarks via the `llm-performance` native binary, the CPU backend
11+
is 5-10x slower than it needs to be because every matmul is a triple-nested scalar loop
12+
(`DefaultCpuOps.kt:264-272`).
13+
14+
## Proposed solution
15+
16+
Add an Accelerate-backed `TensorOps` implementation for the macOS native target, mirroring
17+
how the JVM target has `DefaultCpuOpsJvm`. Apple's Accelerate framework provides
18+
hardware-optimized BLAS and vector DSP routines that leverage ARM NEON and AMX under the hood.
19+
20+
### Architecture
21+
22+
```
23+
PlatformCpuOpsFactory
24+
├── jvmMain → DefaultCpuOpsJvm (Vector API + optional BLAS) ← exists
25+
├── nativeMain → DefaultCpuOps (scalar fallback) ← exists
26+
├── macosMain → AccelerateCpuOps (Accelerate framework via cinterop) ← NEW
27+
└── linuxMain → DefaultCpuOps (scalar, or OpenBLAS in future) ← unchanged
28+
```
29+
30+
### Key changes
31+
32+
**1. Cinterop definition**`src/nativeInterop/cinterop/accelerate.def`
33+
34+
```def
35+
package = platform.accelerate
36+
language = C
37+
headers = Accelerate/Accelerate.h
38+
compilerOpts = -framework Accelerate
39+
linkerOpts = -framework Accelerate
40+
```
41+
42+
**2. New class**`src/macosMain/kotlin/.../AccelerateCpuOps.kt`
43+
44+
Extends `DefaultCpuOps` and overrides hot-path operations with Accelerate calls:
45+
46+
| Priority | Operation | Accelerate function | Impact |
47+
|----------|-----------|---------------------|--------|
48+
| P0 | `matmul` | `cblas_sgemm` | Dominant cost in LLM inference (~90% of forward pass) |
49+
| P1 | `add` | `vDSP_vadd` | Elementwise add (residual connections) |
50+
| P1 | `multiply` | `vDSP_vmul` | Elementwise multiply (gates, scaling) |
51+
| P1 | `subtract` | `vDSP_vsub` | Elementwise subtract |
52+
| P1 | `divide` | `vDSP_vdiv` | Elementwise divide |
53+
| P2 | `sum` (global) | `vDSP_sve` | Reduction for normalization |
54+
| P2 | `mean` (global) | `vDSP_meanv` | Reduction for normalization |
55+
| P2 | `softmax` | `vDSP_vse` + manual | Attention weights |
56+
| P3 | `relu` | `vDSP_vthres` / `vDSP_vthr` | Activation function |
57+
| P3 | `silu` | manual vectorized loop | Activation function (SiLU = x * sigmoid(x)) |
58+
| P3 | `transpose` | `vDSP_mtrans` | Matrix transpose |
59+
60+
**3. Platform factory** — update `PlatformCpuOpsFactory` for macOS
61+
62+
```kotlin
63+
// src/macosMain/kotlin/.../PlatformCpuOpsFactory.macos.kt
64+
internal actual fun platformDefaultCpuOpsFactory(): (TensorDataFactory) -> TensorOps {
65+
println("[SKaiNET] Using Accelerate-backed CPU operations (ARM NEON + AMX)")
66+
return { factory -> AccelerateCpuOps(factory) }
67+
}
68+
```
69+
70+
This requires splitting the current `nativeMain` expect/actual into separate
71+
`macosMain` and `linuxMain` actuals (the `macosMain` source set already exists in
72+
`build.gradle.kts`).
73+
74+
**4. Build changes**`build.gradle.kts`
75+
76+
Add cinterop configuration for macosArm64 (and optionally iosArm64/iosSimulatorArm64):
77+
78+
```kotlin
79+
macosArm64 {
80+
compilations["main"].cinterops {
81+
val accelerate by creating {
82+
defFile("src/nativeInterop/cinterop/accelerate.def")
83+
}
84+
}
85+
}
86+
```
87+
88+
Add linker opts for the Accelerate framework to all macOS/iOS binaries.
89+
90+
### Implementation notes
91+
92+
- `AccelerateCpuOps` should extend `DefaultCpuOps` and override only the operations above.
93+
Non-accelerated operations fall through to the scalar implementation.
94+
- The `matmul` override should handle 2D FP32 tensors with `cblas_sgemm` and delegate
95+
batched/non-float cases to `super.matmul()`.
96+
- `vDSP_*` functions operate on contiguous `FloatArray` buffers. Tensors backed by
97+
`FloatArrayTensorData` can be passed directly; others need a `toFloatArray()` copy.
98+
- Broadcasting logic (e.g., bias add, scalar multiply) should remain in the Kotlin layer
99+
and only dispatch the contiguous inner loop to Accelerate.
100+
- The same approach works for iOS targets (`iosArm64`, `iosSimulatorArm64`) since
101+
Accelerate is available on all Apple platforms.
102+
103+
### Testing
104+
105+
- Existing `DefaultCpuOps` tests in `commonTest` should pass unchanged (numerical equivalence).
106+
- Add macOS-specific tests verifying Accelerate dispatch actually occurs (e.g., check log output
107+
or add a query method).
108+
- Benchmark comparison: run `llm-performance` native benchmark with the current scalar backend
109+
vs Accelerate backend on the same model.
110+
111+
### Expected impact
112+
113+
Based on JVM BLAS vs scalar measurements and Apple's published Accelerate performance data:
114+
115+
- **matmul**: 10-50x speedup (NEON + AMX vs scalar loop)
116+
- **elementwise**: 4-8x speedup (NEON vectorization)
117+
- **reductions**: 4-8x speedup (NEON vectorization)
118+
- **overall LLM inference**: 5-20x speedup on native macOS CPU backend
119+
120+
### Files to create/modify
121+
122+
```
123+
skainet-backends/skainet-backend-cpu/
124+
├── build.gradle.kts # add cinterop
125+
├── src/nativeInterop/cinterop/accelerate.def # NEW
126+
├── src/macosMain/kotlin/.../AccelerateCpuOps.kt # NEW
127+
├── src/macosMain/kotlin/.../PlatformCpuOpsFactory.macos.kt # NEW
128+
├── src/linuxMain/kotlin/.../PlatformCpuOpsFactory.linux.kt # NEW (move from nativeMain)
129+
└── src/nativeMain/kotlin/.../PlatformCpuOpsFactory.native.kt # REMOVE (split to platform-specific)
130+
```
131+
132+
### References
133+
134+
- JVM SIMD implementation: `src/jvmMain/kotlin/.../DefaultCpuOpsJvm.kt`
135+
- JVM BLAS integration: `src/jvmMain/kotlin/.../JvmBlas.kt`
136+
- Apple Accelerate docs: https://developer.apple.com/documentation/accelerate
137+
- CBLAS reference: https://developer.apple.com/documentation/accelerate/blas
138+
- vDSP reference: https://developer.apple.com/documentation/accelerate/vdsp

skainet-backends/skainet-backend-cpu/build.gradle.kts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,20 @@ kotlin {
7070
dependsOn(commonMain)
7171
}
7272

73+
val appleMain by creating {
74+
dependsOn(nativeMain)
75+
}
76+
7377
val linuxMain by creating {
7478
dependsOn(nativeMain)
7579
}
7680

7781
val iosMain by creating {
78-
dependsOn(nativeMain)
82+
dependsOn(appleMain)
7983
}
8084

8185
val macosMain by creating {
82-
dependsOn(nativeMain)
86+
dependsOn(appleMain)
8387
}
8488

8589
val iosArm64Main by getting {

0 commit comments

Comments
 (0)