anquachdev commited on
Commit
ca7ce60
·
verified ·
1 Parent(s): c9eff7e

Upload MLX q4 weights

Browse files
config.json ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "card": 2048,
3
+ "n_q": 32,
4
+ "dep_q": 16,
5
+ "delays": [
6
+ 0,
7
+ 0,
8
+ 2,
9
+ 2,
10
+ 2,
11
+ 2,
12
+ 2,
13
+ 2,
14
+ 2,
15
+ 2,
16
+ 2,
17
+ 2,
18
+ 2,
19
+ 2,
20
+ 2,
21
+ 2,
22
+ 2,
23
+ 0,
24
+ 2,
25
+ 2,
26
+ 2,
27
+ 2,
28
+ 2,
29
+ 2,
30
+ 2,
31
+ 2,
32
+ 2,
33
+ 2,
34
+ 2,
35
+ 2,
36
+ 2,
37
+ 2,
38
+ 2
39
+ ],
40
+ "dim": 2048,
41
+ "text_card": 48000,
42
+ "existing_text_padding_id": 3,
43
+ "num_heads": 16,
44
+ "num_layers": 28,
45
+ "hidden_scale": 6,
46
+ "causal": true,
47
+ "layer_scale": null,
48
+ "context": 3000,
49
+ "max_period": 20000.0,
50
+ "gating": "silu",
51
+ "norm": "rms_norm_f32",
52
+ "positional_embedding": "rope_concat",
53
+ "depformer_dim": 1024,
54
+ "depformer_num_heads": 16,
55
+ "depformer_num_layers": 6,
56
+ "depformer_dim_feedforward": null,
57
+ "depformer_multi_linear": true,
58
+ "depformer_norm": "layer_norm",
59
+ "depformer_pos_emb": "none",
60
+ "depformer_weights_per_step": true,
61
+ "demux_second_stream": false,
62
+ "kv_repeat": 2,
63
+ "depformer_kv_repeat": 1,
64
+ "text_card_out": null,
65
+ "conditioners": {},
66
+ "fuser": {
67
+ "cross_attention_pos_emb": false,
68
+ "cross_attention_pos_emb_scale": 1,
69
+ "sum": [],
70
+ "prepend": [],
71
+ "cross": []
72
+ },
73
+ "cross_attention": false,
74
+ "model_id": {
75
+ "sig": "77f82164",
76
+ "epoch": 110
77
+ },
78
+ "depformer_weights_per_step_schedule": [
79
+ 0,
80
+ 1,
81
+ 2,
82
+ 3,
83
+ 4,
84
+ 5,
85
+ 6,
86
+ 7,
87
+ 8,
88
+ 8,
89
+ 8,
90
+ 8,
91
+ 8,
92
+ 8,
93
+ 8,
94
+ 8
95
+ ],
96
+ "model_type": "hibiki",
97
+ "lm_gen_config": {
98
+ "temp": 0.8,
99
+ "temp_text": 0.8,
100
+ "top_k": 250,
101
+ "top_k_text": 250
102
+ },
103
+ "mimi_name": "mimi-pytorch-e351c8d8@125.safetensors",
104
+ "tokenizer_name": "tokenizer_spm_48k_multi6_2.model",
105
+ "moshi_name": "hibiki-pytorch-77f82164@110.safetensors"
106
+ }
hibiki.q4.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:abb84fee2b49af48b8117a97ff00c81bdfbe957aa4ff889cc45b39e4b4948f0f
3
+ size 2407733999
mimi-pytorch-e351c8d8@125.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:09b782f0629851a271227fb9d36db65c041790365f11bbe5d3d59369cf863f50
3
+ size 384644900
mlx_hibiki_patch.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Runtime patches that make moshi_mlx (0.3.0) run hibiki-zero.
2
+
3
+ NOTE: this project's local scripts use the vendored fork at ./moshi-mlx/, which
4
+ already has all of these deltas folded in, so they no longer import this module.
5
+ It is kept only as the portable compatibility shim published alongside the q4
6
+ weights on the Hub (`huybik/hibiki-zero-3b-mlx-q4`) for users running the stock
7
+ `moshi-mlx` package off PyPI.
8
+
9
+ moshi_mlx targets moshi / older hibiki and misses three hibiki-zero deltas:
10
+ 1. config: `hidden_scale` is ignored (feedforward hardcoded to 4*dim) and the
11
+ depformer feedforward is left None; `kv_repeat` is hardcoded to 1.
12
+ 2. attention: the forward pass asserts kv_repeat==1, so grouped-query
13
+ attention (hibiki-zero main transformer uses kv_repeat=2) won't run.
14
+ 3. positional embedding: only "rope" (interleaved) is wired up; hibiki-zero
15
+ uses "rope_concat" == RoPE with interleave=False (MLX traditional=False).
16
+ 4. depformer: hibiki-zero applies a learned per-slice output LayerNorm
17
+ (`depformer_norms.{i}`) before each audio `linear_out`; moshi_mlx omits it,
18
+ so the audio logits come out ~3x too small -> out-of-distribution tokens ->
19
+ babbling/overlapping speech (the text stream is unaffected).
20
+
21
+ Import this module before building/loading the model.
22
+ """
23
+ import mlx.core as mx
24
+ import mlx.nn as nn
25
+ from moshi_mlx import models
26
+ from moshi_mlx.models import lm as L
27
+ from moshi_mlx.modules import transformer as T
28
+
29
+ # --- 1. config: honour hidden_scale + kv_repeat -----------------------------
30
+ _orig_from = models.LmConfig.from_config_dict.__func__
31
+
32
+
33
+ def _from_config_dict(cls, data):
34
+ cfg = _orig_from(cls, data)
35
+ hs = data["hidden_scale"]
36
+ cfg.transformer.dim_feedforward = hs * data["dim"]
37
+ cfg.depformer.transformer.dim_feedforward = hs * data["depformer_dim"]
38
+ cfg.transformer.kv_repeat = data["kv_repeat"]
39
+ return cfg
40
+
41
+
42
+ models.LmConfig.from_config_dict = classmethod(_from_config_dict)
43
+
44
+ # --- 2 + 3. attention: GQA + rope_concat ------------------------------------
45
+ _orig_attn_init = T.Attention.__init__
46
+
47
+
48
+ def _attn_init(self, cfg):
49
+ _orig_attn_init(self, cfg)
50
+ if cfg.positional_embedding in ("rope", "rope_concat"):
51
+ # rope_concat == interleave=False == MLX traditional=False
52
+ self.rope = nn.RoPE(
53
+ cfg.head_dim,
54
+ traditional=cfg.positional_embedding != "rope_concat",
55
+ base=cfg.max_period,
56
+ )
57
+
58
+
59
+ def _attn_call(self, xs, cache, mask=None):
60
+ cfg = self.cfg
61
+ b, t, _ = xs.shape
62
+ H, D = cfg.num_heads, cfg.head_dim
63
+ Hkv = H // cfg.kv_repeat
64
+ qkv = self.in_proj(xs)
65
+ q = qkv[..., : H * D].reshape(b, t, H, D).transpose(0, 2, 1, 3)
66
+ k = qkv[..., H * D : H * D + Hkv * D].reshape(b, t, Hkv, D).transpose(0, 2, 1, 3)
67
+ v = qkv[..., H * D + Hkv * D :].reshape(b, t, Hkv, D).transpose(0, 2, 1, 3)
68
+ if self.rope is not None:
69
+ q = self.rope(q, offset=cache.offset)
70
+ k = self.rope(k, offset=cache.offset)
71
+ k, v = cache.update_and_fetch(k, v)
72
+ k_len = k.shape[2]
73
+ k_target_len = t + min(cfg.context, k_len - t)
74
+ if k_target_len < k_len:
75
+ k = k[:, :, k_len - k_target_len :]
76
+ v = v[:, :, k_len - k_target_len :]
77
+ # mx scaled_dot_product_attention handles GQA (H a multiple of Hkv) natively.
78
+ xs = mx.fast.scaled_dot_product_attention(q, k, v, scale=self.scale, mask=mask)
79
+ xs = xs.transpose(0, 2, 1, 3).reshape(b, t, H * D)
80
+ return self.out_proj(xs)
81
+
82
+
83
+ T.Attention.__init__ = _attn_init
84
+ T.Attention.__call__ = _attn_call
85
+
86
+ # --- 4. depformer per-codebook output LayerNorm -----------------------------
87
+ # hibiki-zero applies a learned per-slice LayerNorm (`depformer_norms.{i}`,
88
+ # dim=depformer_dim, eps 1e-5, with bias) to the depformer transformer output
89
+ # *before* `linear_out` (PyTorch: logits = linears[i](depformer_norms[i](out))).
90
+ # moshi_mlx feeds the un-normalised features straight into linear_out, so the
91
+ # audio logits come out ~3x too small and uncorrelated -> babble + clipping.
92
+ # Add the norm to each slice, apply it in DepFormer.sample, and load its weights.
93
+ _orig_slice_init = L.DepFormerSlice.__init__
94
+
95
+
96
+ def _slice_init(self, in_vocab_size, out_vocab_size, main_transformer_dim,
97
+ demux_second_stream, cfg):
98
+ _orig_slice_init(self, in_vocab_size, out_vocab_size, main_transformer_dim,
99
+ demux_second_stream, cfg)
100
+ self.norm = nn.LayerNorm(cfg.transformer.d_model, 1e-5)
101
+
102
+
103
+ L.DepFormerSlice.__init__ = _slice_init
104
+
105
+
106
+ def _depformer_sample(self, main_transformer_out, sampler, text_token, cache,
107
+ cfg_coef=1.0):
108
+ tokens = []
109
+ last_token = text_token
110
+ for c in cache:
111
+ c.reset()
112
+ for slice in self.slices:
113
+ if cfg_coef != 1:
114
+ last_token = mx.tile(last_token, (2, 1))
115
+ xs = slice.linear_in(main_transformer_out) + slice.emb(last_token)
116
+ xs = slice.transformer(xs, cache=cache)
117
+ logits = slice.linear_out(slice.norm(xs))
118
+ if cfg_coef != 1:
119
+ l1, l2 = logits.split(2, axis=0)
120
+ logits = cfg_coef * l1 - (cfg_coef - 1) * l2
121
+ last_token, _ = sampler(logits)
122
+ tokens.append(last_token)
123
+ return mx.stack(tokens, axis=1)
124
+
125
+
126
+ L.DepFormer.sample = _depformer_sample
127
+
128
+ # load depformer_norms.{i}.{weight,bias} into slices.{i}.norm
129
+ _orig_load = L.Lm.load_pytorch_weights
130
+
131
+
132
+ def _load_pytorch_weights(self, file, lm_config, strict=True):
133
+ # Run the original mapping non-strict to build the rest, capture its weight
134
+ # dict, append our depformer norms, then do the single strict load.
135
+ pth = mx.load(file)
136
+ extra = {}
137
+ for i in range(lm_config.depformer.num_slices):
138
+ for p in ("weight", "bias"):
139
+ k = f"depformer_norms.{i}.{p}"
140
+ if k in pth:
141
+ extra[f"depformer.slices.{i}.norm.{p}"] = pth[k]
142
+ captured = {}
143
+ real_load = self.load_weights
144
+
145
+ def _capture(items, strict):
146
+ captured.update(dict(items))
147
+ return None
148
+
149
+ self.load_weights = _capture
150
+ try:
151
+ _orig_load(self, file, lm_config, strict=False)
152
+ finally:
153
+ self.load_weights = real_load
154
+ captured.update(extra)
155
+ return self.load_weights(list(captured.items()), strict=strict)
156
+
157
+
158
+ L.Lm.load_pytorch_weights = _load_pytorch_weights
tokenizer_spm_48k_multi6_2.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c22110fb855aa049e17346ea2e88355bdd664f06cbfd09948380ab5e85b39697
3
+ size 857314
verify_mlx_q4.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Verify the 4-bit MLX hibiki-zero weights by translating a sample clip.
3
+
4
+ Uses the pipelined inference path (infer_mlx_fast), which overlaps the CPU Mimi
5
+ codec with the GPU LM (~3x real-time vs ~1.3x for the sequential run_inference
6
+ loop). Output is identical; this is just the fast entry point for the MLX path.
7
+ """
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ import mlx.core as mx
12
+
13
+ HERE = Path(__file__).resolve().parent.parent # repo root (scripts/ -> ..)
14
+ sys.path.insert(0, str(HERE / "src"))
15
+ from infer_mlx_fast import run
16
+
17
+ if __name__ == "__main__":
18
+ mx.random.seed(299792458)
19
+ run(
20
+ str(HERE / "hibiki_zero" / "samples" / "leon.wav"),
21
+ str(HERE / "translations" / "leon_mlx_q4.wav"),
22
+ )