spark_model/layers/glm5next_mlp/
weights.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! The GLM MLP weight contract, already sharded for this rank.
4
5use spark_runtime::gpu::DevicePtr;
6
7/// One NVFP4 projection as ModelOpt stores it: packed `e2m1` pairs, per-block `e4m3` scales,
8/// and one global `f32` scale.
9///
10/// 🪤 The three travel together and are meaningless apart. `weight_scale_2` is a **scalar read
11/// off the device at load** and passed by value — `w4a16_gemm` takes it as `arg_f32`, not as a
12/// pointer, so uploading it and passing the address silently reinterprets a pointer as a float.
13#[derive(Debug, Clone, Copy)]
14pub struct Nvfp4Proj {
15    /// `[out, in/2]` U8 — two `e2m1` codes per byte.
16    pub packed: DevicePtr,
17    /// `[out, in/16]` F8_E4M3 block scales.
18    pub scale: DevicePtr,
19    /// The single global F32 scale.
20    pub scale_2: f32,
21}
22
23/// A dense SwiGLU MLP in BF16 — layers `0..first_k_dense_replace`, and the shared expert of
24/// every routed layer. Same shape, same kernels, different widths.
25///
26/// TP: `gate_proj`/`up_proj` are column-parallel (**row**-sliced, since each is stored `[inter,
27/// hidden]`), `down_proj` is row-parallel (**column**-sliced on `[hidden, inter]`). The output
28/// is therefore a partial sum whenever `tp_world_size > 1`.
29#[derive(Debug, Clone, Copy)]
30pub struct Glm5NextDenseMlpWeights {
31    /// `[local_inter, hidden]` BF16.
32    pub gate_proj: DevicePtr,
33    /// `[local_inter, hidden]` BF16.
34    pub up_proj: DevicePtr,
35    /// `[hidden, local_inter]` BF16 — row-parallel.
36    pub down_proj: DevicePtr,
37}
38
39/// One routed expert. NVFP4, owned **whole** by one EP rank — never split further.
40#[derive(Debug, Clone, Copy)]
41pub struct Glm5NextExpertWeights {
42    pub gate_proj: Nvfp4Proj,
43    pub up_proj: Nvfp4Proj,
44    pub down_proj: Nvfp4Proj,
45}
46
47/// Device-side pointer tables for ONE projection across the **full** expert set.
48///
49/// Indexed by GLOBAL expert id, so the grouped kernel can go straight from the router's
50/// on-device `ids` to weights with no host round trip. Experts another EP rank owns carry a
51/// **null** `packed`/`scale` pointer; the kernel writes nothing for those slots and the
52/// caller's pre-zeroed output row stands.
53#[derive(Debug, Clone, Copy)]
54pub struct Glm5NextExpertPtrTable {
55    /// `[num_experts]` U64 device pointers to each expert's packed NVFP4 weight.
56    pub packed_ptrs: DevicePtr,
57    /// `[num_experts]` U64 device pointers to each expert's block scales.
58    pub scale_ptrs: DevicePtr,
59    /// `[num_experts]` F32 per-expert `weight_scale_2`.
60    pub scale2_vals: DevicePtr,
61}
62
63/// The three projections' pointer tables for one routed site.
64#[derive(Debug, Clone, Copy)]
65pub struct Glm5NextMoePtrTables {
66    pub gate: Glm5NextExpertPtrTable,
67    pub up: Glm5NextExpertPtrTable,
68    pub down: Glm5NextExpertPtrTable,
69}
70
71/// A routed MoE site's weights for this rank.
72pub struct Glm5NextMoeWeights {
73    /// `[num_experts, hidden]` BF16 router. 🪤 **REPLICATED, and it must stay that way** — see
74    /// the module header. Consumed through the FP32-out GEMM.
75    pub router: DevicePtr,
76    /// `[num_experts]` F32 selection bias (`gate.e_score_correction_bias`).
77    ///
78    /// 🪤 Steers SELECTION ONLY. The emitted weight is the chosen expert's *unbiased* score.
79    pub router_bias: DevicePtr,
80    /// The shared expert — dense BF16, TP-sharded, added to every token unscaled.
81    pub shared: Glm5NextDenseMlpWeights,
82    /// Exactly `local_experts` entries, indexed by **local slot**, in ascending global id.
83    ///
84    /// 🪤 Indexed by `Glm5NextMlpConfig::local_slot(global_id)`, never by the global id.
85    pub experts: Vec<Glm5NextExpertWeights>,
86    /// Global-id-indexed device pointer tables over the same experts, for the grouped
87    /// device-dispatch forward. Null entries mark remote ids.
88    pub ptrs: Glm5NextMoePtrTables,
89}