spark_model/weight_loader/gemma4.rs
1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! Weight loader for Gemma-4 models.
4//!
5//! Gemma-4 is a pure-attention model (no SSM) with:
6//! - Sliding + full attention pattern (dual RoPE theta)
7//! - GeGLU activation (gate + up projection fused)
8//! - Explicit head_dim=256
9//! - Tied word embeddings
10//! - 4 layer norms per layer (input, post_attn, pre_ffn, post_ffn)
11//! - Per-layer scalar (`layer_scalar`)
12//! - BF16 attention weights, NVFP4 MLP weights (Standard triple-scale format)
13//!
14//! Weight prefix: `model.language_model.` (auto-detected from safetensors keys
15//! by the main loader when `nested_config = true`).
16
17use anyhow::Result;
18use atlas_core::config::ModelConfig;
19use spark_runtime::gpu::GpuBackend;
20use spark_runtime::kv_cache::KvCacheDtype;
21use spark_runtime::weights::WeightStore;
22
23use super::ModelWeightLoader;
24use crate::layer::TransformerLayer;
25use crate::weight_map::{DenseWeight, MtpWeights};
26
27mod loader_a;
28mod loader_b;
29
30pub struct Gemma4WeightLoader;
31
32impl ModelWeightLoader for Gemma4WeightLoader {
33 fn supports_tp(&self) -> bool {
34 // Gemma-4 has per-layer dim overrides: sliding layers use
35 // head_dim=256 explicitly, full-attention layers use whatever
36 // the on-disk weight's [out, in] shape declares. We slice
37 // each layer's q/k/v BF16 weights with the layer-specific
38 // full dims (read from store), then pass the per-rank-local
39 // dims to quantize_to_nvfp4. K=V aliasing in sliding layers
40 // works under TP because both K and V share the same per-rank
41 // slice pointer (the alias survives sharding).
42 // MLP weights stay full-replica per rank — Gemma-4 31B is
43 // dense; sharding the GeGLU MLP would double the TP win but
44 // requires NVFP4 byte-slicing in `quantized_any`-loaded paths
45 // (deferred — leaves Gemma-4 functionally correct under TP
46 // with extra MLP memory per rank).
47 true
48 }
49
50 fn load_layers(
51 &self,
52 store: &WeightStore,
53 config: &ModelConfig,
54 gpu: &dyn GpuBackend,
55 layer_kv_dtypes: &[KvCacheDtype],
56 ) -> Result<Vec<Box<dyn TransformerLayer>>> {
57 loader_a::load_layers_impl(store, config, gpu, layer_kv_dtypes)
58 }
59
60 fn load_embedding(
61 &self,
62 store: &WeightStore,
63 config: &ModelConfig,
64 _gpu: &dyn GpuBackend,
65 ) -> Result<DenseWeight> {
66 loader_b::load_embedding_impl(store, config)
67 }
68
69 fn load_final_norm(
70 &self,
71 store: &WeightStore,
72 config: &ModelConfig,
73 _gpu: &dyn GpuBackend,
74 ) -> Result<DenseWeight> {
75 loader_b::load_final_norm_impl(store, config)
76 }
77
78 fn load_lm_head(
79 &self,
80 store: &WeightStore,
81 config: &ModelConfig,
82 _gpu: &dyn GpuBackend,
83 ) -> Result<DenseWeight> {
84 loader_b::load_lm_head_impl(store, config)
85 }
86
87 fn load_mtp_weights(
88 &self,
89 store: &WeightStore,
90 config: &ModelConfig,
91 gpu: &dyn GpuBackend,
92 ) -> Result<Option<MtpWeights>> {
93 loader_b::load_mtp_weights_impl(store, config, gpu)
94 }
95
96 fn kv_layer_dims(&self, config: &ModelConfig) -> Vec<(usize, usize)> {
97 loader_b::kv_layer_dims_impl(config)
98 }
99}