Instructions to use justanurag/maya-mac with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use justanurag/maya-mac with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download justanurag/maya-mac --local-dir maya-mac
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
Download src/maya_mac/mlx_kernels.py from justanurag/maya-mac: direct link, hf CLI and curl.
- Browser
- Download file 15.6 kB
-
https://huggingface.co/justanurag/maya-mac/resolve/main/src/maya_mac/mlx_kernels.py
- Command line
-
hf download hf://justanurag/maya-mac/src/maya_mac/mlx_kernels.py
-
curl -L -o mlx_kernels.py https://huggingface.co/justanurag/maya-mac/resolve/main/src/maya_mac/mlx_kernels.py
15.6 kB
| """Custom Metal kernels for the depth decoder (mx.fast.metal_kernel, JIT-compiled once per shape). | |
| The depth decoder runs 124 tiny layer passes per frame (one or a few positions each), so its cost is the latency of | |
| dependent GPU kernels, not FLOPs: every LayerNorm, GELU, residual add, KV concatenate and transpose is one more | |
| ~5-10 us dispatch on the critical path. These kernels cut a layer from ~12 dependent kernels to 5: | |
| linear LayerNorm prologue (optional) + x @ W.T for T <= 32 rows + GELU / residual / constant-add epilogue; | |
| W dense fp16/bf16 or MLX 8-bit (affine, group 64) | |
| attend append this pass's K/V to the band's cache and run causal attention over <= 32 positions | |
| sample top-k (exact k-th largest by radix select, ties kept) + Gumbel-max draw + the drawn code's embedding, | |
| appended to the prefix | |
| """ | |
| from __future__ import annotations | |
| import functools | |
| import mlx.core as mx | |
| SIMDS = 8 # simdgroups per threadgroup in `linear` (one output row each) | |
| _ERF = """ | |
| // erf with |error| < 1.2e-7 (Numerical Recipes erfc, Chebyshev fit): the exact-GELU torch uses | |
| inline float erf_(float x) { | |
| float z = metal::fabs(x), t = 1.0f / (1.0f + 0.5f * z); | |
| float r = t * metal::exp(-z * z - 1.26551223f + t * (1.00002368f + t * (0.37409196f + t * (0.09678418f + | |
| t * (-0.18628806f + t * (0.27886807f + t * (-1.13520398f + t * (1.48851587f + | |
| t * (-0.82215223f + t * 0.17087277f))))))))); | |
| return x >= 0.0f ? 1.0f - r : r - 1.0f; | |
| } | |
| """ | |
| def _linear_kernel(ln, act, res, add, bits, rows, split): | |
| """Each simdgroup owns `rows` output rows and 1/split of the K range (lane l reads k = 8l, 8l + 256, ... of its | |
| part) and accumulates the LayerNorm sums of x alongside the dot products: no normalisation pass before the weight | |
| stream starts. LN(x).w = rstd * (sum (x - x0) g w - (mean - x0) sum g w) + sum b w, the shift x0 = x[t, 0] | |
| guarding the single-pass variance against cancellation. With split > 1 the parts meet in threadgroup memory.""" | |
| ins = ["x"] + (["wq", "scales", "biases"] if bits else ["w"]) + (["lnw", "lnb"] if ln else []) + \ | |
| (["res"] if res else []) + (["cst"] if add else []) | |
| if bits == 8: | |
| load_w = """{ | |
| const device uint* p = wq + ((row0 + r) * K + k) / 4; | |
| uint a = p[0], b = p[1]; | |
| float sc = float(scales[((row0 + r) * K + k) / 64]), bi = float(biases[((row0 + r) * K + k) / 64]); | |
| for (int j = 0; j < 4; ++j) { wv[r][j] = float((a >> (8 * j)) & 0xff) * sc + bi; | |
| wv[r][4 + j] = float((b >> (8 * j)) & 0xff) * sc + bi; } }""" | |
| else: | |
| load_w = """{ | |
| const device half4* p = (const device half4*)(w + (row0 + r) * K + k); | |
| half4 a = p[0], b = p[1]; | |
| for (int j = 0; j < 4; ++j) { wv[r][j] = float(a[j]); wv[r][4 + j] = float(b[j]); } }""" | |
| ln_w = """ | |
| for (int j = 0; j < 8; ++j) { | |
| float g = float(lnw[k + j]), b = float(lnb[k + j]); | |
| for (int r = 0; r < R; ++r) { gw[r] += g * wv[r][j]; bw[r] += b * wv[r][j]; wv[r][j] *= g; } | |
| }""" if ln else "" | |
| ln_x = """ | |
| for (int j = 0; j < 8; ++j) { xv[j] -= x0[t]; sx[t] += xv[j]; sxx[t] += xv[j] * xv[j]; }""" if ln else "" | |
| ln_out = """ | |
| float m = SUM(sx[t]) / K, var = SUM(sxx[t]) / K - m * m; | |
| float rstd = metal::rsqrt(metal::max(var, 0.0f) + 1e-5f); | |
| for (int r = 0; r < R; ++r) out[r] = rstd * (out[r] - m * gws[r]) + bws[r];""" if ln else "" | |
| epilogue = "".join([ | |
| "v = 0.5f * v * (1.0f + erf_(v * 0.70710678f));" if act else "", | |
| "v += float(res[t * N + row0 + r]);" if res else "", | |
| "v += float(cst[row0 + r]);" if add else ""]) | |
| reduce = f""" | |
| threadgroup float part_acc[{SIMDS}][R][T], part_g[{SIMDS}][R], part_b[{SIMDS}][R], part_x[{SIMDS}][T], part_xx[{SIMDS}][T]; | |
| for (int r = 0; r < R; ++r) {{ for (int t = 0; t < T; ++t) {{ float v = simd_sum(acc[r][t]); if (lane == 0) part_acc[sg][r][t] = v; }} | |
| float a = simd_sum(gw[r]), b = simd_sum(bw[r]); if (lane == 0) {{ part_g[sg][r] = a; part_b[sg][r] = b; }} }} | |
| for (int t = 0; t < T; ++t) {{ float a = simd_sum(sx[t]), b = simd_sum(sxx[t]); if (lane == 0) {{ part_x[sg][t] = a; part_xx[sg][t] = b; }} }} | |
| threadgroup_barrier(mem_flags::mem_threadgroup); | |
| if (part != 0) return; | |
| for (int r = 0; r < R; ++r) {{ | |
| float a = 0.0f, b = 0.0f; | |
| for (int q = 0; q < S; ++q) {{ a += part_g[sg + q][r]; b += part_b[sg + q][r]; }} | |
| gw[r] = a; bw[r] = b; | |
| for (int t = 0; t < T; ++t) {{ float c = 0.0f; for (int q = 0; q < S; ++q) c += part_acc[sg + q][r][t]; acc[r][t] = c; }} | |
| }} | |
| for (int t = 0; t < T; ++t) {{ float a = 0.0f, b = 0.0f; for (int q = 0; q < S; ++q) {{ a += part_x[sg + q][t]; b += part_xx[sg + q][t]; }} | |
| sx[t] = a; sxx[t] = b; }} | |
| #define SUM(v) (v)""" if split > 1 else "#define SUM(v) simd_sum(v)" | |
| src = f""" | |
| constexpr int R = {rows}, S = {split}; | |
| uint sg = simdgroup_index_in_threadgroup, lane = thread_index_in_simdgroup, part = sg % S; | |
| uint row0 = (threadgroup_position_in_grid.x * ({SIMDS} / S) + sg / S) * R; | |
| float acc[R][T], wv[R][8], gw[R], bw[R], x0[T], sx[T], sxx[T]; | |
| for (int r = 0; r < R; ++r) {{ gw[r] = 0.0f; bw[r] = 0.0f; for (int t = 0; t < T; ++t) acc[r][t] = 0.0f; }} | |
| for (int t = 0; t < T; ++t) {{ x0[t] = {"float(x[t * K])" if ln else "0.0f"}; sx[t] = 0.0f; sxx[t] = 0.0f; }} | |
| for (uint k = part * (K / S) + lane * 8; k < (part + 1) * (K / S); k += 256) {{ | |
| for (int r = 0; r < R; ++r) {load_w} | |
| {ln_w} | |
| for (int t = 0; t < T; ++t) {{ | |
| const device half4* xp = (const device half4*)(x + t * K + k); | |
| half4 xa = xp[0], xb = xp[1]; | |
| float xv[8] = {{float(xa[0]), float(xa[1]), float(xa[2]), float(xa[3]), | |
| float(xb[0]), float(xb[1]), float(xb[2]), float(xb[3])}}; | |
| {ln_x} | |
| for (int r = 0; r < R; ++r) {{ float s = 0.0f; for (int j = 0; j < 8; ++j) s += xv[j] * wv[r][j]; acc[r][t] += s; }} | |
| }} | |
| }} | |
| {reduce} | |
| float gws[R], bws[R]; | |
| for (int r = 0; r < R; ++r) {{ gws[r] = SUM(gw[r]); bws[r] = SUM(bw[r]); }} | |
| for (int t = 0; t < T; ++t) {{ | |
| float out[R]; | |
| for (int r = 0; r < R; ++r) out[r] = SUM(acc[r][t]); | |
| {ln_out} | |
| if (lane == 0) for (int r = 0; r < R; ++r) {{ float v = out[r]; {epilogue} y[t * N + row0 + r] = v; }} | |
| }} | |
| """ | |
| return mx.fast.metal_kernel(name=f"maya_linear_{int(ln)}{int(act)}{int(res)}{int(add)}_{bits or 16}_{rows}_{split}", | |
| input_names=ins, output_names=["y"], source=src, header=_ERF) | |
| # Launch shape, swept on an M5 Pro over a chain of real-size depth layers: one row per simdgroup (most threadgroups in | |
| # flight) everywhere; 8-bit weights also split the K range of the 1024-row matrices across two simdgroups. | |
| # fp16 129 us/layer (vs 199 with two rows per simdgroup), 8-bit 94 us/layer. | |
| def _shape(T, K, N, bits): | |
| return 1, (2 if bits and N <= 1024 else 1) | |
| def linear(x, lin, ln=None, gelu=False, res=None, add=None, rows=None, split=None): | |
| """x (T, K) -> (T, N) = [res +] [add +] act(LN(x) @ W.T); lin: mlx_model.Linear (dense or 8-bit).""" | |
| T, K = x.shape | |
| bits = lin.bits | |
| if bits not in (None, 8): | |
| raise ValueError("fused depth kernels support dense or 8-bit weights") | |
| N = (lin.wq if bits else lin.w).shape[0] | |
| rows = rows or _shape(T, K, N, bits)[0] | |
| split = split or _shape(T, K, N, bits)[1] | |
| while (K // split) % 256 or N % (SIMDS // split * rows): | |
| split //= 2 | |
| ins = [x] + ([lin.wq, lin.scales, lin.biases] if bits else [lin.w]) + (list(ln) if ln is not None else []) + \ | |
| ([res] if res is not None else []) + ([add] if add is not None else []) | |
| k = _linear_kernel(ln is not None, gelu, res is not None, add is not None, bits, rows, split) | |
| groups = N // (SIMDS // split * rows) | |
| return k(inputs=ins, template=[("T", T), ("K", K), ("N", N)], grid=(groups * 32 * SIMDS, 1, 1), | |
| threadgroup=(32 * SIMDS, 1, 1), output_shapes=[(T, N)], output_dtypes=[x.dtype])[0] | |
| _ATTEND = """ | |
| // one simdgroup per (head, new position t); lane j <-> key j (<= 32 keys), lanes d, d+32 <-> head dims | |
| uint h = threadgroup_position_in_grid.x, t = threadgroup_position_in_grid.y, lane = thread_index_in_simdgroup; | |
| const uint D = H * HD, L = P + T; | |
| for (uint d = lane; d < HD; d += 32) { // this pass's K/V rows -> the cache | |
| kv_out[(h * L + P + t) * HD + d] = qkv[t * 3 * D + D + h * HD + d]; | |
| kv_out[((H + h) * L + P + t) * HD + d] = qkv[t * 3 * D + 2 * D + h * HD + d]; | |
| } | |
| if (t == 0) for (uint j = 0; j < P; ++j) for (uint d = lane; d < HD; d += 32) { | |
| kv_out[(h * L + j) * HD + d] = kv[(h * P + j) * HD + d]; | |
| kv_out[((H + h) * L + j) * HD + d] = kv[((H + h) * P + j) * HD + d]; | |
| } | |
| if (LAST && t != T - 1) return; | |
| uint pos = P + t; // causal: keys 0..pos | |
| float s = -INFINITY; | |
| if (lane <= pos) { | |
| const device half* q = qkv + t * 3 * D + h * HD; | |
| const device half* k = lane < P ? kv + (h * P + lane) * HD : qkv + (lane - P) * 3 * D + D + h * HD; | |
| float a = 0.0f; | |
| for (uint d = 0; d < HD; ++d) a += float(q[d]) * float(k[d]); | |
| s = a * SCALE; | |
| } | |
| float m = simd_max(s); | |
| float p = lane <= pos ? metal::exp(s - m) : 0.0f; | |
| p /= simd_sum(p); | |
| uint qi = LAST ? 0 : t; | |
| for (uint d = lane; d < HD; d += 32) { | |
| float o = 0.0f; | |
| for (uint j = 0; j <= pos; ++j) { | |
| float v = j < P ? float(kv[((H + h) * P + j) * HD + d]) : float(qkv[(j - P) * 3 * D + 2 * D + h * HD + d]); | |
| o += simd_shuffle(p, (ushort)j) * v; | |
| } | |
| out[qi * D + h * HD + d] = o; | |
| } | |
| """ | |
| def _attend_kernel(scale): | |
| return mx.fast.metal_kernel(name="maya_attend", input_names=["qkv", "kv"], output_names=["out", "kv_out"], | |
| source=_ATTEND.replace("SCALE", repr(float(scale)) + "f")) | |
| def attend(qkv, kv, heads, head_dim, last=False): | |
| """qkv (T, 3*D) of the new positions, kv (2, H, P, HD) the band's cache or None -> (attention output (T or 1, D), | |
| cache (2, H, P + T, HD)). Causal; last=True computes only the last position's output.""" | |
| T = qkv.shape[0] | |
| P = 0 if kv is None else kv.shape[2] | |
| if P + T > 32: | |
| raise ValueError("attend handles at most 32 positions") | |
| D = heads * head_dim | |
| if kv is None: # nothing cached yet: a placeholder the kernel never reads (P = 0) | |
| kv = mx.zeros((2, heads, 1, head_dim), qkv.dtype) | |
| out, kv_out = _attend_kernel(head_dim ** -0.5)( | |
| inputs=[qkv, kv], template=[("H", heads), ("HD", head_dim), ("P", P), ("T", T), ("LAST", int(last))], | |
| grid=(heads * 32, T, 1), threadgroup=(32, 1, 1), | |
| output_shapes=[(1 if last else T, D), (2, heads, P + T, head_dim)], output_dtypes=[qkv.dtype, qkv.dtype]) | |
| return out, kv_out | |
| _SAMPLE = """ | |
| // one threadgroup of 1024 threads, 2 logits each: exact k-th largest (radix select on order-preserving keys), | |
| // then Gumbel-max over the kept set (ties with the k-th value are kept, as torch's `logits < kth` mask does) | |
| uint tid = thread_position_in_threadgroup.x, lane = thread_index_in_simdgroup, sg = simdgroup_index_in_threadgroup; | |
| threadgroup atomic_uint hist[256]; | |
| threadgroup uint sh_prefix, sh_need, sh_code; | |
| threadgroup float best_s[32]; | |
| threadgroup uint best_i[32]; | |
| uint key[2]; float val[2]; | |
| for (int e = 0; e < 2; ++e) { | |
| float f = float(logits[tid + e * 1024]); | |
| uint u = as_type<uint>(f); | |
| key[e] = (u & 0x80000000u) ? ~u : (u | 0x80000000u); | |
| val[e] = f; | |
| } | |
| if (tid == 0) { sh_prefix = 0; sh_need = TOPK; } | |
| for (int shift = 24; shift >= 0; shift -= 8) { | |
| if (tid < 256) atomic_store_explicit(&hist[tid], 0u, memory_order_relaxed); | |
| threadgroup_barrier(mem_flags::mem_threadgroup); | |
| uint prefix = sh_prefix; | |
| for (int e = 0; e < 2; ++e) | |
| if (shift == 24 || (key[e] >> (shift + 8)) == prefix) | |
| atomic_fetch_add_explicit(&hist[(key[e] >> shift) & 0xff], 1u, memory_order_relaxed); | |
| threadgroup_barrier(mem_flags::mem_threadgroup); | |
| if (sg == 0) { // digit holding the need-th largest, scanning bins from the top | |
| uint c[8], tot = 0; | |
| for (int i = 0; i < 8; ++i) { c[i] = atomic_load_explicit(&hist[255 - (lane * 8 + i)], memory_order_relaxed); tot += c[i]; } | |
| uint before = simd_prefix_exclusive_sum(tot), need = sh_need; | |
| if (before < need && need <= before + tot) { | |
| uint run = before; | |
| for (int i = 0; i < 8; ++i) { | |
| if (need <= run + c[i]) { sh_prefix = (prefix << 8) | (255 - (lane * 8 + i)); sh_need = need - run; break; } | |
| run += c[i]; | |
| } | |
| } | |
| } | |
| threadgroup_barrier(mem_flags::mem_threadgroup); | |
| } | |
| uint kth = sh_prefix; | |
| float bs = -INFINITY; uint bi = 0xffffffffu; | |
| for (int e = 0; e < 2; ++e) { | |
| uint i = tid + e * 1024; | |
| if (key[e] >= kth) { | |
| float s = val[e] * INV_TEMP + gumbel[LEVEL * 2048 + i]; | |
| if (s > bs || (s == bs && i < bi)) { bs = s; bi = i; } | |
| } | |
| } | |
| for (ushort off = 16; off > 0; off >>= 1) { | |
| float os = simd_shuffle_down(bs, off); uint oi = simd_shuffle_down(bi, off); | |
| if (os > bs || (os == bs && oi < bi)) { bs = os; bi = oi; } | |
| } | |
| if (lane == 0) { best_s[sg] = bs; best_i[sg] = bi; } | |
| threadgroup_barrier(mem_flags::mem_threadgroup); | |
| if (sg == 0) { | |
| bs = best_s[lane]; bi = best_i[lane]; | |
| for (ushort off = 16; off > 0; off >>= 1) { | |
| float os = simd_shuffle_down(bs, off); uint oi = simd_shuffle_down(bi, off); | |
| if (os > bs || (os == bs && oi < bi)) { bs = os; bi = oi; } | |
| } | |
| if (lane == 0) { sh_code = bi; code[0] = int(bi); } | |
| } | |
| threadgroup_barrier(mem_flags::mem_threadgroup); | |
| uint c = sh_code; | |
| for (uint r = 0; r < N_PREV; ++r) prefix_out[r * 1024 + tid] = prefix[r * 1024 + tid]; | |
| if (EMBED) prefix_out[N_PREV * 1024 + tid] = table[(LEVEL * 2048 + c) * 1024 + tid]; | |
| """ | |
| def _sample_kernel(inv_temp, top_k): | |
| src = _SAMPLE.replace("INV_TEMP", repr(float(inv_temp)) + "f").replace("TOPK", f"{int(top_k)}u") | |
| return mx.fast.metal_kernel(name=f"maya_sample_{top_k}", input_names=["logits", "gumbel", "table", "prefix"], | |
| output_names=["code", "prefix_out"], source=src) | |
| def sample(logits, gumbel, level, table, prefix, inv_temp, top_k, embed=True): | |
| """logits (1, 2048) of depth level `level`; gumbel (32, 2048) float32 noise of this frame (row = level); table | |
| (31, 2048, D) code | |
| embeddings; prefix (n, D) -> (code (1,) int32, prefix + [table[level, code]] (n+1, D), or prefix if not embed).""" | |
| n, D = prefix.shape | |
| if logits.shape[-1] != 2048 or D != 1024: | |
| raise ValueError("sample is built for 2048 codes and D = 1024") | |
| return _sample_kernel(inv_temp, top_k)( | |
| inputs=[logits, gumbel, table, prefix], template=[("LEVEL", level), ("N_PREV", n), ("EMBED", int(embed))], | |
| grid=(1024, 1, 1), threadgroup=(1024, 1, 1), output_shapes=[(1,), (n + int(embed), D)], | |
| output_dtypes=[mx.int32, prefix.dtype]) | |