Text Generation
MLX
English
diffusion_gemma
nvfp4
diffusion-gemma
diffusion-lm
structured-output
decision-making
conversational
modelopt
Instructions to use GenbaLabs/super-decider-mlx with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use GenbaLabs/super-decider-mlx with MLX:
# Make sure mlx-lm is installed # pip install --upgrade mlx-lm # Generate text with mlx-lm from mlx_lm import load, generate model, tokenizer = load("GenbaLabs/super-decider-mlx") prompt = "Write a story about Einstein" messages = [{"role": "user", "content": prompt}] prompt = tokenizer.apply_chat_template( messages, add_generation_prompt=True ) text = generate(model, tokenizer, prompt=prompt, verbose=True) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Pi
How to use GenbaLabs/super-decider-mlx with Pi:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "GenbaLabs/super-decider-mlx"
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "mlx-lm": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "GenbaLabs/super-decider-mlx" } ] } } }Run Pi
# Start Pi in your project directory: pi
- MLX LM
How to use GenbaLabs/super-decider-mlx with MLX LM:
Generate or start a chat session
# Install MLX LM uv tool install mlx-lm # Interactive chat REPL mlx_lm.chat --model "GenbaLabs/super-decider-mlx"
Run an OpenAI-compatible server
# Install MLX LM uv tool install mlx-lm # Start the server mlx_lm.server --model "GenbaLabs/super-decider-mlx" # Calling the OpenAI-compatible server with curl curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "GenbaLabs/super-decider-mlx", "messages": [ {"role": "user", "content": "Hello"} ] }' - Hermes Agent
How to use GenbaLabs/super-decider-mlx with Hermes Agent:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "GenbaLabs/super-decider-mlx"
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default GenbaLabs/super-decider-mlx
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use GenbaLabs/super-decider-mlx with OpenClaw:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "GenbaLabs/super-decider-mlx"
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "GenbaLabs/super-decider-mlx" \ --custom-provider-id mlx-lm \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Download superdecider/modeling.py from GenbaLabs/super-decider-mlx: direct link, hf CLI and curl.
- Browser
- Download file 21.9 kB
-
https://huggingface.co/GenbaLabs/super-decider-mlx/resolve/main/superdecider/modeling.py
- Command line
-
hf download hf://GenbaLabs/super-decider-mlx/superdecider/modeling.py
-
curl -L -o modeling.py https://huggingface.co/GenbaLabs/super-decider-mlx/resolve/main/superdecider/modeling.py
21.9 kB
| """MLX implementation of the DiffusionGemma text model (encoder + block-diffusion decoder). | |
| Shape conventions: activations are [B, L, hidden]; attention is [B, heads, L, head_dim]; the KV cache keeps | |
| [B, heads, S, head_dim] per layer. Weight names follow the HF checkpoint (``model.decoder.*``), which is also | |
| where the encoder's weights live (in the checkpoint the encoder shares them). A linear weight is either a float | |
| array ``X.weight`` or NVFP4-packed ``X.weight`` (uint32, 8 nibbles per word) + ``X.scales`` (uint8, group 16). | |
| Batch size is 1: the port serves one request at a time, so no padding/batch masks are needed. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| from typing import Any | |
| import mlx.core as mx | |
| from .config import DiffusionGemmaConfig, TextConfig | |
| NVFP4 = dict(group_size=16, bits=4, mode="nvfp4") | |
| # --------------------------------------------------------------------------- weights | |
| def _packed(sd: dict[str, mx.array], name: str) -> tuple[mx.array, mx.array | None, mx.array | None]: | |
| """(weight, scales, global_scale) for a checkpoint linear name ending in ``.weight``.""" | |
| stem = name[: -len(".weight")] if name.endswith(".weight") else name | |
| return sd[name], sd.get(stem + ".scales"), sd.get(stem + ".global_scale") | |
| class Linear: | |
| """``x @ W.T`` for a float or NVFP4-packed weight.""" | |
| def __init__(self, weight: mx.array, scales: mx.array | None = None, global_scale: mx.array | None = None): | |
| self.weight = weight | |
| self.scales = scales | |
| self.global_scale = global_scale | |
| def __call__(self, x: mx.array) -> mx.array: | |
| if self.scales is None: | |
| return x @ self.weight.T | |
| if self.global_scale is not None: | |
| # mlx 0.32.4 has no global_scale on quantized_matmul (only gather_qmm); no plain linear in | |
| # this checkpoint is packed, so this is a guard, not a path. | |
| raise TypeError("packed Linear with a global scale is not supported") | |
| return mx.quantized_matmul(x, self.weight, scales=self.scales, transpose=True, **NVFP4) | |
| class RMSNorm: | |
| """DiffusionGemma RMSNorm: ``x / sqrt(mean(x^2) + eps) * w``, computed in float32.""" | |
| def __init__(self, weight: mx.array | None, eps: float): | |
| self.weight = weight # None -> no scale (v_norm, router norm, self-conditioning post norm) | |
| self.eps = eps | |
| def __call__(self, x: mx.array) -> mx.array: | |
| dtype = x.dtype | |
| x = x.astype(mx.float32) | |
| x = x * mx.power(mx.mean(x * x, axis=-1, keepdims=True) + self.eps, -0.5) | |
| if self.weight is not None: | |
| x = x * self.weight.astype(mx.float32) | |
| return x.astype(dtype) | |
| def gelu_tanh(x: mx.array) -> mx.array: | |
| return 0.5 * x * (1.0 + mx.tanh(0.7978845608028654 * (x + 0.044715 * x * x * x))) | |
| # --------------------------------------------------------------------------- attention | |
| def rope_tables(text: TextConfig, layer_type: str, positions: mx.array) -> tuple[mx.array, mx.array]: | |
| """cos/sin of shape [L, head_dim/2]; both head halves rotate with the same angles (reference layout).""" | |
| params = text.rope_parameters[layer_type] | |
| head_dim = text.global_head_dim if layer_type == "full_attention" else text.head_dim | |
| base = params["rope_theta"] | |
| factor = params.get("partial_rotary_factor", 1.0) | |
| if params.get("rope_type", "default") == "proportional": | |
| # transformers' proportional RoPE: rotate the first dims, zero frequencies pad the rest. | |
| angles = int(factor * head_dim // 2) | |
| exponents = mx.arange(0, 2 * angles, 2, dtype=mx.float32) / head_dim | |
| inv_freq = mx.concatenate( | |
| [1.0 / (base**exponents), mx.zeros(head_dim // 2 - angles, dtype=mx.float32)] | |
| ) | |
| else: | |
| dim = int(factor * head_dim) | |
| inv_freq = 1.0 / (base ** (mx.arange(0, dim, 2, dtype=mx.float32) / dim)) | |
| freqs = positions.astype(mx.float32)[:, None] * inv_freq[None, :] | |
| return mx.cos(freqs), mx.sin(freqs) | |
| def apply_rope(x: mx.array, cos: mx.array, sin: mx.array) -> mx.array: | |
| """x [B, L, H, D], cos/sin [L, D/2].""" | |
| half = x.shape[-1] // 2 | |
| cos, sin = cos[None, :, None, :], sin[None, :, None, :] | |
| x1, x2 = x[..., :half], x[..., half:] | |
| return mx.concatenate([x1 * cos - x2 * sin, x1 * sin + x2 * cos], axis=-1) | |
| class KVCache: | |
| """Per-layer prefix cache. Sliding layers keep only ``sliding_window - 1`` tokens, like the reference.""" | |
| def __init__(self, text: TextConfig): | |
| n = text.num_hidden_layers | |
| self.text = text | |
| self.keys: list[mx.array | None] = [None] * n | |
| self.values: list[mx.array | None] = [None] * n | |
| self.seen = [0] * n # tokens encoded so far; can exceed the kept length on sliding layers | |
| def append(self, i: int, k: mx.array, v: mx.array) -> tuple[mx.array, mx.array]: | |
| """Append [B, H, L, D] states; returns all states visible to this forward.""" | |
| self.seen[i] += k.shape[2] | |
| if self.keys[i] is not None: | |
| k = mx.concatenate([self.keys[i], k], axis=2) | |
| v = mx.concatenate([self.values[i], v], axis=2) | |
| if self.text.is_sliding(i): | |
| keep = self.text.sliding_window - 1 | |
| self.keys[i] = k[:, :, -keep:, :] if k.shape[2] > keep else k | |
| self.values[i] = v[:, :, -keep:, :] if v.shape[2] > keep else v | |
| else: | |
| self.keys[i], self.values[i] = k, v | |
| return k, v | |
| def prefix(self, i: int) -> tuple[mx.array | None, mx.array | None, int]: | |
| """Cached K/V and the absolute position of the first cached token.""" | |
| if self.keys[i] is None: | |
| return None, None, 0 | |
| return self.keys[i], self.values[i], self.seen[i] - self.keys[i].shape[2] | |
| def copy(self) -> KVCache: | |
| """Shallow copy: ``append`` always replaces entries, so a copy can be extended without touching this one.""" | |
| other = KVCache.__new__(KVCache) | |
| other.text = self.text | |
| other.keys = list(self.keys) | |
| other.values = list(self.values) | |
| other.seen = list(self.seen) | |
| return other | |
| class Attention: | |
| def __init__(self, text: TextConfig, layer_idx: int, sd: dict[str, mx.array], prefix: str): | |
| self.layer_idx = layer_idx | |
| self.head_dim = text.layer_head_dim(layer_idx) | |
| self.num_heads = text.num_attention_heads | |
| self.num_kv_heads = text.layer_num_kv_heads(layer_idx) | |
| self.scaling = 1.0 # the reference does not scale by 1/sqrt(head_dim) | |
| self.q_proj = Linear(*_packed(sd, f"{prefix}.q_proj.weight")) | |
| self.k_proj = Linear(*_packed(sd, f"{prefix}.k_proj.weight")) | |
| v_proj = f"{prefix}.v_proj.weight" | |
| # Full-attention layers have no v_proj: V is K after k_norm + rope. | |
| self.v_proj = Linear(*_packed(sd, v_proj)) if v_proj in sd else None | |
| self.o_proj = Linear(*_packed(sd, f"{prefix}.o_proj.weight")) | |
| eps = text.rms_norm_eps | |
| self.q_norm = RMSNorm(sd[f"{prefix}.q_norm.weight"], eps) | |
| self.k_norm = RMSNorm(sd[f"{prefix}.k_norm.weight"], eps) | |
| self.v_norm = RMSNorm(None, eps) # no scale | |
| def __call__(self, x, cos, sin, mask, cache: KVCache, write: bool) -> mx.array: | |
| b, length, _ = x.shape | |
| hd, nh, nkv = self.head_dim, self.num_heads, self.num_kv_heads | |
| cos, sin = cos.astype(x.dtype), sin.astype(x.dtype) | |
| q = apply_rope(self.q_norm(self.q_proj(x).reshape(b, length, nh, hd)), cos, sin).transpose(0, 2, 1, 3) | |
| k = self.k_proj(x).reshape(b, length, nkv, hd) | |
| v = k if self.v_proj is None else self.v_proj(x).reshape(b, length, nkv, hd) | |
| k = apply_rope(self.k_norm(k), cos, sin) | |
| v = self.v_norm(v) | |
| k, v = k.transpose(0, 2, 1, 3), v.transpose(0, 2, 1, 3) | |
| if write: # encoder: append to the prefix cache | |
| k, v = cache.append(self.layer_idx, k, v) | |
| else: # diffusion decoder: cached prefix + canvas, read-only | |
| pk, pv, _ = cache.prefix(self.layer_idx) | |
| if pk is not None: | |
| if pk.shape[0] != b: # batched draws share the one encoded prefix | |
| pk = mx.broadcast_to(pk, (b,) + pk.shape[1:]) | |
| pv = mx.broadcast_to(pv, (b,) + pv.shape[1:]) | |
| k = mx.concatenate([pk, k], axis=2) | |
| v = mx.concatenate([pv, v], axis=2) | |
| out = mx.fast.scaled_dot_product_attention( | |
| q, k, v, scale=self.scaling, mask=mask | |
| ) | |
| return self.o_proj(out.transpose(0, 2, 1, 3).reshape(b, length, nh * hd)) | |
| # --------------------------------------------------------------------------- MoE | |
| class Router: | |
| def __init__(self, text: TextConfig, sd: dict[str, mx.array], prefix: str): | |
| self.top_k = text.top_k_experts | |
| self.scalar_root_size = text.hidden_size**-0.5 | |
| self.norm = RMSNorm(None, text.rms_norm_eps) | |
| self.proj = Linear(*_packed(sd, f"{prefix}.proj.weight")) | |
| self.scale = sd[f"{prefix}.scale"] | |
| self.per_expert_scale = sd[f"{prefix}.per_expert_scale"] | |
| def __call__(self, x: mx.array) -> tuple[mx.array, mx.array]: | |
| """x [T, hidden] -> (top-k expert ids, top-k weights), both [T, top_k].""" | |
| h = self.norm(x) * self.scale * self.scalar_root_size | |
| probs = mx.softmax(self.proj(h).astype(mx.float32), axis=-1) | |
| idx = mx.argpartition(-probs, kth=self.top_k, axis=-1)[..., : self.top_k] | |
| weights = mx.take_along_axis(probs, idx, axis=-1) | |
| order = mx.argsort(-weights, axis=-1) # descending, like torch.topk | |
| idx = mx.take_along_axis(idx, order, axis=-1) | |
| weights = mx.take_along_axis(weights, order, axis=-1) | |
| weights = weights / weights.sum(axis=-1, keepdims=True) | |
| return idx, weights * self.per_expert_scale[idx] | |
| def _expert_weights(sd: dict[str, mx.array], prefix: str, num_experts: int, kinds: list[str]): | |
| """Stacked (weight, scales, global_scale) over experts, one entry per kind concatenated on the output axis. | |
| Takes the converted artifact's pre-stacked tensors as they are (``experts.gate_up_proj`` / | |
| ``experts.down_proj``, with ``global_scale``) and falls back to stacking the checkpoint's per-expert | |
| layout (``experts.{e}.gate_proj`` etc.) for float weights, as the tiny parity tests need. | |
| """ | |
| base = f"{prefix}.experts" | |
| if f"{base}.{kinds[0]}.weight" in sd: | |
| return _packed(sd, f"{base}.{kinds[0]}.weight") | |
| if f"{base}.{kinds[0]}" in sd: # reference layout: one Parameter, no suffix (packed or float) | |
| name = f"{base}.{kinds[0]}" | |
| if sd[name].dtype in (mx.uint32, mx.uint8): | |
| return sd[name], sd.get(name + ".scales"), sd.get(name + ".global_scale") | |
| return sd[name], None, None | |
| weights, scales = [], [] | |
| for e in range(num_experts): | |
| parts = [_packed(sd, f"{base}.{e}.{kind}.weight") for kind in kinds] | |
| weights.append(mx.concatenate([p[0] for p in parts], axis=0)) | |
| scales.append(None if parts[0][1] is None else mx.concatenate([p[1] for p in parts], axis=0)) | |
| return mx.stack(weights), None if scales[0] is None else mx.stack(scales), None | |
| class Experts: | |
| """128-expert MLP; gate and up stacked on the output axis, as in the reference's ``gate_up_proj``.""" | |
| def __init__(self, text: TextConfig, sd: dict[str, mx.array], prefix: str): | |
| n = text.num_experts | |
| kinds = ( | |
| ["gate_up_proj"] | |
| if f"{prefix}.experts.gate_up_proj.weight" in sd or f"{prefix}.experts.gate_up_proj" in sd | |
| else ["gate_proj", "up_proj"] | |
| ) | |
| self.gate_up, self.gate_up_scales, self.gate_up_gs = _expert_weights(sd, prefix, n, kinds) | |
| self.down, self.down_scales, self.down_gs = _expert_weights(sd, prefix, n, ["down_proj"]) | |
| def __call__(self, x: mx.array, idx: mx.array, weights: mx.array) -> mx.array: | |
| """x [T, hidden] with idx/weights [T, top_k] -> [T, hidden]. | |
| Expert assignments are sorted by expert before the gathers: unsorted nvfp4 gather_qmm is ~9x slower | |
| here (measured on this Mac, 9 ms vs 1 ms per call), because the kernel cannot stream the weights. | |
| """ | |
| t, k = idx.shape | |
| flat = idx.reshape(-1) | |
| order = mx.argsort(flat) | |
| idx_s = mx.take(flat, order) | |
| xs = mx.take(x, order // k, axis=0) | |
| gate_up = self._mm(xs, self.gate_up, self.gate_up_scales, self.gate_up_gs, idx_s) | |
| gate, up = mx.split(gate_up, 2, axis=-1) | |
| h = gelu_tanh(gate) * up | |
| out = self._mm(h.reshape(t * k, -1), self.down, self.down_scales, self.down_gs, idx_s) | |
| out = mx.take(out, mx.argsort(order), axis=0).reshape(t, k, -1) | |
| # router probabilities are float32; promote the 9x bigger activation tensor instead of the weights | |
| return (out * weights[..., None].astype(out.dtype)).sum(axis=-2) | |
| def _mm(x: mx.array, w: mx.array, scales: mx.array | None, gs: mx.array | None, idx: mx.array) -> mx.array: | |
| """x [N, in] with sorted idx [N] expert ids -> [N, out].""" | |
| x = mx.expand_dims(x, -2) | |
| if scales is None: | |
| out = mx.gather_mm(x, w.swapaxes(-1, -2), rhs_indices=idx, sorted_indices=True) | |
| else: | |
| out = mx.gather_qmm( | |
| x, w, scales, rhs_indices=idx, transpose=True, sorted_indices=True, global_scale=gs, **NVFP4 | |
| ) | |
| return out[:, 0, :] | |
| # --------------------------------------------------------------------------- transformer blocks | |
| class Layer: | |
| def __init__(self, text: TextConfig, layer_idx: int, sd: dict[str, mx.array], prefix: str): | |
| self.self_attn = Attention(text, layer_idx, sd, f"{prefix}.self_attn") | |
| self.gate_proj = Linear(*_packed(sd, f"{prefix}.mlp.gate_proj.weight")) | |
| self.up_proj = Linear(*_packed(sd, f"{prefix}.mlp.up_proj.weight")) | |
| self.down_proj = Linear(*_packed(sd, f"{prefix}.mlp.down_proj.weight")) | |
| eps = text.rms_norm_eps | |
| self.input_layernorm = RMSNorm(sd[f"{prefix}.input_layernorm.weight"], eps) | |
| self.post_attention_layernorm = RMSNorm(sd[f"{prefix}.post_attention_layernorm.weight"], eps) | |
| self.pre_feedforward_layernorm = RMSNorm(sd[f"{prefix}.pre_feedforward_layernorm.weight"], eps) | |
| self.pre_feedforward_layernorm_2 = RMSNorm(sd[f"{prefix}.pre_feedforward_layernorm_2.weight"], eps) | |
| self.post_feedforward_layernorm = RMSNorm(sd[f"{prefix}.post_feedforward_layernorm.weight"], eps) | |
| self.post_feedforward_layernorm_1 = RMSNorm(sd[f"{prefix}.post_feedforward_layernorm_1.weight"], eps) | |
| self.post_feedforward_layernorm_2 = RMSNorm(sd[f"{prefix}.post_feedforward_layernorm_2.weight"], eps) | |
| self.layer_scalar = sd[f"{prefix}.layer_scalar"] | |
| self.router = Router(text, sd, f"{prefix}.router") | |
| self.experts = Experts(text, sd, prefix) | |
| def __call__(self, x, cos, sin, mask, cache: KVCache, write: bool) -> mx.array: | |
| attn = self.self_attn(self.input_layernorm(x), cos, sin, mask, cache, write) | |
| residual = x + self.post_attention_layernorm(attn) | |
| h = self.pre_feedforward_layernorm(residual) | |
| dense = self.post_feedforward_layernorm_1(self.down_proj(gelu_tanh(self.gate_proj(h)) * self.up_proj(h))) | |
| flat = residual.reshape(-1, residual.shape[-1]) | |
| # Routing reads the raw residual; the experts read its pre-normalised version. | |
| idx, weights = self.router(flat) | |
| moe = self.experts(self.pre_feedforward_layernorm_2(flat), idx, weights) | |
| moe = self.post_feedforward_layernorm_2(moe.reshape(residual.shape)) | |
| return (residual + self.post_feedforward_layernorm(dense + moe)) * self.layer_scalar | |
| class SelfConditioning: | |
| def __init__(self, text: TextConfig, sd: dict[str, mx.array], prefix: str): | |
| eps = text.rms_norm_eps | |
| self.pre_norm = RMSNorm(sd[f"{prefix}.pre_norm.weight"], eps) | |
| self.post_norm = RMSNorm(None, eps) | |
| self.gate_proj = Linear(*_packed(sd, f"{prefix}.gate_proj.weight")) | |
| self.up_proj = Linear(*_packed(sd, f"{prefix}.up_proj.weight")) | |
| self.down_proj = Linear(*_packed(sd, f"{prefix}.down_proj.weight")) | |
| def __call__(self, inputs_embeds: mx.array, signal: mx.array) -> mx.array: | |
| h = self.pre_norm(signal) | |
| h = self.down_proj(gelu_tanh(self.gate_proj(h)) * self.up_proj(h)) | |
| return self.post_norm(inputs_embeds + h) | |
| # --------------------------------------------------------------------------- model | |
| class DiffusionGemma: | |
| """Text-only DiffusionGemma: an encoder that writes a prefix KV cache, and a diffusion decoder.""" | |
| def __init__(self, config: DiffusionGemmaConfig, state_dict: dict[str, Any]): | |
| text = config.text_config | |
| self.config = config | |
| self.text = text | |
| sd = {k: (v if isinstance(v, mx.array) else mx.array(v)) for k, v in state_dict.items()} | |
| self.embed_tokens = sd["model.decoder.embed_tokens.weight"] | |
| self.lm_head = sd.get("lm_head.weight", self.embed_tokens) | |
| # The reference downcasts sqrt(hidden_size) to the embedding dtype (bf16 -> 53.0 for 2816). | |
| self.embed_scale = mx.array(math.sqrt(text.hidden_size)).astype(self.embed_tokens.dtype) | |
| self.layers = [Layer(text, i, sd, f"model.decoder.layers.{i}") for i in range(text.num_hidden_layers)] | |
| self.norm = RMSNorm(sd["model.decoder.norm.weight"], text.rms_norm_eps) | |
| self.self_conditioning = SelfConditioning(text, sd, "model.decoder.self_conditioning") | |
| # -- encoder ----------------------------------------------------------------- | |
| def _encoder_mask(self, layer_idx: int, positions: mx.array, kv_len: int, kv_pos0: int, block_ids): | |
| """Causal (+ sliding window) mask, with bidirectional groups where ``block_ids`` agree.""" | |
| text = self.text | |
| kv_pos = mx.arange(kv_len) + kv_pos0 | |
| allowed = kv_pos[None, :] <= positions[:, None] | |
| if text.is_sliding(layer_idx): | |
| allowed &= kv_pos[None, :] > (positions - text.sliding_window)[:, None] | |
| if block_ids is not None: | |
| # keys already in the cache belong to no group of this chunk (-1), so they stay causal | |
| pad = kv_len - block_ids.shape[0] | |
| keys_ids = mx.concatenate([mx.full((pad,), -1, mx.int32), block_ids]) if pad > 0 else block_ids | |
| allowed |= (block_ids[:, None] == keys_ids[None, :]) & (block_ids[:, None] >= 0) | |
| # the dtype the attention kernel wants; the mask is cached per layer type, so cast it once here | |
| return mx.where(allowed, 0.0, -mx.inf)[None, None].astype(self.embed_tokens.dtype) | |
| def encode( | |
| self, | |
| input_ids: mx.array, | |
| positions: mx.array, | |
| cache: KVCache | None = None, | |
| block_ids: mx.array | None = None, | |
| ) -> tuple[mx.array, KVCache]: | |
| """Encode ``input_ids`` into the prefix cache; returns (last_hidden_state, cache). | |
| ``block_ids`` (one int per token) marks groups that attend bidirectionally inside the group -- the | |
| per-question blocks of the reference's ``create_masks_for_generate``; ``-1`` is plain causal text. | |
| As in the reference, they apply to the prefill only (they are dropped for later canvases). | |
| """ | |
| text = self.text | |
| cache = cache if cache is not None else KVCache(text) | |
| if block_ids is not None and cache.seen[0] != 0 and block_ids.shape[0] != input_ids.shape[1]: | |
| raise ValueError("block_ids must cover exactly the tokens being encoded") | |
| x = self.embed_tokens[input_ids] * self.embed_scale | |
| ropes, masks = {}, {} | |
| for i, layer in enumerate(self.layers): | |
| layer_type = text.layer_types[i] | |
| if layer_type not in ropes: | |
| ropes[layer_type] = rope_tables(text, layer_type, positions) | |
| _, _, kv_pos0 = cache.prefix(i) | |
| kv_len = cache.seen[i] - kv_pos0 + input_ids.shape[1] | |
| masks[layer_type] = self._encoder_mask(i, positions, kv_len, kv_pos0, block_ids) | |
| x = layer(x, *ropes[layer_type], masks[layer_type] if text.is_causal else None, cache, write=True) | |
| return self.norm(x), cache | |
| # -- decoder ----------------------------------------------------------------- | |
| def denoise( | |
| self, | |
| canvas_ids: mx.array, | |
| cache: KVCache, | |
| positions: mx.array, | |
| self_conditioning_logits: mx.array | None = None, | |
| read_positions: mx.array | None = None, | |
| vocab_ids: mx.array | None = None, | |
| ) -> mx.array: | |
| """One denoiser forward over the canvas; returns softcapped logits [B, canvas, vocab] in float32. | |
| ``read_positions``/``vocab_ids`` project only those canvas rows onto those vocabulary ids, which is | |
| what a constrained read needs and saves the full-vocabulary unembedding of every canvas position. | |
| """ | |
| text = self.text | |
| x = self.embed_tokens[canvas_ids] * self.embed_scale | |
| if self_conditioning_logits is not None: | |
| probs = mx.softmax(self_conditioning_logits.astype(mx.float32), axis=-1) | |
| soft = (probs.astype(self.embed_tokens.dtype) @ self.embed_tokens) * self.embed_scale | |
| else: | |
| soft = mx.zeros_like(x) | |
| x = self.self_conditioning(x, soft) | |
| ropes = {} | |
| for i, layer in enumerate(self.layers): | |
| layer_type = text.layer_types[i] | |
| if layer_type not in ropes: | |
| ropes[layer_type] = rope_tables(text, layer_type, positions) | |
| x = layer(x, *ropes[layer_type], None, cache, write=False) | |
| h = self.norm(x) | |
| if read_positions is not None: | |
| h = mx.take(h, read_positions, axis=1) | |
| head = self.lm_head if vocab_ids is None else mx.take(self.lm_head, vocab_ids, axis=0) | |
| logits = (h @ head.T).astype(mx.float32) | |
| return mx.tanh(logits / text.final_logit_softcapping) * text.final_logit_softcapping | |