Instructions to use fukujusou/Anima-mlx with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use fukujusou/Anima-mlx with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download fukujusou/Anima-mlx --local-dir Anima-mlx
- Diffusion Single File
How to use fukujusou/Anima-mlx with Diffusion Single File:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
File size: 6,868 Bytes
3bdc93d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | """Qwen3 0.6B text encoder for Anima MLX parity."""
from __future__ import annotations
import math
from pathlib import Path
from typing import Any, Mapping
from .primitives import apply_rotary_pos_emb, linear, rms_norm, silu
class Qwen3Attention:
def __init__(
self,
weights: Mapping[str, Any],
prefix: str,
*,
hidden_size: int = 1024,
num_heads: int = 16,
num_key_value_heads: int = 8,
head_dim: int = 128,
) -> None:
self.weights = weights
self.prefix = prefix
self.hidden_size = hidden_size
self.num_heads = num_heads
self.num_key_value_heads = num_key_value_heads
self.head_dim = head_dim
self.num_key_value_groups = num_heads // num_key_value_heads
def _weight(self, name: str) -> Any:
return self.weights[f"{self.prefix}.{name}"]
def _linear(self, name: str, x: Any) -> Any:
return linear(x, self._weight(f"{name}.weight"))
def __call__(self, x: Any, cos: Any, sin: Any, attention_mask: Any | None = None) -> Any:
import mlx.core as mx
batch, seq_len, _ = x.shape
q = self._linear("q_proj", x).reshape(batch, seq_len, self.num_heads, self.head_dim)
k = self._linear("k_proj", x).reshape(batch, seq_len, self.num_key_value_heads, self.head_dim)
v = self._linear("v_proj", x).reshape(batch, seq_len, self.num_key_value_heads, self.head_dim)
q = rms_norm(q, self._weight("q_norm.weight"))
k = rms_norm(k, self._weight("k_norm.weight"))
q = mx.swapaxes(q, 1, 2)
k = mx.swapaxes(k, 1, 2)
v = mx.swapaxes(v, 1, 2)
q = apply_rotary_pos_emb(q, cos, sin)
k = apply_rotary_pos_emb(k, cos, sin)
if self.num_key_value_groups != 1:
k = mx.repeat(k, self.num_key_value_groups, axis=1)
v = mx.repeat(v, self.num_key_value_groups, axis=1)
scores = (q @ mx.swapaxes(k, -1, -2)) * (1.0 / math.sqrt(self.head_dim))
scores = _apply_attention_mask(scores, attention_mask)
probs = mx.softmax(scores, axis=-1)
out = probs @ v
out = mx.swapaxes(out, 1, 2).reshape(batch, seq_len, self.num_heads * self.head_dim)
return self._linear("o_proj", out)
class Qwen3MLP:
def __init__(self, weights: Mapping[str, Any], prefix: str) -> None:
self.weights = weights
self.prefix = prefix
def _weight(self, name: str) -> Any:
return self.weights[f"{self.prefix}.{name}"]
def __call__(self, x: Any) -> Any:
gate = linear(x, self._weight("gate_proj.weight"))
up = linear(x, self._weight("up_proj.weight"))
return linear(silu(gate) * up, self._weight("down_proj.weight"))
class Qwen3DecoderLayer:
def __init__(self, weights: Mapping[str, Any], index: int) -> None:
self.weights = weights
self.prefix = f"layers.{index}"
self.self_attn = Qwen3Attention(weights, f"{self.prefix}.self_attn")
self.mlp = Qwen3MLP(weights, f"{self.prefix}.mlp")
def _weight(self, name: str) -> Any:
return self.weights[f"{self.prefix}.{name}"]
def __call__(self, x: Any, cos: Any, sin: Any, attention_mask: Any | None = None) -> Any:
residual = x
hidden = rms_norm(x, self._weight("input_layernorm.weight"))
x = residual + self.self_attn(hidden, cos, sin, attention_mask)
residual = x
hidden = rms_norm(x, self._weight("post_attention_layernorm.weight"))
return residual + self.mlp(hidden)
class Qwen3TextEncoder:
"""MLX implementation of the Qwen3 0.6B text encoder used by Anima."""
def __init__(
self,
weights: Mapping[str, Any],
*,
layer_count: int = 28,
head_dim: int = 128,
rope_theta: float = 1_000_000.0,
) -> None:
self.weights = self._normalize_keys(weights)
self.layer_count = layer_count
self.head_dim = head_dim
self.rope_theta = rope_theta
self.layers = [Qwen3DecoderLayer(self.weights, index) for index in range(layer_count)]
@classmethod
def from_safetensors(cls, path: str | Path, *, dtype: str = "float32") -> "Qwen3TextEncoder":
from anima_mlx.utils.weights import load_mlx_safetensors_subset
path = _resolve_text_encoder_path(path)
weights = load_mlx_safetensors_subset(path, prefix="model.", strip_prefix="model.", dtype=dtype)
return cls(weights)
@staticmethod
def _normalize_keys(weights: Mapping[str, Any]) -> dict[str, Any]:
normalized: dict[str, Any] = {}
for key, value in weights.items():
if key.startswith("model."):
key = key.removeprefix("model.")
normalized[key] = value
return normalized
def __call__(
self,
input_ids: Any,
*,
attention_mask: Any | None = None,
output_hidden_states: bool = False,
) -> Any | tuple[Any, tuple[Any, ...]]:
import mlx.core as mx
x = mx.take(self.weights["embed_tokens.weight"], input_ids.astype(mx.int64), axis=0)
cos, sin = self._position_embeddings(x, input_ids.shape[-1])
hidden_states: list[Any] = []
if output_hidden_states:
hidden_states.append(x)
for layer in self.layers:
x = layer(x.astype(mx.float32), cos, sin, attention_mask)
if output_hidden_states:
hidden_states.append(x)
x = rms_norm(x.astype(mx.float32), self.weights["norm.weight"])
if output_hidden_states:
hidden_states[-1] = x
return x, tuple(hidden_states)
return x
def _position_embeddings(self, x: Any, seq_len: int) -> tuple[Any, Any]:
import mlx.core as mx
inv_freq = 1.0 / (self.rope_theta ** (mx.arange(0, self.head_dim, 2).astype(mx.float32) / self.head_dim))
position_ids = mx.arange(seq_len).astype(mx.float32)[None, :]
freqs = position_ids[..., None] * inv_freq
emb = mx.concatenate([freqs, freqs], axis=-1)
return mx.cos(emb).astype(x.dtype), mx.sin(emb).astype(x.dtype)
def _apply_attention_mask(scores: Any, attention_mask: Any | None) -> Any:
import mlx.core as mx
seq_len = scores.shape[-1]
positions = mx.arange(seq_len)
causal_mask = positions[None, :] > positions[:, None]
min_value = mx.array(-1e9, dtype=scores.dtype)
scores = mx.where(causal_mask[None, None, :, :], min_value, scores)
if attention_mask is not None:
padding_mask = attention_mask.astype(mx.bool_)
scores = mx.where(padding_mask[:, None, None, :], scores, min_value)
return scores
def _resolve_text_encoder_path(path: str | Path) -> Path:
resolved = Path(path)
if resolved.is_dir():
return resolved / "text_encoder.safetensors"
return resolved
|