WaveCut's picture
Add files using upload-large-folder tool
106570a verified
Raw
History Blame Contribute Delete
10.3 kB
"""MLX-LM architecture adapter for the looped Nanbeige 4.2 model family.
This file intentionally supports the released Nanbeige4.2-3B configuration only:
standard Llama-style decoder blocks whose physical layers are reused for multiple
loops. Optional Nanbeige n-gram, hyper-connection, depth-attention, split-loop,
and shared-KV features are rejected instead of being approximated silently.
"""
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Union
import mlx.core as mx
import mlx.nn as nn
from mlx.nn.layers.distributed import shard_linear
from mlx_lm.models.activations import swiglu
from mlx_lm.models.base import (
BaseModelArgs,
create_attention_mask,
scaled_dot_product_attention,
)
from mlx_lm.models.cache import KVCache
from mlx_lm.models.rope_utils import initialize_rope
@dataclass
class ModelArgs(BaseModelArgs):
model_type: str
hidden_size: int
num_hidden_layers: int
intermediate_size: int
num_attention_heads: int
rms_norm_eps: float
vocab_size: int
num_loops: int = 1
head_dim: Optional[int] = None
max_position_embeddings: Optional[int] = None
num_key_value_heads: Optional[int] = None
attention_bias: bool = False
mlp_bias: bool = False
rope_theta: float = 10000
rope_traditional: bool = False
rope_scaling: Optional[Dict[str, Union[float, str]]] = None
tie_word_embeddings: bool = False
skip_loop_final_norm: bool = False
loop_loss_weights: Optional[List[float]] = None
emb_neighbor_num: Optional[int] = None
emb_split_num: Optional[int] = None
ngram_vocab_size_ratio: Optional[float] = None
enable_hyper_connection: bool = False
enable_double_loop_split: bool = False
enable_depth_attention: bool = False
loop_share_kv: bool = False
def __post_init__(self) -> None:
if self.num_key_value_heads is None:
self.num_key_value_heads = self.num_attention_heads
unsupported = {
"n-gram embeddings": (
self.emb_neighbor_num is not None
or self.emb_split_num is not None
or self.ngram_vocab_size_ratio is not None
),
"hyper-connections": self.enable_hyper_connection,
"double-loop split": self.enable_double_loop_split,
"depth attention": self.enable_depth_attention,
"shared loop KV": self.loop_share_kv,
}
enabled = [name for name, value in unsupported.items() if value]
if enabled:
raise ValueError(
"This Nanbeige MLX adapter does not support: " + ", ".join(enabled)
)
if self.loop_loss_weights:
self.num_loops = len(self.loop_loss_weights) + 1
if self.num_loops < 1:
raise ValueError("num_loops must be at least 1")
class Attention(nn.Module):
def __init__(self, args: ModelArgs):
super().__init__()
dim = args.hidden_size
self.n_heads = args.num_attention_heads
self.n_kv_heads = args.num_key_value_heads
self.head_dim = args.head_dim or dim // self.n_heads
self.scale = self.head_dim**-0.5
self.q_proj = nn.Linear(
dim, self.n_heads * self.head_dim, bias=args.attention_bias
)
self.k_proj = nn.Linear(
dim, self.n_kv_heads * self.head_dim, bias=args.attention_bias
)
self.v_proj = nn.Linear(
dim, self.n_kv_heads * self.head_dim, bias=args.attention_bias
)
self.o_proj = nn.Linear(
self.n_heads * self.head_dim, dim, bias=args.attention_bias
)
self.rope = initialize_rope(
self.head_dim,
args.rope_theta,
args.rope_traditional,
args.rope_scaling,
args.max_position_embeddings,
)
def __call__(
self,
x: mx.array,
mask: Optional[mx.array] = None,
cache: Optional[Any] = None,
) -> mx.array:
batch, length, _ = x.shape
queries = self.q_proj(x)
keys = self.k_proj(x)
values = self.v_proj(x)
queries = queries.reshape(batch, length, self.n_heads, self.head_dim).transpose(
0, 2, 1, 3
)
keys = keys.reshape(batch, length, self.n_kv_heads, self.head_dim).transpose(
0, 2, 1, 3
)
values = values.reshape(
batch, length, self.n_kv_heads, self.head_dim
).transpose(0, 2, 1, 3)
if cache is None:
queries = self.rope(queries)
keys = self.rope(keys)
else:
queries = self.rope(queries, offset=cache.offset)
keys = self.rope(keys, offset=cache.offset)
keys, values = cache.update_and_fetch(keys, values)
output = scaled_dot_product_attention(
queries,
keys,
values,
cache=cache,
scale=self.scale,
mask=mask,
)
output = output.transpose(0, 2, 1, 3).reshape(batch, length, -1)
return self.o_proj(output)
class MLP(nn.Module):
def __init__(self, args: ModelArgs):
super().__init__()
self.gate_proj = nn.Linear(
args.hidden_size, args.intermediate_size, bias=args.mlp_bias
)
self.up_proj = nn.Linear(
args.hidden_size, args.intermediate_size, bias=args.mlp_bias
)
self.down_proj = nn.Linear(
args.intermediate_size, args.hidden_size, bias=args.mlp_bias
)
def __call__(self, x: mx.array) -> mx.array:
return self.down_proj(swiglu(self.gate_proj(x), self.up_proj(x)))
class TransformerBlock(nn.Module):
def __init__(self, args: ModelArgs):
super().__init__()
self.self_attn = Attention(args)
self.mlp = MLP(args)
self.input_layernorm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps)
self.post_attention_layernorm = nn.RMSNorm(
args.hidden_size, eps=args.rms_norm_eps
)
def __call__(
self,
x: mx.array,
mask: Optional[mx.array] = None,
cache: Optional[Any] = None,
) -> mx.array:
h = x + self.self_attn(self.input_layernorm(x), mask, cache)
return h + self.mlp(self.post_attention_layernorm(h))
class NanbeigeModel(nn.Module):
def __init__(self, args: ModelArgs):
super().__init__()
self.args = args
self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size)
self.layers = [TransformerBlock(args) for _ in range(args.num_hidden_layers)]
self.norm = nn.RMSNorm(args.hidden_size, eps=args.rms_norm_eps)
def __call__(
self,
inputs: mx.array,
cache: Optional[List[Any]] = None,
input_embeddings: Optional[mx.array] = None,
) -> mx.array:
h = self.embed_tokens(inputs) if input_embeddings is None else input_embeddings
total_executions = self.args.num_loops * len(self.layers)
if cache is None:
cache = [None] * total_executions
elif len(cache) != total_executions:
raise ValueError(
f"Expected {total_executions} KV caches for the looped model, "
f"got {len(cache)}."
)
for loop_index in range(self.args.num_loops):
cache_offset = loop_index * len(self.layers)
mask = create_attention_mask(h, cache[cache_offset])
for layer_index, layer in enumerate(self.layers):
h = layer(h, mask, cache[cache_offset + layer_index])
if not self.args.skip_loop_final_norm:
h = self.norm(h)
if self.args.skip_loop_final_norm:
h = self.norm(h)
return h
class Model(nn.Module):
def __init__(self, args: ModelArgs):
super().__init__()
self.args = args
self.model_type = args.model_type
self.model = NanbeigeModel(args)
if not args.tie_word_embeddings:
self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False)
def __call__(
self,
inputs: mx.array,
cache: Optional[List[Any]] = None,
input_embeddings: Optional[mx.array] = None,
) -> mx.array:
output = self.model(inputs, cache, input_embeddings)
if self.args.tie_word_embeddings:
return self.model.embed_tokens.as_linear(output)
return self.lm_head(output)
def sanitize(self, weights: Dict[str, mx.array]) -> Dict[str, mx.array]:
weights = {
key: value
for key, value in weights.items()
if "self_attn.rotary_emb.inv_freq" not in key
}
if self.args.tie_word_embeddings:
weights.pop("lm_head.weight", None)
return weights
def shard(self, group: Optional[mx.distributed.Group] = None) -> None:
group = group or mx.distributed.init()
shard_count = group.size()
for layer in self.model.layers:
layer.self_attn.q_proj = shard_linear(
layer.self_attn.q_proj, "all-to-sharded", group=group
)
layer.self_attn.k_proj = shard_linear(
layer.self_attn.k_proj, "all-to-sharded", group=group
)
layer.self_attn.v_proj = shard_linear(
layer.self_attn.v_proj, "all-to-sharded", group=group
)
layer.self_attn.o_proj = shard_linear(
layer.self_attn.o_proj, "sharded-to-all", group=group
)
layer.self_attn.n_heads //= shard_count
layer.self_attn.n_kv_heads //= shard_count
layer.mlp.gate_proj = shard_linear(
layer.mlp.gate_proj, "all-to-sharded", group=group
)
layer.mlp.up_proj = shard_linear(
layer.mlp.up_proj, "all-to-sharded", group=group
)
layer.mlp.down_proj = shard_linear(
layer.mlp.down_proj, "sharded-to-all", group=group
)
@property
def layers(self):
return self.model.layers
def make_cache(self) -> List[KVCache]:
return [
KVCache() for _ in range(self.args.num_loops * self.args.num_hidden_layers)
]