Skip to content

Commit 9b20178

Browse files
committed
Investigate 10x performance with mathematical bounds and controlled Metal experiments
1 parent 6c18f44 commit 9b20178

38 files changed

Lines changed: 20451 additions & 2 deletions

‎README.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,15 @@ GitHub Actions runs small-model CPU tests on a macOS arm64 runner. Full checkpoi
160160

161161
## Performance research
162162

163-
[PERFORMANCE_RESEARCH.md](docs/PERFORMANCE_RESEARCH.md) analyzes the measured bottlenecks and proposes experiments for compilation, projection quantization, actual sparse window attention, batching and exact head pruning. These are research directions, not additional measured speedups in the released runtime.
163+
The performance investigations include both mathematical analysis and independent local experiments:
164+
165+
- [Initial performance research](docs/PERFORMANCE_RESEARCH.md): implementation bottlenecks, MLX kernel dispatch, and a controlled experiment plan.
166+
- [Mathematical investigation of a further 10× speedup](docs/MATH_10X_RESEARCH.md): arithmetic budgets, conditional bandwidth bounds, real weight spectra, exact reuse, and smaller-model designs.
167+
- [Engineering investigation](docs/ENGINEERING_10X_RESEARCH.md): measured compilation, quantization, final-head selection, custom Metal kernels, and representative matrix multiplications.
168+
169+
[experiments/](experiments) contains the research scripts and their raw measurements. The published runtime's performance and validation results are in [BENCHMARKS.md](BENCHMARKS.md); each experimental variant has its own timing and correctness results.
170+
171+
The current investigation does not support a further universal 10× speedup with the same checkpoints. Selected cases show approximately 1.03–1.08× paired median speedups; the engineering report gives the uncertainty intervals, quantization fidelity results, and custom Metal kernel measurements.
164172

165173
To prepare model cards and verified exports for publication, install the reference extras and run:
166174

‎README.zh-CN.md‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ uv run laya-mlx convert \
9494

9595
详细的 API、测试和复现命令见 [英文 README](README.md)。[BENCHMARKS.md](BENCHMARKS.md) 包含本机 PyTorch MPS FP32、MLX FP32 与 MLX FP16 的端到端 P50/P95、吞吐量、内存、数值一致性、重复运行和固定抽样分类测试。所有原始测量数据位于 [benchmarks/results](benchmarks/results),GPU 测试应串行运行。
9696

97-
[性能优化研究](docs/PERFORMANCE_RESEARCH.md) 根据实际实现、测量结果和 MLX 源码,分析编译、量化、稀疏局部注意力、批处理等方向,并给出正确性约束和实验方案。该研究中的优化建议尚未作为加速功能集成到本次发布。
97+
[初步性能研究](docs/PERFORMANCE_RESEARCH.md) 分析实现、基准和 MLX 源码。针对“能否再快一个数量级”,另有两份深入报告:
98+
99+
- [数学分析](docs/MATH_10X_RESEARCH.md):计算预算、带宽条件下界、真实权重谱、精确复用,以及蒸馏学生模型的设计空间。
100+
- [工程实测](docs/ENGINEERING_10X_RESEARCH.md):编译、量化、最后一层输出裁剪、自定义 Metal 核与矩阵乘法实验。
101+
102+
[experiments/](experiments) 保存研究脚本和原始数据。发布版本的结果见 [BENCHMARKS.md](BENCHMARKS.md),各实验变体的耗时与数值一致性单独记录。
103+
104+
目前证据不支持相同检查点下普遍再快 10 倍。部分场景的逐轮配对中位加速约为 1.03–1.08 倍;误差区间、量化保真结果和自定义 Metal 核的实测详见工程报告。
98105

99106
这是独立的 MLX 移植,模型能力及其限制来自上游;模型输出概率不等于答案必然正确。采用 Apache-2.0,原作者与移植说明见 [NOTICE](NOTICE)。

‎docs/ENGINEERING_10X_RESEARCH.md‎

Lines changed: 333 additions & 0 deletions
Large diffs are not rendered by default.

‎docs/MATH_10X_RESEARCH.md‎

Lines changed: 174 additions & 0 deletions
Large diffs are not rendered by default.

‎experiments/engineering/analyze.py‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""CPU-only analysis of paired experiment files; preserves the raw timings."""
2+
3+
import json
4+
from pathlib import Path
5+
6+
import numpy as np
7+
8+
from benchmarks.common import save_json
9+
10+
11+
def main():
12+
rng = np.random.default_rng(20260919)
13+
report = {
14+
"method": "median of paired per-round eager/candidate ratios; percentile bootstrap of round indices (2000 resamples), exploratory 95% intervals",
15+
"results": [],
16+
}
17+
for path in sorted(Path("experiments/engineering").glob("*-paired.json")):
18+
raw = json.loads(path.read_text())
19+
for row in raw["results"]:
20+
for mode in ("forward", "end_to_end"):
21+
baseline = np.asarray(row["candidates"]["eager"][mode]["samples_ms"])
22+
for candidate, metrics in row["candidates"].items():
23+
if candidate == "eager":
24+
continue
25+
times = np.asarray(metrics[mode]["samples_ms"])
26+
ratios = baseline / times
27+
resampled = ratios[rng.integers(len(ratios), size=(2000, len(ratios)))]
28+
interval = np.percentile(np.median(resampled, axis=1), [2.5, 97.5])
29+
report["results"].append(
30+
{
31+
"source": path.name,
32+
"case": row["case"],
33+
"candidate": candidate,
34+
"mode": mode,
35+
"rounds": len(ratios),
36+
"paired_speedup_median": float(np.median(ratios)),
37+
"bootstrap_95_interval": interval.tolist(),
38+
"per_round_min_max": [float(ratios.min()), float(ratios.max())],
39+
"ratio_of_p50": float(np.median(baseline) / np.median(times)),
40+
}
41+
)
42+
save_json(Path("experiments/engineering/paired_analysis.json"), report)
43+
44+
45+
if __name__ == "__main__":
46+
main()

‎experiments/engineering/kernels.py‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Experimental Metal GELU/gate kernel, using MLX's same erf implementation.
2+
3+
The vendored erf/expm1 helpers are from MLX v0.32.2, copyright Apple and
4+
Norbert Juffa. Their original notices are retained in vendor/ and MLX_LICENSE.
5+
Only this experiment uses them; the production package is unaffected.
6+
"""
7+
8+
from pathlib import Path
9+
10+
import mlx.core as mx
11+
import mlx.nn as nn
12+
13+
VENDOR = Path(__file__).with_name("vendor")
14+
helpers = "\n".join(
15+
line
16+
for filename in ("mlx_expm1f.h", "mlx_erf.h")
17+
for line in (VENDOR / filename).read_text().splitlines()
18+
if not line.startswith(("#include", "#pragma"))
19+
)
20+
GELU_GATE = mx.fast.metal_kernel(
21+
name="laya_experimental_exact_gelu_gate",
22+
input_names=["inp"],
23+
output_names=["out"],
24+
header="namespace laya_erf { using namespace metal;\n" + helpers + "\n}",
25+
source="""
26+
uint elem = thread_position_in_grid.x;
27+
uint row = elem / I;
28+
uint col = elem % I;
29+
T value = inp[row * (2 * I) + col];
30+
T gate = inp[row * (2 * I) + I + col];
31+
T scaled = value / T(1.4142135623730951);
32+
T erf_value = T(laya_erf::erf(float(scaled)));
33+
T gelu = T(T(value * T(T(1) + erf_value)) / T(2));
34+
out[elem] = gelu * gate;
35+
""",
36+
compile_options={"math_mode": "safe"},
37+
)
38+
39+
40+
def metal_gelu_gate(x):
41+
if x.dtype != mx.float16:
42+
raise ValueError("This research kernel has only been implemented and tested for FP16")
43+
if x.shape[-1] % 2:
44+
raise ValueError("GELU/gate input needs two equal-width branches")
45+
intermediate = x.shape[-1] // 2
46+
shape = (*x.shape[:-1], intermediate)
47+
return GELU_GATE(
48+
inputs=[x],
49+
template=[("T", x.dtype), ("I", intermediate)],
50+
grid=(x.size // 2, 1, 1),
51+
threadgroup=(256, 1, 1),
52+
output_shapes=[shape],
53+
output_dtypes=[x.dtype],
54+
)[0]
55+
56+
57+
class MetalMLP(nn.Module):
58+
def __init__(self, layer):
59+
super().__init__()
60+
self.Wi, self.Wo = layer.Wi, layer.Wo
61+
62+
def __call__(self, x):
63+
return self.Wo(metal_gelu_gate(self.Wi(x)))

0 commit comments

Comments
 (0)