Views
No views yet
torch-ext/flex_sae/topk_kernels.pytorch-ext/flex_sae/hierarchical_kernels.py.1from kernels import get_kernel
2
3
4flex = get_kernel('t-tech/flex-sae')
5
6top_k_kernel = flex.triton_topk_sae_loss
7hierarchical_top_k_kernel = flex.triton_hierarchical_sae_loss
8
9"B -- batch size, K -- top-k, F -- dictionary size, D -- model hidden dim"
10
11loss: torch.Tensor = top_k_kernel(
12 indices: torch.Tensor, # [B, K]
13 weight: torch.Tensor, # [F, D]
14 vals: torch.Tensor, # [B, K]
15 bias: torch.Tensor, # [D]
16 target: torch.Tensor, # [B, D]
17)
18
19loss: torch.Tensor = hierarchical_top_k_kernel(
20 indices: torch.Tensor, # [B, K]
21 weight: torch.Tensor, # [F, D]
22 vals: torch.Tensor, # [B, K]
23 bias: torch.Tensor, # [D]
24 target: torch.Tensor, # [B, D]
25)torch-ext/flex_sae/ contains the Triton kernels alongside torch reference implementations.tests/ hosts CUDA-backed property tests that ensure numerical parity across dtypes and kernels.build.toml, flake.nix integrate the project with Hugging Face kernel-builder.1# /// script
2# dependencies = [
3# "torch",
4# "numpy",
5# "kernels",
6# ]
7# ///
8
9import torch
10import numpy as np
11from kernels import get_kernel
12
13flex = get_kernel("t-tech/flex-sae") #Fast Kernels
14
15@torch.compile(fullgraph=True)
16def hierarchical_sae_loss(
17 indices: torch.Tensor, # [B, K]
18 weight: torch.Tensor, # [F, D]
19 vals: torch.Tensor, # [B, K]
20 bias: torch.Tensor, # [D]
21 target: torch.Tensor, # [B, D]
22) -> torch.Tensor:
23 emb = weight[indices].to(torch.float32) # [K, D]
24 recon_cum = bias.to(torch.float32) + (emb * vals.unsqueeze(-1)).cumsum(dim=1)
25 diff = recon_cum.to(torch.float32) - target.to(torch.float32).unsqueeze(1)
26 loss = diff.pow(2).mean()
27 return loss
28
29
30B = 2048
31K = 256
32F = 1024 * 128
33D = 1024
34WARMUP = 5
35NUM_ITER = 100
36dtype = torch.float32
37
38vals = None
39decoder = None
40bias = None
41target = None
42indices = None
43
44
45def init_parameters():
46 global vals, decoder, bias, target, indices
47 vals = torch.randn(B, K, dtype=dtype, device="cuda").abs().requires_grad_()
48 decoder = torch.randn(F, D, dtype=dtype, device="cuda", requires_grad=True)
49 bias = torch.randn(D, dtype=dtype, device="cuda", requires_grad=True)
50 target = torch.randn(B, D, dtype=dtype, device="cuda")
51 indices = torch.randint(0, F, (B, K), dtype=torch.long, device="cuda")
52
53
54timing_kernel = []
55timing_vanilla = []
56torch.cuda.reset_peak_memory_stats()
57loss_kernel_list = torch.zeros((100,))
58loss_vanilla_list = torch.zeros((100,))
59
60
61def zero_grad():
62 vals.grad = None
63 decoder.grad = None
64 bias.grad = None
65 torch.cuda.empty_cache()
66
67
68for i in range(NUM_ITER + WARMUP):
69 init_parameters()
70 start_kernel = torch.cuda.Event(enable_timing=True)
71 end_kernel = torch.cuda.Event(enable_timing=True)
72 start_vanilla = torch.cuda.Event(enable_timing=True)
73 end_vanilla = torch.cuda.Event(enable_timing=True)
74
75 start_kernel.record()
76 loss_kernel = flex.triton_hierarchical_sae_loss(indices, decoder, vals, bias, target)
77 loss_kernel.backward()
78 end_kernel.record()
79
80 zero_grad()
81 start_vanilla.record()
82 loss_vanilla = hierarchical_sae_loss(indices, decoder, vals, bias, target)
83 loss_vanilla.backward()
84 end_vanilla.record()
85 if i >= WARMUP:
86 torch.cuda.synchronize()
87 timing_kernel.append(start_kernel.elapsed_time(end_kernel))
88 timing_vanilla.append(start_vanilla.elapsed_time(end_vanilla))
89 loss_kernel_list[i-WARMUP] = loss_kernel.detach()
90 loss_vanilla_list[i-WARMUP] = loss_vanilla.detach()
91 zero_grad()
92
93if torch.allclose(loss_kernel, loss_vanilla):
94 print("✅ Outputs are close! Everything is good! 🎉")
95else:
96 print("❌ Outputs mismatch... ⚠️🤔")
97
98
99print(f"🦎 Triton Kernel Time (Ours): {np.mean(timing_kernel):.4f} ± {np.std(timing_kernel):.4f} ms")
100print(f"🔥 Torch Compile Kernel Time: {np.mean(timing_vanilla):.4f} ± {np.std(timing_vanilla):.4f} ms")
101print(f"🚀 Speedup: {np.mean(timing_vanilla) / np.mean(timing_kernel):.2f}x")uv run https://huggingface.co/t-tech/flex-sae/resolve/main/example.py.| Decoder backend | K=32 (ms / GiB) | K=64 (ms / GiB) | K=128 (ms / GiB) |
|---|---|---|---|
| Pure torch-compiled | |||
| TopK | 8.787 / 2.92 | 11.746 / 2.92 | 18.877 / 2.93 |
| HierarchicalTopK | 12.824 / 6.29 | 23.379 / 10.79 | 43.851 / 19.80 |
| Triton kernels | |||
| TopK | 5.576 / 2.92 | 6.339 / 2.92 | 7.961 / 2.93 |
| HierarchicalTopK | 6.696 / 2.92 | 7.995 / 2.92 | 10.609 / 2.93 |
torch-ext/flex_sae/topk_kernels.py are released under the Apache License 2.0.torch-ext/flex_sae/topk_kernels.py includes code adapted from Facebook Research's memory project, originally published under the Creative Commons Attribution-NonCommercial 4.0 International License. That component therefore remains available for non-commercial use only; see NOTICE for details.1@misc{balagansky2025trainsparseautoencodermultiple,
2 title={Train One Sparse Autoencoder Across Multiple Sparsity Budgets to Preserve Interpretability and Accuracy},
3 author={Nikita Balagansky and Yaroslav Aksenov and Daniil Laptev and Vadim Kurochkin and Gleb Gerasimov and Nikita Koryagin and Daniil Gavrilov},
4 year={2025},
5 eprint={2505.24473},
6 archivePrefix={arXiv},
7 primaryClass={cs.LG},
8 url={https://arxiv.org/abs/2505.24473},
9}