K2-Horizon-3.7B-MLX-8bit / k2_horizon_mlx.py
abenzerps's picture
K2-Horizon-3.7B MLX 8-bit release
b913eda
Raw
History Blame Contribute Delete
7.52 kB
"""MLX-LM adapter for the dense K2 Horizon configuration.
K2 Horizon uses the Llama-style decoder/attention layout, but its RMSNorm
normalizes two groups of the hidden dimension independently. This module
keeps that detail instead of silently treating the checkpoint as Llama.
"""
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Union
import mlx.core as mx
import mlx.nn as nn
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, RotatingKVCache
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_key_value_heads: Optional[int] = None
head_dim: Optional[int] = None
max_position_embeddings: Optional[int] = None
# K2 stores RoPE settings under rope_parameters; this is the model default.
rope_theta: float = 10_000_000.0
rope_traditional: bool = False
rope_scaling: Optional[Dict[str, Union[float, str]]] = None
tie_word_embeddings: bool = False
attention_bias: bool = False
mlp_bias: bool = False
layernorm_num_groups: int = 2
sliding_window: Optional[int] = None
layer_types: Optional[List[str]] = None
def __post_init__(self):
if self.num_key_value_heads is None:
self.num_key_value_heads = self.num_attention_heads
if self.head_dim is None:
self.head_dim = self.hidden_size // self.num_attention_heads
if self.layer_types is None:
self.layer_types = ["full_attention"] * self.num_hidden_layers
class GroupRMSNorm(nn.Module):
"""K2's T5-style grouped RMS normalization."""
def __init__(self, dims: int, eps: float, groups: int):
super().__init__()
if dims % groups:
raise ValueError(f"hidden size {dims} is not divisible by {groups} groups")
self.weight = mx.ones((dims,))
self.groups = groups
self.eps = eps
def __call__(self, x: mx.array) -> mx.array:
x = mx.unflatten(x, axis=-1, shape=(self.groups, -1))
x = mx.fast.rms_norm(x, weight=None, eps=self.eps)
return self.weight * mx.flatten(x, -2)
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
self.scale = self.head_dim**-0.5
bias = args.attention_bias
self.q_proj = nn.Linear(dim, self.n_heads * self.head_dim, bias=bias)
self.k_proj = nn.Linear(dim, self.n_kv_heads * self.head_dim, bias=bias)
self.v_proj = nn.Linear(dim, self.n_kv_heads * self.head_dim, bias=bias)
self.o_proj = nn.Linear(self.n_heads * self.head_dim, dim, bias=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=None, cache=None) -> mx.array:
B, L, _ = x.shape
q = self.q_proj(x).reshape(B, L, self.n_heads, self.head_dim).transpose(0, 2, 1, 3)
k = self.k_proj(x).reshape(B, L, self.n_kv_heads, self.head_dim).transpose(0, 2, 1, 3)
v = self.v_proj(x).reshape(B, L, self.n_kv_heads, self.head_dim).transpose(0, 2, 1, 3)
offset = cache.offset if cache is not None else 0
q = self.rope(q, offset=offset)
k = self.rope(k, offset=offset)
if cache is not None:
k, v = cache.update_and_fetch(k, v)
out = scaled_dot_product_attention(q, k, v, cache=cache, scale=self.scale, mask=mask)
out = out.transpose(0, 2, 1, 3).reshape(B, L, -1)
return self.o_proj(out)
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.down_proj = nn.Linear(args.intermediate_size, args.hidden_size, bias=args.mlp_bias)
self.up_proj = nn.Linear(args.hidden_size, args.intermediate_size, bias=args.mlp_bias)
def __call__(self, x):
return self.down_proj(swiglu(self.gate_proj(x), self.up_proj(x)))
class TransformerBlock(nn.Module):
def __init__(self, args: ModelArgs, use_sliding: bool = False):
super().__init__()
self.self_attn = Attention(args)
self.mlp = MLP(args)
self.input_layernorm = GroupRMSNorm(
args.hidden_size, args.rms_norm_eps, args.layernorm_num_groups
)
self.post_attention_layernorm = GroupRMSNorm(
args.hidden_size, args.rms_norm_eps, args.layernorm_num_groups
)
self.use_sliding = use_sliding
def __call__(self, x, mask=None, cache=None):
h = x + self.self_attn(self.input_layernorm(x), mask, cache)
return h + self.mlp(self.post_attention_layernorm(h))
class K2HorizonModel(nn.Module):
def __init__(self, args: ModelArgs):
super().__init__()
self.args = args
self.vocab_size = args.vocab_size
self.embed_tokens = nn.Embedding(args.vocab_size, args.hidden_size)
self.layers = [
TransformerBlock(args, use_sliding=t == "sliding_attention")
for t in args.layer_types
]
self.norm = GroupRMSNorm(args.hidden_size, args.rms_norm_eps, args.layernorm_num_groups)
self.sliding_window = args.sliding_window
self.fa_idx = 0
self.swa_idx = None
for i, layer in enumerate(self.layers):
if layer.use_sliding:
self.swa_idx = i
break
def __call__(self, inputs, cache=None, input_embeddings=None):
h = self.embed_tokens(inputs) if input_embeddings is None else input_embeddings
if cache is None:
cache = [None] * len(self.layers)
fa_mask = create_attention_mask(h, cache[self.fa_idx])
swa_mask = None
if self.swa_idx is not None:
swa_mask = create_attention_mask(
h, cache[self.swa_idx], window_size=self.sliding_window
)
for layer, c in zip(self.layers, cache):
h = layer(h, swa_mask if layer.use_sliding else fa_mask, c)
return self.norm(h)
class Model(nn.Module):
def __init__(self, args: ModelArgs):
super().__init__()
self.args = args
self.model_type = args.model_type
self.model = K2HorizonModel(args)
if not args.tie_word_embeddings:
self.lm_head = nn.Linear(args.hidden_size, args.vocab_size, bias=False)
def __call__(self, inputs, cache=None, input_embeddings=None):
h = self.model(inputs, cache, input_embeddings)
return self.lm_head(h)
def sanitize(self, weights):
return {
k: v
for k, v in weights.items()
if "rotary_emb.inv_freq" not in k
}
@property
def layers(self):
return self.model.layers
def make_cache(self):
return [
RotatingKVCache(max_size=self.model.sliding_window)
if layer.use_sliding
else KVCache()
for layer in self.model.layers
]