adjaxt 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adjaxt-0.0.1/PKG-INFO +22 -0
- adjaxt-0.0.1/README.md +1 -0
- adjaxt-0.0.1/pyproject.toml +33 -0
- adjaxt-0.0.1/src/adjaxt/__init__.py +36 -0
- adjaxt-0.0.1/src/adjaxt/approx.py +0 -0
- adjaxt-0.0.1/src/adjaxt/checkpoint.py +0 -0
- adjaxt-0.0.1/src/adjaxt/config.py +88 -0
- adjaxt-0.0.1/src/adjaxt/data.py +0 -0
- adjaxt-0.0.1/src/adjaxt/hub.py +0 -0
- adjaxt-0.0.1/src/adjaxt/init.py +0 -0
- adjaxt-0.0.1/src/adjaxt/layers.py +322 -0
- adjaxt-0.0.1/src/adjaxt/model_maps.py +27 -0
- adjaxt-0.0.1/src/adjaxt/models.py +148 -0
- adjaxt-0.0.1/src/adjaxt/sharding.py +251 -0
- adjaxt-0.0.1/src/adjaxt/train.py +0 -0
- adjaxt-0.0.1/tests/conftest.py +14 -0
- adjaxt-0.0.1/tests/test_hub.py +0 -0
- adjaxt-0.0.1/tests/test_layers.py +340 -0
- adjaxt-0.0.1/tests/test_qwen3_moe.py +300 -0
adjaxt-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: adjaxt
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Distributed training with JAX on free compute, for humans
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: datasets>=2.19
|
|
8
|
+
Requires-Dist: huggingface-hub>=0.23
|
|
9
|
+
Requires-Dist: jax>=0.4.30
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: optax>=0.2.2
|
|
12
|
+
Requires-Dist: orbax-checkpoint>=0.5
|
|
13
|
+
Requires-Dist: safetensors>=0.4
|
|
14
|
+
Provides-Extra: cuda
|
|
15
|
+
Requires-Dist: jax[cuda12]>=0.4.30; extra == 'cuda'
|
|
16
|
+
Provides-Extra: test
|
|
17
|
+
Requires-Dist: pytest; extra == 'test'
|
|
18
|
+
Requires-Dist: torch; extra == 'test'
|
|
19
|
+
Requires-Dist: transformers; extra == 'test'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
Hi, this package is in development, not ready yet
|
adjaxt-0.0.1/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Hi, this package is in development, not ready yet
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "adjaxt"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Distributed training with JAX on free compute, for humans"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"jax>=0.4.30",
|
|
14
|
+
"optax>=0.2.2",
|
|
15
|
+
"orbax-checkpoint>=0.5",
|
|
16
|
+
"datasets>=2.19",
|
|
17
|
+
"safetensors>=0.4",
|
|
18
|
+
"huggingface-hub>=0.23",
|
|
19
|
+
"numpy",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
cuda = [
|
|
24
|
+
"jax[cuda12]>=0.4.30",
|
|
25
|
+
]
|
|
26
|
+
test = [
|
|
27
|
+
"pytest",
|
|
28
|
+
"transformers",
|
|
29
|
+
"torch",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["src/adjaxt"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""adjaxt — distributed training with JAX on free compute, made not sloppy."""
|
|
2
|
+
|
|
3
|
+
from adjaxt.config import (
|
|
4
|
+
RMSNormConfig,
|
|
5
|
+
SwiGLUConfig,
|
|
6
|
+
StandardAttnImplementation,
|
|
7
|
+
GQAAttnConfig,
|
|
8
|
+
Qwen3AttnConfig,
|
|
9
|
+
Qwen3MLPConfig,
|
|
10
|
+
Qwen3MoEBlockConfig,
|
|
11
|
+
Qwen3MoELayerConfig,
|
|
12
|
+
Qwen3MoEModelConfig,
|
|
13
|
+
compute_rope_freqs,
|
|
14
|
+
)
|
|
15
|
+
from adjaxt import layers, models, model_maps, sharding
|
|
16
|
+
|
|
17
|
+
__version__ = "0.0.1"
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
# Configs
|
|
21
|
+
"RMSNormConfig",
|
|
22
|
+
"SwiGLUConfig",
|
|
23
|
+
"StandardAttnImplementation",
|
|
24
|
+
"GQAAttnConfig",
|
|
25
|
+
"Qwen3AttnConfig",
|
|
26
|
+
"Qwen3MLPConfig",
|
|
27
|
+
"Qwen3MoEBlockConfig",
|
|
28
|
+
"Qwen3MoELayerConfig",
|
|
29
|
+
"Qwen3MoEModelConfig",
|
|
30
|
+
"compute_rope_freqs",
|
|
31
|
+
# Submodules
|
|
32
|
+
"layers",
|
|
33
|
+
"models",
|
|
34
|
+
"model_maps",
|
|
35
|
+
"sharding",
|
|
36
|
+
]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Dict, Any, Tuple
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
import jax
|
|
5
|
+
import jax.numpy as jnp
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class RMSNormConfig:
|
|
9
|
+
dim: int
|
|
10
|
+
eps: float = 1e-6
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class SwiGLUConfig:
|
|
14
|
+
in_dim: int
|
|
15
|
+
hidden_dim: int
|
|
16
|
+
|
|
17
|
+
class StandardAttnImplementation(StrEnum):
|
|
18
|
+
CUDNN = "cudnn"
|
|
19
|
+
XLA = "xla"
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class GQAAttnConfig:
|
|
23
|
+
implementation: StandardAttnImplementation
|
|
24
|
+
num_kv_groups: int
|
|
25
|
+
is_causal: bool
|
|
26
|
+
|
|
27
|
+
def compute_rope_freqs(seq_len: int, head_dim: int, theta: float = 10000.0):
|
|
28
|
+
dim_indices = jnp.arange(0, head_dim, 2, dtype=jnp.float32)
|
|
29
|
+
inv_freq = 1.0 / (theta ** (dim_indices / head_dim))
|
|
30
|
+
t = jnp.arange(seq_len, dtype=jnp.float32)
|
|
31
|
+
freqs = jnp.outer(t, inv_freq)
|
|
32
|
+
|
|
33
|
+
freqs = jnp.concatenate([freqs, freqs], axis=-1)
|
|
34
|
+
|
|
35
|
+
cos = jnp.cos(freqs)[None, :, None, :]
|
|
36
|
+
sin = jnp.sin(freqs)[None, :, None, :]
|
|
37
|
+
return cos, sin
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class Qwen3AttnConfig:
|
|
41
|
+
gqa_conf: GQAAttnConfig
|
|
42
|
+
q_rms_conf: RMSNormConfig
|
|
43
|
+
k_rms_conf: RMSNormConfig
|
|
44
|
+
d: int
|
|
45
|
+
head_dim: int
|
|
46
|
+
rope_theta: float
|
|
47
|
+
cos_table: jax.Array = field(init=False, compare=False, hash=False)
|
|
48
|
+
sin_table: jax.Array = field(init=False, compare=False, hash=False)
|
|
49
|
+
max_position_embeddings: int
|
|
50
|
+
n_layers: int
|
|
51
|
+
|
|
52
|
+
def __post_init__(self):
|
|
53
|
+
cos, sin = compute_rope_freqs(
|
|
54
|
+
self.max_position_embeddings,
|
|
55
|
+
self.head_dim,
|
|
56
|
+
self.rope_theta
|
|
57
|
+
)
|
|
58
|
+
object.__setattr__(self, "cos_table", cos)
|
|
59
|
+
object.__setattr__(self, "sin_table", sin)
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class Qwen3MLPConfig:
|
|
63
|
+
act_fn: function
|
|
64
|
+
in_dim: int
|
|
65
|
+
hidden_dim: int
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class Qwen3MoEBlockConfig:
|
|
69
|
+
mlp_conf: Qwen3MLPConfig
|
|
70
|
+
top_k: int
|
|
71
|
+
num_experts: int
|
|
72
|
+
d_model: int
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class Qwen3MoELayerConfig:
|
|
76
|
+
input_rms_conf: RMSNormConfig
|
|
77
|
+
attn_conf: Qwen3AttnConfig
|
|
78
|
+
post_attn_rms_conf: RMSNormConfig
|
|
79
|
+
moe_block_conf: Qwen3MoEBlockConfig
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class Qwen3MoEModelConfig:
|
|
83
|
+
moe_layer_conf: Qwen3MoELayerConfig
|
|
84
|
+
final_rms_conf: RMSNormConfig
|
|
85
|
+
num_decoder_blocks: int
|
|
86
|
+
vocab_size: int
|
|
87
|
+
d_model: int
|
|
88
|
+
tie_word_embeddings: bool = False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import jax, jax.numpy as jnp
|
|
2
|
+
import math
|
|
3
|
+
from adjaxt.config import *
|
|
4
|
+
|
|
5
|
+
#def init_normal_layer(key, shape, std: float) -> jax.Array:
|
|
6
|
+
# return jax.random.normal(key, shape, jnp.bfloat16) * std
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
#===================================================================================
|
|
10
|
+
#RMS Norm
|
|
11
|
+
|
|
12
|
+
def rms_norm(x, w, cfg) -> jax.Array:
|
|
13
|
+
"""
|
|
14
|
+
Args:
|
|
15
|
+
x: [..., D]
|
|
16
|
+
w: [D]
|
|
17
|
+
Returns:
|
|
18
|
+
out: [..., D]
|
|
19
|
+
"""
|
|
20
|
+
x32 = x.astype(jnp.float32)
|
|
21
|
+
ms = jnp.mean(jnp.square(x32), axis=-1, keepdims=True)
|
|
22
|
+
return w * (x32 * jax.lax.rsqrt(ms + cfg.eps)).astype(x.dtype)
|
|
23
|
+
|
|
24
|
+
def rms_norm_init(key, cfg: RMSNormConfig) -> jax.Array:
|
|
25
|
+
"""
|
|
26
|
+
Returns:
|
|
27
|
+
w: [dim]
|
|
28
|
+
"""
|
|
29
|
+
return jnp.ones((cfg.dim,), dtype=jnp.float32)
|
|
30
|
+
|
|
31
|
+
#===================================================================================
|
|
32
|
+
#RoPE
|
|
33
|
+
|
|
34
|
+
def rotate_half(x: jax.Array) -> jax.Array:
|
|
35
|
+
"""
|
|
36
|
+
Args:
|
|
37
|
+
x: [B, S, H, D]
|
|
38
|
+
Returns:
|
|
39
|
+
out: [B, S, H, D]
|
|
40
|
+
"""
|
|
41
|
+
x1 = x[..., : x.shape[-1] // 2]
|
|
42
|
+
x2 = x[..., x.shape[-1] // 2 :]
|
|
43
|
+
return jnp.concatenate((-x2, x1), axis=-1)
|
|
44
|
+
|
|
45
|
+
def apply_rope(x: jax.Array, cos: jax.Array, sin: jax.Array) -> jax.Array:
|
|
46
|
+
return x * cos + rotate_half(x) * sin
|
|
47
|
+
|
|
48
|
+
def apply_rot_pos_emb(q: jax.Array, k: jax.Array, cos: jax.Array, sin: jax.Array):
|
|
49
|
+
"""
|
|
50
|
+
Args:
|
|
51
|
+
q: [B, S, H_q, D]
|
|
52
|
+
k: [B, S, H_kv, D]
|
|
53
|
+
cos: [1, S, 1, D]
|
|
54
|
+
sin: [1, S, 1, D]
|
|
55
|
+
Returns:
|
|
56
|
+
q_rot, k_rot: ([B, S, H_q, D], [B, S, H_kv, D])
|
|
57
|
+
"""
|
|
58
|
+
# q: (batch, seq_len, num_heads, head_dim)
|
|
59
|
+
# cos: (1, seq_len, 1, head_dim)
|
|
60
|
+
q_rot = (q * cos) + (rotate_half(q) * sin)
|
|
61
|
+
k_rot = (k * cos) + (rotate_half(k) * sin)
|
|
62
|
+
return q_rot, k_rot
|
|
63
|
+
|
|
64
|
+
#===================================================================================
|
|
65
|
+
#SwiGLU
|
|
66
|
+
|
|
67
|
+
def swiglu(x: jax.Array, w: dict, cfg: SwiGLUConfig) -> jax.Array:
|
|
68
|
+
return (jax.nn.silu(x @ w["w_gate"]) * (x @ w["w_up"])) @ w["w_down"]
|
|
69
|
+
|
|
70
|
+
def swiglu_init(key, cfg: SwiGLUConfig) -> dict:
|
|
71
|
+
k1, k2, k3 = jax.random.split(key, 3)
|
|
72
|
+
def kaiming(k, shape):
|
|
73
|
+
return jax.random.normal(k, shape, dtype=jnp.float32) * jnp.sqrt(2.0 / shape[0])
|
|
74
|
+
return {
|
|
75
|
+
"w_gate": kaiming(k1, (cfg.in_dim, cfg.hidden_dim)),
|
|
76
|
+
"w_up": kaiming(k2, (cfg.in_dim, cfg.hidden_dim)),
|
|
77
|
+
"w_down": kaiming(k3, (cfg.hidden_dim, cfg.in_dim))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#===================================================================================
|
|
81
|
+
#RepeatKV
|
|
82
|
+
|
|
83
|
+
def repeat_kv(hidden_states: jax.Array, n_rep: int) -> jax.Array:
|
|
84
|
+
"""
|
|
85
|
+
Expands KV heads to match query heads.
|
|
86
|
+
Args:
|
|
87
|
+
x: [B, S, H_kv, D]
|
|
88
|
+
Returns:
|
|
89
|
+
out: [B, S, H_kv * n_rep, D]
|
|
90
|
+
"""
|
|
91
|
+
if n_rep == 1:
|
|
92
|
+
return hidden_states
|
|
93
|
+
return jnp.repeat(hidden_states, n_rep, axis=2)
|
|
94
|
+
|
|
95
|
+
#===================================================================================
|
|
96
|
+
#GQAAttention
|
|
97
|
+
|
|
98
|
+
def gqa_attn(
|
|
99
|
+
q: jax.Array,
|
|
100
|
+
k: jax.Array,
|
|
101
|
+
v: jax.Array,
|
|
102
|
+
cfg: GQAAttnConfig,
|
|
103
|
+
attn_mask: jax.Array = None
|
|
104
|
+
) -> jax.Array:
|
|
105
|
+
"""
|
|
106
|
+
Args:
|
|
107
|
+
q: [B, S_q, H_q, D]
|
|
108
|
+
k: [B, S_kv, H_kv, D]
|
|
109
|
+
v: [B, S_kv, H_kv, D]
|
|
110
|
+
attn_mask: [B, H_q, S_q, S_kv] or None
|
|
111
|
+
Returns:
|
|
112
|
+
out: [B, S_q, H_q, D]
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
batch, q_len, num_q_heads, head_dim = q.shape
|
|
116
|
+
_, kv_len, num_kv_heads, _ = k.shape
|
|
117
|
+
|
|
118
|
+
num_groups = num_q_heads // num_kv_heads
|
|
119
|
+
|
|
120
|
+
if num_groups > 1:
|
|
121
|
+
k = jnp.repeat(k, repeats=num_groups, axis=2)
|
|
122
|
+
v = jnp.repeat(v, repeats=num_groups, axis=2)
|
|
123
|
+
|
|
124
|
+
causal_flag = cfg.is_causal if attn_mask is None else False
|
|
125
|
+
|
|
126
|
+
out = jax.nn.dot_product_attention(
|
|
127
|
+
query=q,
|
|
128
|
+
key=k,
|
|
129
|
+
value=v,
|
|
130
|
+
mask=attn_mask,
|
|
131
|
+
is_causal=causal_flag,
|
|
132
|
+
implementation=cfg.implementation,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return out
|
|
136
|
+
|
|
137
|
+
#===================================================================================
|
|
138
|
+
#Qwen3Attn
|
|
139
|
+
|
|
140
|
+
def qwen3_attn(
|
|
141
|
+
x: jax.Array,
|
|
142
|
+
w: dict,
|
|
143
|
+
cfg: Qwen3AttnConfig
|
|
144
|
+
):
|
|
145
|
+
"""
|
|
146
|
+
Args:
|
|
147
|
+
x: [B, S, D_model]
|
|
148
|
+
w: Dict containing projection weights and head norms
|
|
149
|
+
Returns:
|
|
150
|
+
out: [B, S, D_model]
|
|
151
|
+
"""
|
|
152
|
+
seq_len = x.shape[1]
|
|
153
|
+
input_shape = x.shape[:-1]
|
|
154
|
+
hidden_shape = (*input_shape, -1, cfg.head_dim)
|
|
155
|
+
|
|
156
|
+
# Shapes: (batch, seq_len, num_heads, head_dim)
|
|
157
|
+
q = rms_norm((x @ w["q_proj"]).reshape(hidden_shape), w["q_norm"], cfg.q_rms_conf)
|
|
158
|
+
k = rms_norm((x @ w["k_proj"]).reshape(hidden_shape), w["k_norm"], cfg.k_rms_conf)
|
|
159
|
+
v = (x @ w["v_proj"]).reshape(hidden_shape) # keep (B, S, KV_H, D)
|
|
160
|
+
|
|
161
|
+
# cos/sin shape: (1, seq_len, 1, head_dim)
|
|
162
|
+
cos = cfg.cos_table[:, :seq_len, :, :]
|
|
163
|
+
sin = cfg.sin_table[:, :seq_len, :, :]
|
|
164
|
+
q, k = apply_rot_pos_emb(q, k, cos, sin)
|
|
165
|
+
|
|
166
|
+
attn_out = gqa_attn(
|
|
167
|
+
q,
|
|
168
|
+
k,
|
|
169
|
+
v,
|
|
170
|
+
cfg.gqa_conf
|
|
171
|
+
)
|
|
172
|
+
attn_out = attn_out.reshape(*input_shape, -1)
|
|
173
|
+
attn_out = attn_out @ w["o_proj"]
|
|
174
|
+
return attn_out
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def qwen3_attn_init(
|
|
178
|
+
key,
|
|
179
|
+
cfg: Qwen3AttnConfig
|
|
180
|
+
) -> dict:
|
|
181
|
+
k1, k2, k3, k4, k5, k6 = jax.random.split(key, 6)
|
|
182
|
+
res = 1.0 / math.sqrt(2 * cfg.n_layers)
|
|
183
|
+
|
|
184
|
+
# Calculate dimensions based on GQA heads
|
|
185
|
+
q_dim = cfg.d # num_heads * head_dim
|
|
186
|
+
kv_dim = (cfg.d // cfg.gqa_conf.num_kv_groups) # num_kv_heads * head_dim
|
|
187
|
+
|
|
188
|
+
w = {
|
|
189
|
+
"q_proj": jax.random.normal(k1, (cfg.d, q_dim), jnp.bfloat16) / math.sqrt(cfg.d),
|
|
190
|
+
"k_proj": jax.random.normal(k2, (cfg.d, kv_dim), jnp.bfloat16) / math.sqrt(cfg.d),
|
|
191
|
+
"v_proj": jax.random.normal(k3, (cfg.d, kv_dim), jnp.bfloat16) / math.sqrt(cfg.d),
|
|
192
|
+
"o_proj": jax.random.normal(k4, (q_dim, cfg.d), jnp.bfloat16) * res / math.sqrt(q_dim),
|
|
193
|
+
"q_norm": jnp.ones((cfg.head_dim,), dtype=jnp.bfloat16),
|
|
194
|
+
"k_norm": jnp.ones((cfg.head_dim,), dtype=jnp.bfloat16),
|
|
195
|
+
}
|
|
196
|
+
return w
|
|
197
|
+
|
|
198
|
+
#===================================================================================
|
|
199
|
+
#Qwen3MLP
|
|
200
|
+
|
|
201
|
+
def qwen3_mlp(x: jax.Array, w: dict, cfg: Qwen3MLPConfig) -> jax.Array:
|
|
202
|
+
"""
|
|
203
|
+
Args:
|
|
204
|
+
x: [..., D_in]
|
|
205
|
+
w: {"w_gate": [D_in, D_hidden], "w_up": [D_in, D_hidden], "w_down": [D_hidden, D_in]}
|
|
206
|
+
Returns:
|
|
207
|
+
out: [..., D_in]
|
|
208
|
+
"""
|
|
209
|
+
return (cfg.act_fn(x @ w["w_gate"]) * (x @ w["w_up"])) @ w["w_down"]
|
|
210
|
+
|
|
211
|
+
def qwen3_mlp_init(key, cfg: Qwen3MLPConfig) -> dict:
|
|
212
|
+
k1, k2, k3 = jax.random.split(key, 3)
|
|
213
|
+
def kaiming(k, shape):
|
|
214
|
+
return jax.random.normal(k, shape, dtype=jnp.float32) * jnp.sqrt(2.0 / shape[0])
|
|
215
|
+
return {
|
|
216
|
+
"w_gate": kaiming(k1, (cfg.in_dim, cfg.hidden_dim)),
|
|
217
|
+
"w_up": kaiming(k2, (cfg.in_dim, cfg.hidden_dim)),
|
|
218
|
+
"w_down": kaiming(k3, (cfg.hidden_dim, cfg.in_dim))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#===================================================================================
|
|
222
|
+
#Qwen3MoEBlock
|
|
223
|
+
|
|
224
|
+
def qwen3_moe_block(x: jax.Array, w: dict, cfg: Qwen3MoEBlockConfig) -> jax.Array:
|
|
225
|
+
"""
|
|
226
|
+
Args:
|
|
227
|
+
x: [B, S, D_model]
|
|
228
|
+
Returns:
|
|
229
|
+
out: [B, S, D_model]
|
|
230
|
+
"""
|
|
231
|
+
tokens = x.reshape(-1, x.shape[-1]) # (T, d)
|
|
232
|
+
router_logits = tokens @ w["router"] # (T, E)
|
|
233
|
+
probs = jax.nn.softmax(router_logits.astype(jnp.float32), -1)
|
|
234
|
+
topk_probs, topk_idx = jax.lax.top_k(probs, cfg.top_k) # (T, k)
|
|
235
|
+
topk_probs = topk_probs / topk_probs.sum(-1, keepdims=True) # norm_topk_prob
|
|
236
|
+
# scatter the renormalized probs back to (T, E), zeros elsewhere
|
|
237
|
+
weights = jnp.zeros_like(probs).at[
|
|
238
|
+
jnp.arange(tokens.shape[0])[:, None], topk_idx
|
|
239
|
+
].set(topk_probs)
|
|
240
|
+
expert_out = jax.vmap(
|
|
241
|
+
lambda we: qwen3_mlp(tokens, we, cfg.mlp_conf)
|
|
242
|
+
)(w["experts"]) # (E, T, d)
|
|
243
|
+
out = jnp.einsum("te,etd->td", weights.astype(x.dtype), expert_out)
|
|
244
|
+
return out.reshape(x.shape)
|
|
245
|
+
|
|
246
|
+
def qwen3_moe_block_init(key, cfg: Qwen3MoEBlockConfig) -> dict:
|
|
247
|
+
k_router, k_experts = jax.random.split(key, 2)
|
|
248
|
+
expert_keys = jax.random.split(k_experts, cfg.num_experts)
|
|
249
|
+
return {
|
|
250
|
+
"router": jax.random.normal(k_router, (cfg.d_model, cfg.num_experts), dtype=jnp.bfloat16) * (cfg.d_model ** -0.5),
|
|
251
|
+
"experts": jax.vmap(lambda k: qwen3_mlp_init(k, cfg.mlp_conf))(expert_keys),
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#===================================================================================
|
|
255
|
+
#Qwen3MoELayer
|
|
256
|
+
|
|
257
|
+
def qwen3_moe_layer(x: jax.Array, w: dict, cfg: Qwen3MoELayerConfig) -> jax.Array:
|
|
258
|
+
"""
|
|
259
|
+
Args:
|
|
260
|
+
x: [B, S, D_model]
|
|
261
|
+
Returns:
|
|
262
|
+
out: [B, S, D_model]
|
|
263
|
+
"""
|
|
264
|
+
residual = x
|
|
265
|
+
x = rms_norm(x, w["input_layernorm"], cfg.input_rms_conf)
|
|
266
|
+
x = qwen3_attn(x, w["attn"], cfg.attn_conf)
|
|
267
|
+
x = x + residual
|
|
268
|
+
residual = x
|
|
269
|
+
x = rms_norm(x, w["post_attn_layernorm"], cfg.post_attn_rms_conf)
|
|
270
|
+
x = qwen3_moe_block(x, w["mlp"], cfg.moe_block_conf)
|
|
271
|
+
x = residual + x
|
|
272
|
+
return x
|
|
273
|
+
|
|
274
|
+
def qwen3_moe_layer_init(key, cfg: Qwen3MoELayerConfig) -> dict:
|
|
275
|
+
attn_key, mlp_key, k3, k4 = jax.random.split(key, 4)
|
|
276
|
+
return {
|
|
277
|
+
"attn": qwen3_attn_init(attn_key, cfg.attn_conf),
|
|
278
|
+
"mlp": qwen3_moe_block_init(mlp_key, cfg.moe_block_conf),
|
|
279
|
+
"input_layernorm": rms_norm_init(k3, cfg.input_rms_conf),
|
|
280
|
+
"post_attn_layernorm": rms_norm_init(k4, cfg.post_attn_rms_conf)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#===================================================================================
|
|
284
|
+
#Qwen3MoeModel
|
|
285
|
+
|
|
286
|
+
def qwen3_moe_model(x: jax.Array, w: dict, cfg: Qwen3MoEModelConfig) -> jax.Array:
|
|
287
|
+
x = w["embeds"][x]
|
|
288
|
+
for l in w["decoder_blocks"]:
|
|
289
|
+
x = qwen3_moe_layer(
|
|
290
|
+
x,
|
|
291
|
+
l,
|
|
292
|
+
cfg.moe_layer_conf
|
|
293
|
+
)
|
|
294
|
+
x = rms_norm(x, w["norm"], cfg.final_rms_conf)
|
|
295
|
+
logits = x @ w["lm_head"]
|
|
296
|
+
return logits
|
|
297
|
+
|
|
298
|
+
def qwen3_moe_model_init(key, cfg: Qwen3MoEModelConfig) -> dict:
|
|
299
|
+
k_embed, k_layers, k_norm, k_head = jax.random.split(key, 4)
|
|
300
|
+
embeds = jax.random.normal(k_embed, shape=(cfg.vocab_size, cfg.d_model)) * (
|
|
301
|
+
cfg.d_model**-0.5
|
|
302
|
+
)
|
|
303
|
+
layer_keys = jax.random.split(k_layers, cfg.num_decoder_blocks)
|
|
304
|
+
decoder_blocks = [
|
|
305
|
+
qwen3_moe_layer_init(k, cfg.moe_layer_conf) for k in layer_keys
|
|
306
|
+
]
|
|
307
|
+
norm = rms_norm_init(k_norm, cfg.final_rms_conf)
|
|
308
|
+
if cfg.tie_word_embeddings:
|
|
309
|
+
lm_head = embeds.T
|
|
310
|
+
else:
|
|
311
|
+
lm_head = jax.random.normal(k_head, shape=(cfg.d_model, cfg.vocab_size)) * (
|
|
312
|
+
cfg.d_model**-0.5
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
"embeds": embeds,
|
|
317
|
+
"decoder_blocks": decoder_blocks,
|
|
318
|
+
"norm": norm,
|
|
319
|
+
"lm_head": lm_head,
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
#===================================================================================
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from adjaxt.sharding import *
|
|
2
|
+
|
|
3
|
+
QWEN3_MOE_WEIGHT_MAP = ModelWeightMap(
|
|
4
|
+
specs=[
|
|
5
|
+
WeightSpec("embeds", "model.embed_tokens.weight"),
|
|
6
|
+
WeightSpec("norm", "model.norm.weight"),
|
|
7
|
+
WeightSpec("lm_head", "lm_head.weight", transpose=True),
|
|
8
|
+
|
|
9
|
+
# Layer Norms
|
|
10
|
+
WeightSpec("decoder_blocks.{i}.input_layernorm", "model.layers.{i}.input_layernorm.weight"),
|
|
11
|
+
WeightSpec("decoder_blocks.{i}.post_attn_layernorm", "model.layers.{i}.post_attention_layernorm.weight"),
|
|
12
|
+
|
|
13
|
+
# Attention Projections & QK Norms
|
|
14
|
+
WeightSpec("decoder_blocks.{i}.attn.q_proj", "model.layers.{i}.self_attn.q_proj.weight", transpose=True),
|
|
15
|
+
WeightSpec("decoder_blocks.{i}.attn.k_proj", "model.layers.{i}.self_attn.k_proj.weight", transpose=True),
|
|
16
|
+
WeightSpec("decoder_blocks.{i}.attn.v_proj", "model.layers.{i}.self_attn.v_proj.weight", transpose=True),
|
|
17
|
+
WeightSpec("decoder_blocks.{i}.attn.o_proj", "model.layers.{i}.self_attn.o_proj.weight", transpose=True),
|
|
18
|
+
WeightSpec("decoder_blocks.{i}.attn.q_norm", "model.layers.{i}.self_attn.q_norm.weight"),
|
|
19
|
+
WeightSpec("decoder_blocks.{i}.attn.k_norm", "model.layers.{i}.self_attn.k_norm.weight"),
|
|
20
|
+
|
|
21
|
+
# MoE Router & Stacked Experts
|
|
22
|
+
WeightSpec("decoder_blocks.{i}.mlp.router", "model.layers.{i}.mlp.gate.weight", transpose=True),
|
|
23
|
+
WeightSpec("decoder_blocks.{i}.mlp.experts.w_gate", "model.layers.{i}.mlp.experts.{e}.gate_proj.weight", transpose=True),
|
|
24
|
+
WeightSpec("decoder_blocks.{i}.mlp.experts.w_up", "model.layers.{i}.mlp.experts.{e}.up_proj.weight", transpose=True),
|
|
25
|
+
WeightSpec("decoder_blocks.{i}.mlp.experts.w_down", "model.layers.{i}.mlp.experts.{e}.down_proj.weight", transpose=True),
|
|
26
|
+
]
|
|
27
|
+
)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from adjaxt.config import *
|
|
2
|
+
from adjaxt.layers import *
|
|
3
|
+
from adjaxt.sharding import *
|
|
4
|
+
from adjaxt.model_maps import *
|
|
5
|
+
from huggingface_hub import snapshot_download, hf_hub_download
|
|
6
|
+
from typing import Callable
|
|
7
|
+
|
|
8
|
+
#=====================================================================================================
|
|
9
|
+
#Qwen3 MoE
|
|
10
|
+
|
|
11
|
+
def qwen3_moe_model_load(cfg: Qwen3MoEModelConfig, ckpt_path: str, token: Optional[str] = None):
|
|
12
|
+
"""
|
|
13
|
+
Loads weights into a Qwen3 MoE JAX model from either a local path
|
|
14
|
+
or directly from a Hugging Face repository repo_id.
|
|
15
|
+
"""
|
|
16
|
+
# 1. Resolve checkpoint path (download from HF hub if not a local folder/file)
|
|
17
|
+
if not os.path.exists(ckpt_path):
|
|
18
|
+
resolved_path = snapshot_download(
|
|
19
|
+
repo_id=ckpt_path,
|
|
20
|
+
allow_patterns=["*.safetensors", "*.json"],
|
|
21
|
+
token=token,
|
|
22
|
+
)
|
|
23
|
+
else:
|
|
24
|
+
resolved_path = ckpt_path
|
|
25
|
+
|
|
26
|
+
# 2. Extract loop/stack dimension limits dynamically from cfg
|
|
27
|
+
dim_sizes = {
|
|
28
|
+
"i": cfg.num_decoder_blocks,
|
|
29
|
+
"e": cfg.moe_layer_conf.moe_block_conf.num_experts,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# 3. Load flat safetensors and reconstruct the nested JAX tree
|
|
33
|
+
weights = load_checkpoint(
|
|
34
|
+
checkpoint_path=resolved_path,
|
|
35
|
+
weight_map=QWEN3_MOE_WEIGHT_MAP,
|
|
36
|
+
dim_sizes=dim_sizes,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# 4. Handle tied word embeddings if configured
|
|
40
|
+
if cfg.tie_word_embeddings:
|
|
41
|
+
weights["lm_head"] = weights["embeds"].T
|
|
42
|
+
|
|
43
|
+
return weights
|
|
44
|
+
|
|
45
|
+
def _get_act_fn(act_name: str) -> Callable[[jax.Array], jax.Array]:
|
|
46
|
+
act_map = {
|
|
47
|
+
"silu": jax.nn.silu,
|
|
48
|
+
"swish": jax.nn.swish,
|
|
49
|
+
"gelu": jax.nn.gelu,
|
|
50
|
+
"relu": jax.nn.relu,
|
|
51
|
+
}
|
|
52
|
+
if act_name not in act_map:
|
|
53
|
+
raise ValueError(f"Unsupported activation function: {act_name}")
|
|
54
|
+
return act_map[act_name]
|
|
55
|
+
|
|
56
|
+
def create_qwen3_moe_config(
|
|
57
|
+
hf_config: dict,
|
|
58
|
+
attn_implementation: StandardAttnImplementation = StandardAttnImplementation.XLA,
|
|
59
|
+
) -> Qwen3MoEModelConfig:
|
|
60
|
+
"""Builds an MoE Qwen3 configuration dataclass from a parsed HF config.json dictionary."""
|
|
61
|
+
hidden_size = hf_config["hidden_size"]
|
|
62
|
+
num_heads = hf_config["num_attention_heads"]
|
|
63
|
+
num_kv_heads = hf_config.get("num_key_value_heads", num_heads)
|
|
64
|
+
head_dim = hf_config.get("head_dim", hidden_size // num_heads)
|
|
65
|
+
rms_eps = hf_config.get("rms_norm_eps", 1e-6)
|
|
66
|
+
moe_intermediate_size = hf_config.get("moe_intermediate_size", hf_config.get("intermediate_size"))
|
|
67
|
+
num_layers = hf_config["num_hidden_layers"]
|
|
68
|
+
vocab_size = hf_config["vocab_size"]
|
|
69
|
+
rope_theta = float(hf_config.get("rope_theta", 1000000.0))
|
|
70
|
+
max_pos = hf_config.get("max_position_embeddings", 32768)
|
|
71
|
+
num_experts = hf_config.get("num_experts", hf_config.get("num_local_experts", 64))
|
|
72
|
+
top_k = hf_config.get("num_experts_per_tok", 8)
|
|
73
|
+
act_fn = _get_act_fn(hf_config.get("hidden_act", "silu"))
|
|
74
|
+
|
|
75
|
+
gqa_conf = GQAAttnConfig(
|
|
76
|
+
implementation=attn_implementation,
|
|
77
|
+
num_kv_groups=num_heads // num_kv_heads,
|
|
78
|
+
is_causal=True,
|
|
79
|
+
)
|
|
80
|
+
q_rms_conf = RMSNormConfig(dim=head_dim, eps=rms_eps)
|
|
81
|
+
k_rms_conf = RMSNormConfig(dim=head_dim, eps=rms_eps)
|
|
82
|
+
|
|
83
|
+
attn_conf = Qwen3AttnConfig(
|
|
84
|
+
gqa_conf=gqa_conf,
|
|
85
|
+
q_rms_conf=q_rms_conf,
|
|
86
|
+
k_rms_conf=k_rms_conf,
|
|
87
|
+
d=hidden_size,
|
|
88
|
+
head_dim=head_dim,
|
|
89
|
+
rope_theta=rope_theta,
|
|
90
|
+
max_position_embeddings=max_pos,
|
|
91
|
+
n_layers=num_layers,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
mlp_conf = Qwen3MLPConfig(
|
|
95
|
+
act_fn=act_fn,
|
|
96
|
+
in_dim=hidden_size,
|
|
97
|
+
hidden_dim=moe_intermediate_size,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
moe_block_conf = Qwen3MoEBlockConfig(
|
|
101
|
+
mlp_conf=mlp_conf,
|
|
102
|
+
top_k=top_k,
|
|
103
|
+
num_experts=num_experts,
|
|
104
|
+
d_model=hidden_size,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
moe_layer_conf = Qwen3MoELayerConfig(
|
|
108
|
+
input_rms_conf=RMSNormConfig(dim=hidden_size, eps=rms_eps),
|
|
109
|
+
attn_conf=attn_conf,
|
|
110
|
+
post_attn_rms_conf=RMSNormConfig(dim=hidden_size, eps=rms_eps),
|
|
111
|
+
moe_block_conf=moe_block_conf,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return Qwen3MoEModelConfig(
|
|
115
|
+
moe_layer_conf=moe_layer_conf,
|
|
116
|
+
final_rms_conf=RMSNormConfig(dim=hidden_size, eps=rms_eps),
|
|
117
|
+
num_decoder_blocks=num_layers,
|
|
118
|
+
vocab_size=vocab_size,
|
|
119
|
+
d_model=hidden_size,
|
|
120
|
+
tie_word_embeddings=hf_config.get("tie_word_embeddings", False),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def qwen3_moe_config_from_pretrained(
|
|
125
|
+
model_name_or_path: str,
|
|
126
|
+
attn_implementation: StandardAttnImplementation = StandardAttnImplementation.XLA,
|
|
127
|
+
token: Optional[str] = None,
|
|
128
|
+
) -> Qwen3MoEModelConfig:
|
|
129
|
+
"""Loads config.json from local disk or HuggingFace Hub and constructs the corresponding dataclass."""
|
|
130
|
+
if os.path.exists(model_name_or_path):
|
|
131
|
+
config_file = (
|
|
132
|
+
model_name_or_path
|
|
133
|
+
if os.path.isfile(model_name_or_path)
|
|
134
|
+
else os.path.join(model_name_or_path, "config.json")
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
config_file = hf_hub_download(
|
|
138
|
+
repo_id=model_name_or_path,
|
|
139
|
+
filename="config.json",
|
|
140
|
+
token=token,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
with open(config_file, "r", encoding="utf-8") as f:
|
|
144
|
+
hf_config = json.load(f)
|
|
145
|
+
|
|
146
|
+
return create_qwen3_moe_config(hf_config, attn_implementation)
|
|
147
|
+
|
|
148
|
+
#=====================================================================================================
|