Download speed_check.py from JakCwi/eugen-sparse: direct link, hf CLI and curl.
- Browser
- Download file 3.5 kB
-
https://huggingface.co/JakCwi/eugen-sparse/resolve/main/speed_check.py
- Command line
-
hf download hf://JakCwi/eugen-sparse/speed_check.py
-
curl -L -o speed_check.py https://huggingface.co/JakCwi/eugen-sparse/resolve/main/speed_check.py
3.5 kB
| # SPDX-License-Identifier: CC-BY-NC-4.0 | |
| # Copyright (c) 2026 Jakub Ćwirlej | |
| """FFN block micro-benchmark (CUDA): dense SwiGLU vs eugen_sparse (selection + kernel), forward and | |
| forward+backward, bf16, optionally under torch.compile. | |
| python speed_check.py --tokens 65536 --d-model 512 --d-ff 4096 --group-size 32 \ | |
| --active-groups 8 --samples-per-group 8 [--compile] | |
| This times one FFN block on random inputs. It is not a prediction of end-to-end training speed: | |
| measure your own training loop. Random inputs spread tokens evenly over the groups; a trained model | |
| is more skewed (popular groups), which the load-balancing bias counteracts. | |
| """ | |
| import argparse | |
| import statistics | |
| import time | |
| import torch | |
| import torch.nn.functional as F | |
| import eugen_sparse as es | |
| def timer(fn, warmup=5, iters=20): | |
| for _ in range(warmup): | |
| fn() | |
| torch.cuda.synchronize() | |
| ts = [] | |
| for _ in range(iters): | |
| t0 = time.perf_counter() | |
| fn() | |
| torch.cuda.synchronize() | |
| ts.append((time.perf_counter() - t0) * 1000) | |
| return statistics.median(ts) | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument('--tokens', type=int, default=65536) | |
| ap.add_argument('--d-model', type=int, default=512) | |
| ap.add_argument('--d-ff', type=int, default=4096) | |
| ap.add_argument('--group-size', type=int, default=32) | |
| ap.add_argument('--active-groups', type=int, default=8) | |
| ap.add_argument('--samples-per-group', type=int, default=8) | |
| ap.add_argument('--compile', action='store_true') | |
| a = ap.parse_args() | |
| assert torch.cuda.is_available(), 'CUDA required' | |
| G, kg = a.group_size, a.active_groups | |
| if not es.kernel_supported(True, a.d_model, a.d_ff, G, kg): | |
| raise SystemExit('shape not supported by the kernel (see kernel_supported)') | |
| dt = torch.bfloat16 | |
| torch.manual_seed(1) | |
| x = torch.randn(a.tokens, a.d_model, device='cuda', dtype=dt) | |
| gw, uw = (torch.randn(a.d_ff, a.d_model, device='cuda', dtype=dt) * a.d_model ** -0.5 for _ in range(2)) | |
| dw = torch.randn(a.d_model, a.d_ff, device='cuda', dtype=dt) * a.d_ff ** -0.5 | |
| dy = torch.randn_like(x) | |
| sub, spg = es.make_subsample_index(a.d_ff, G, a.samples_per_group) | |
| sub = sub.cuda() | |
| def dense(x_, g_, u_, d_): | |
| return F.linear(F.silu(F.linear(x_, g_)) * F.linear(x_, u_), d_) | |
| def sparse(x_, g_, u_, d_): | |
| idx = es.grouped_topk_select(x_, g_, G, kg, sub, spg) | |
| return es.sparse_swiglu_ffn(x_, g_, u_, d_, idx, G) | |
| print(f'tokens={a.tokens} d_model={a.d_model} d_ff={a.d_ff} group_size={G} active_groups={kg} ' | |
| f'(density {kg * G / a.d_ff:.2%}) compile={a.compile} | {torch.cuda.get_device_name(0)} | ' | |
| f'torch {torch.__version__}') | |
| print('plan:', es.plan(a.tokens, a.d_model, a.d_ff, G, kg)) | |
| res = {} | |
| for name, fn in (('dense', dense), ('sparse', sparse)): | |
| if a.compile: | |
| fn = torch.compile(fn, fullgraph=True, dynamic=False) | |
| t_f = timer(lambda: fn(x, gw, uw, dw)) | |
| leaves = tuple(t.clone().requires_grad_() for t in (x, gw, uw, dw)) | |
| def fb(fn=fn, l=leaves): | |
| torch.autograd.grad(fn(*l), l, dy) | |
| t_fb = timer(fb) | |
| res[name] = (t_f, t_fb) | |
| print(f' {name:6s} fwd {t_f:8.2f} ms fwd+bwd {t_fb:8.2f} ms') | |
| print(f' sparse vs dense: fwd x{res["dense"][0] / res["sparse"][0]:.2f} ' | |
| f'fwd+bwd x{res["dense"][1] / res["sparse"][1]:.2f} (>1 = sparse faster)') | |
| if __name__ == '__main__': | |
| main() | |