Skip to content

Commit f1c72e9

Browse files
committed
adopt mHC from deepseek
1 parent 4885af8 commit f1c72e9

4 files changed

Lines changed: 49 additions & 12 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2593,4 +2593,16 @@ ids_out, num_out, is_number_mask = model.generate(start_ids, start_nums, 17)
25932593
}
25942594
```
25952595

2596+
```bibtex
2597+
@misc{xie2025mhcmanifoldconstrainedhyperconnections,
2598+
title = {mHC: Manifold-Constrained Hyper-Connections},
2599+
author = {Zhenda Xie and Yixuan Wei and Huanqi Cao and Chenggang Zhao and Chengqi Deng and Jiashi Li and Damai Dai and Huazuo Gao and Jiang Chang and Liang Zhao and Shangyan Zhou and Zhean Xu and Zhengyan Zhang and Wangding Zeng and Shengding Hu and Yuqing Wang and Jingyang Yuan and Lean Wang and Wenfeng Liang},
2600+
year = {2025},
2601+
eprint = {2512.24880},
2602+
archivePrefix = {arXiv},
2603+
primaryClass = {cs.CL},
2604+
url = {https://arxiv.org/abs/2512.24880},
2605+
}
2606+
```
2607+
25962608
*solve intelligence... then use that to solve everything else.* - Demis Hassabis

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "x-transformers"
3-
version = "2.12.2"
3+
version = "2.14.1"
44
description = "X-Transformers"
55
authors = [
66
{ name = "Phil Wang", email = "lucidrains@gmail.com" }

tests/test_x_transformers.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -594,8 +594,8 @@ def test_cross_attn_rotary(
594594
context_mask = context_mask
595595
)
596596

597-
@param('tanh', (True, False))
598-
def test_hyper_connections(tanh):
597+
@param('qkv_receive_diff_residuals', (False, True))
598+
def test_hyper_connections(qkv_receive_diff_residuals):
599599

600600
model = TransformerWrapper(
601601
num_tokens = 20000,
@@ -605,9 +605,7 @@ def test_hyper_connections(tanh):
605605
depth = 6,
606606
heads = 8,
607607
num_residual_streams = 8, # 8 dynamic hyper connection residual streams
608-
residual_fn_kwargs = dict(
609-
tanh = tanh
610-
)
608+
qkv_receive_diff_residuals = qkv_receive_diff_residuals
611609
)
612610
)
613611

x_transformers/x_transformers.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,18 @@ def forward(self, x, residual, **kwargs):
10521052

10531053
# hyper connections
10541054

1055+
def sinkhorn_knopps(t, iters = 20):
1056+
dtype = t.dtype
1057+
t = t.float()
1058+
1059+
t = t.softmax(dim = -2)
1060+
1061+
for _ in range(iters):
1062+
t = F.normalize(t, p = 1, dim = -1)
1063+
t = F.normalize(t, p = 1, dim = -2)
1064+
1065+
return t.to(dtype)
1066+
10551067
class HyperConnection(Module):
10561068
def __init__(
10571069
self,
@@ -1066,11 +1078,12 @@ def __init__(
10661078
"""
10671079
https://arxiv.org/abs/2409.19606
10681080
Appendix J - Algorithm 2, Dynamic only
1081+
1082+
https://arxiv.org/abs/2512.24880
1083+
"Manifold constrained" mixing matrices
10691084
"""
10701085
super().__init__()
10711086

1072-
self.act = nn.Tanh() if tanh else nn.Identity()
1073-
10741087
self.norm = nn.LayerNorm(dim, bias = False)
10751088

10761089
self.num_residual_streams = num_residual_streams
@@ -1092,25 +1105,39 @@ def __init__(
10921105
self.dynamic_beta_scale = nn.Parameter(torch.ones(()) * 1e-2)
10931106

10941107
def prepare(self, residuals):
1108+
views = self.num_input_views
1109+
streams = self.num_residual_streams
10951110

10961111
residuals = rearrange(residuals, '(b s) n d -> b n s d', s = self.num_residual_streams)
10971112

10981113
normed = self.norm(residuals)
10991114

1100-
wc_weight = self.act(normed @ self.dynamic_alpha_fn)
1115+
wc_weight = normed @ self.dynamic_alpha_fn
11011116
dynamic_alpha = wc_weight * self.dynamic_alpha_scale
11021117
alpha = dynamic_alpha + self.static_alpha
11031118

1104-
dc_weight = self.act(normed @ self.dynamic_beta_fn)
1119+
alpha_input, alpha_residual = alpha[..., :views], alpha[..., views:]
1120+
1121+
alpha_input = alpha_input.sigmoid() # constraint Hpre
1122+
1123+
# the sinkhorn knopps constraint for the residual mixing
1124+
1125+
alpha_residual = rearrange(alpha_residual, '... (s1 s2) -> ... s1 s2', s2 = streams)
1126+
alpha_residual = sinkhorn_knopps(alpha_residual)
1127+
alpha_residual = rearrange(alpha_residual, '... s1 s2 -> ... (s1 s2)')
1128+
1129+
alpha = cat((alpha_input, alpha_residual), dim = -1)
1130+
1131+
dc_weight = (normed @ self.dynamic_beta_fn).sigmoid() * 2
11051132
dynamic_beta = dc_weight * self.dynamic_beta_scale
11061133
beta = dynamic_beta + self.static_beta
11071134

1135+
beta = beta.sigmoid() * 2 # constraint Hpost
1136+
11081137
# width connection
11091138

11101139
mix_h = einsum('... s t, ... s d -> ... t d', alpha, residuals)
11111140

1112-
views = self.num_input_views
1113-
11141141
if views == 1:
11151142
branch_input, residuals = mix_h[..., 0, :], mix_h[..., 1:, :]
11161143
else:

0 commit comments

Comments
 (0)