|
16 | 16 |
|
17 | 17 | import math |
18 | 18 | from typing import Any, List, Optional, Tuple |
| 19 | +from flax import nnx |
19 | 20 | import flax.linen as nn |
20 | 21 | import jax |
21 | 22 | import jax.numpy as jnp |
@@ -400,6 +401,224 @@ def __call__( |
400 | 401 | return hidden_states, all_hidden_states |
401 | 402 |
|
402 | 403 |
|
| 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 | + |
403 | 622 | # ----------------------------------------------------------------------------- |
404 | 623 | # Weight Mapping & Conversion Utilities |
405 | 624 | # ----------------------------------------------------------------------------- |
|
0 commit comments