Skip to content

Commit 772fbae

Browse files
committed
Add NNXFlaxQwen3Model and NNX Qwen3 sub-modules
1 parent 63f7d0b commit 772fbae

1 file changed

Lines changed: 219 additions & 0 deletions

File tree

src/maxdiffusion/models/qwen3_flax.py

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import math
1818
from typing import Any, List, Optional, Tuple
19+
from flax import nnx
1920
import flax.linen as nn
2021
import jax
2122
import jax.numpy as jnp
@@ -400,6 +401,224 @@ def __call__(
400401
return hidden_states, all_hidden_states
401402

402403

404+
# -----------------------------------------------------------------------------
405+
# NNX Transformer Model Implementations for Qwen3
406+
# -----------------------------------------------------------------------------
407+
408+
409+
class NNXFlaxQwen3RMSNorm(nnx.Module):
410+
411+
def __init__(self, rngs: nnx.Rngs, dim: int, eps: float = 1e-6, dtype: jnp.dtype = jnp.float32):
412+
self.eps = eps
413+
self.dtype = dtype
414+
self.weight = nnx.Param(jnp.ones((dim,), dtype=dtype))
415+
416+
def __call__(self, x: jnp.ndarray) -> jnp.ndarray:
417+
x_float = x.astype(jnp.float32)
418+
variance = jnp.mean(jnp.square(x_float), axis=-1, keepdims=True)
419+
normed = x_float * jax.lax.rsqrt(variance + self.eps)
420+
return (normed.astype(self.dtype)) * self.weight.value
421+
422+
423+
class NNXFlaxQwen3MLP(nnx.Module):
424+
425+
def __init__(self, rngs: nnx.Rngs, config: FlaxQwen3Config):
426+
self.config = config
427+
self.gate_proj = nnx.Linear(
428+
in_features=config.hidden_size,
429+
out_features=config.intermediate_size,
430+
use_bias=False,
431+
kernel_init=nnx.with_partitioning(nnx.initializers.lecun_normal(), ("embed", "mlp")),
432+
dtype=config.dtype,
433+
rngs=rngs,
434+
)
435+
self.up_proj = nnx.Linear(
436+
in_features=config.hidden_size,
437+
out_features=config.intermediate_size,
438+
use_bias=False,
439+
kernel_init=nnx.with_partitioning(nnx.initializers.lecun_normal(), ("embed", "mlp")),
440+
dtype=config.dtype,
441+
rngs=rngs,
442+
)
443+
self.down_proj = nnx.Linear(
444+
in_features=config.intermediate_size,
445+
out_features=config.hidden_size,
446+
use_bias=False,
447+
kernel_init=nnx.with_partitioning(nnx.initializers.lecun_normal(), ("mlp", "embed")),
448+
dtype=config.dtype,
449+
rngs=rngs,
450+
)
451+
452+
def __call__(self, x: jnp.ndarray) -> jnp.ndarray:
453+
return self.down_proj(jax.nn.silu(self.gate_proj(x)) * self.up_proj(x))
454+
455+
456+
class NNXFlaxQwen3Attention(nnx.Module):
457+
458+
def __init__(self, rngs: nnx.Rngs, config: FlaxQwen3Config):
459+
self.config = config
460+
self.num_heads = config.num_attention_heads
461+
self.num_kv_heads = config.num_key_value_heads
462+
self.head_dim = config.head_dim
463+
464+
self.q_proj = nnx.Linear(
465+
in_features=config.hidden_size,
466+
out_features=config.num_attention_heads * config.head_dim,
467+
use_bias=False,
468+
kernel_init=nnx.with_partitioning(nnx.initializers.lecun_normal(), ("embed", "heads")),
469+
dtype=config.dtype,
470+
rngs=rngs,
471+
)
472+
self.k_proj = nnx.Linear(
473+
in_features=config.hidden_size,
474+
out_features=config.num_key_value_heads * config.head_dim,
475+
use_bias=False,
476+
kernel_init=nnx.with_partitioning(nnx.initializers.lecun_normal(), ("embed", "heads")),
477+
dtype=config.dtype,
478+
rngs=rngs,
479+
)
480+
self.v_proj = nnx.Linear(
481+
in_features=config.hidden_size,
482+
out_features=config.num_key_value_heads * config.head_dim,
483+
use_bias=False,
484+
kernel_init=nnx.with_partitioning(nnx.initializers.lecun_normal(), ("embed", "heads")),
485+
dtype=config.dtype,
486+
rngs=rngs,
487+
)
488+
self.o_proj = nnx.Linear(
489+
in_features=config.num_attention_heads * config.head_dim,
490+
out_features=config.hidden_size,
491+
use_bias=False,
492+
kernel_init=nnx.with_partitioning(nnx.initializers.lecun_normal(), ("heads", "embed")),
493+
dtype=config.dtype,
494+
rngs=rngs,
495+
)
496+
497+
self.q_norm = NNXFlaxQwen3RMSNorm(rngs=rngs, dim=config.head_dim, eps=config.rms_norm_eps, dtype=config.dtype)
498+
self.k_norm = NNXFlaxQwen3RMSNorm(rngs=rngs, dim=config.head_dim, eps=config.rms_norm_eps, dtype=config.dtype)
499+
500+
def __call__(
501+
self,
502+
x: jnp.ndarray,
503+
attention_mask: Optional[jnp.ndarray] = None,
504+
cos_table: Optional[jnp.ndarray] = None,
505+
sin_table: Optional[jnp.ndarray] = None,
506+
) -> jnp.ndarray:
507+
batch_size, seq_len, _ = x.shape
508+
509+
q = self.q_proj(x)
510+
k = self.k_proj(x)
511+
v = self.v_proj(x)
512+
513+
q = q.reshape(batch_size, seq_len, self.num_heads, self.head_dim)
514+
k = k.reshape(batch_size, seq_len, self.num_kv_heads, self.head_dim)
515+
v = v.reshape(batch_size, seq_len, self.num_kv_heads, self.head_dim)
516+
517+
q = self.q_norm(q)
518+
k = self.k_norm(k)
519+
520+
if cos_table is not None and sin_table is not None:
521+
q, k = apply_qwen3_rotary_pos_emb(q, k, cos_table, sin_table)
522+
523+
if self.num_kv_heads != self.num_heads:
524+
num_repeats = self.num_heads // self.num_kv_heads
525+
k = jnp.repeat(k, num_repeats, axis=2)
526+
v = jnp.repeat(v, num_repeats, axis=2)
527+
528+
q = jnp.transpose(q, (0, 2, 1, 3))
529+
k = jnp.transpose(k, (0, 2, 1, 3))
530+
v = jnp.transpose(v, (0, 2, 1, 3))
531+
532+
scale = 1.0 / math.sqrt(self.head_dim)
533+
scores = jnp.matmul(q, jnp.transpose(k, (0, 1, 3, 2))) * scale
534+
535+
if attention_mask is not None:
536+
scores = scores + attention_mask
537+
538+
attn_probs = jax.nn.softmax(scores, axis=-1)
539+
output = jnp.matmul(attn_probs, v)
540+
output = jnp.transpose(output, (0, 2, 1, 3))
541+
output = output.reshape(batch_size, seq_len, -1)
542+
output = self.o_proj(output)
543+
return output
544+
545+
546+
class NNXFlaxQwen3DecoderLayer(nnx.Module):
547+
548+
def __init__(self, rngs: nnx.Rngs, config: FlaxQwen3Config):
549+
self.config = config
550+
self.input_layernorm = NNXFlaxQwen3RMSNorm(
551+
rngs=rngs, dim=config.hidden_size, eps=config.rms_norm_eps, dtype=config.dtype
552+
)
553+
self.self_attn = NNXFlaxQwen3Attention(rngs=rngs, config=config)
554+
self.post_attention_layernorm = NNXFlaxQwen3RMSNorm(
555+
rngs=rngs, dim=config.hidden_size, eps=config.rms_norm_eps, dtype=config.dtype
556+
)
557+
self.mlp = NNXFlaxQwen3MLP(rngs=rngs, config=config)
558+
559+
def __call__(
560+
self,
561+
x: jnp.ndarray,
562+
attention_mask: Optional[jnp.ndarray] = None,
563+
cos_table: Optional[jnp.ndarray] = None,
564+
sin_table: Optional[jnp.ndarray] = None,
565+
) -> jnp.ndarray:
566+
residual = x
567+
normed_x = self.input_layernorm(x)
568+
attn_out = self.self_attn(
569+
normed_x,
570+
attention_mask=attention_mask,
571+
cos_table=cos_table,
572+
sin_table=sin_table,
573+
)
574+
x = residual + attn_out
575+
576+
mlp_out = self.mlp(self.post_attention_layernorm(x))
577+
x = x + mlp_out
578+
return x
579+
580+
581+
class NNXFlaxQwen3Model(nnx.Module):
582+
583+
def __init__(self, rngs: nnx.Rngs, config: FlaxQwen3Config):
584+
self.config = config
585+
self.embed_tokens = nnx.Embed(
586+
num_embeddings=config.vocab_size,
587+
features=config.hidden_size,
588+
embedding_init=nnx.with_partitioning(nnx.initializers.normal(stddev=config.hidden_size**-0.5), ("vocab", "embed")),
589+
dtype=config.dtype,
590+
rngs=rngs,
591+
)
592+
self.layers = [NNXFlaxQwen3DecoderLayer(rngs=rngs, config=config) for _ in range(config.num_hidden_layers)]
593+
self.norm = NNXFlaxQwen3RMSNorm(rngs=rngs, dim=config.hidden_size, eps=config.rms_norm_eps, dtype=config.dtype)
594+
595+
def __call__(
596+
self,
597+
input_ids: jnp.ndarray,
598+
attention_mask: Optional[jnp.ndarray] = None,
599+
) -> Tuple[jnp.ndarray, List[jnp.ndarray]]:
600+
hidden_states = self.embed_tokens(input_ids)
601+
all_hidden_states = [hidden_states]
602+
603+
cos_table, sin_table = precompute_qwen3_freqs_cis(
604+
head_dim=self.config.head_dim,
605+
max_seq_len=self.config.max_position_embeddings,
606+
theta=self.config.rope_theta,
607+
)
608+
609+
for layer in self.layers:
610+
hidden_states = layer(
611+
hidden_states,
612+
attention_mask=attention_mask,
613+
cos_table=cos_table,
614+
sin_table=sin_table,
615+
)
616+
all_hidden_states.append(hidden_states)
617+
618+
hidden_states = self.norm(hidden_states)
619+
return hidden_states, all_hidden_states
620+
621+
403622
# -----------------------------------------------------------------------------
404623
# Weight Mapping & Conversion Utilities
405624
# -----------------------------------------------------------------------------

0 commit comments

Comments
 (0)