metrale_model_layers/layers/moe/
nvfp4_lean.rs1use anyhow::Context;
24
25use super::forward_nvfp4_grouped_decode::{Nvfp4GroupedKernels, Nvfp4GroupedLaunch};
26use super::*;
27
28impl Nvfp4GroupedKernels {
29 pub(super) fn lean_launch(&self) -> Option<Nvfp4GroupedLaunch> {
31 self.lean.then_some(Nvfp4GroupedLaunch {
32 gate_up: self.gate_up_tc_lean,
33 gate_up_geometry: ops::NVFP4_GROUPED_GATE_UP_TC,
34 down: self.down_tc_lean,
35 down_geometry: ops::NVFP4_GROUPED_DOWN_TC,
36 max_rows: super::NVFP4_GROUPED_DECODE_TC_MAX_ROWS,
37 })
38 }
39}
40
41impl MoeLayer {
42 pub fn nvfp4_experts_lean(&self) -> bool {
44 self.nvfp4_grouped.lean
45 }
46
47 pub(super) fn refuse_lean_layout(&self, arm: &str) -> Result<()> {
50 anyhow::ensure!(
51 !self.nvfp4_grouped.lean,
52 "{arm}: this layer's NVFP4 experts were repacked for the lean tensor-core decode at \
53 load; only `forward_nvfp4_grouped_decode` reads them (set METRALE_NO_MOE_NVFP4_TC to \
54 keep the row-major layout)"
55 );
56 Ok(())
57 }
58
59 pub fn nvfp4_lean_refusal(&self, config: &metrale_config::ModelConfig) -> Option<&'static str> {
62 let (h, inter) = (
63 config.hidden_size as u32,
64 config.moe_intermediate_size as u32,
65 );
66 let k = &self.nvfp4_grouped;
67 let levers = ops::ModelLevers::get();
68 let local = |q: &QuantizedWeight| !q.weight.is_null() && !q.weight_scale.is_null();
69 let checks: [(bool, &'static str); 12] = [
70 (
71 k.declared_experts
72 && self.fp8_shared_expert.is_none()
73 && self.fp8_down_weight_ptrs.is_none(),
74 "experts are not the checkpoint's declared NVFP4 alone (the FP8 kernels would read \
75 the lifted SiLU rows)",
76 ),
77 (
78 super::forward_nvfp4_grouped_decode::nvfp4_grouped_tc_enabled(),
79 "METRALE_NO_MOE_NVFP4_TC",
80 ),
81 (
82 k.gate_up_tc_lean.0 != 0 && k.down_tc_lean.0 != 0 && k.lean_repack.0 != 0,
83 "lean kernels not in the kernel set",
84 ),
85 (
86 ops::nvfp4_grouped_tc_shape_ok(inter, h, ops::NVFP4_GROUPED_GATE_UP_TC)
87 && ops::nvfp4_grouped_tc_shape_ok(h, inter, ops::NVFP4_GROUPED_DOWN_TC)
88 && ops::nvfp4_lean_repack_shape_ok(inter, h)
89 && ops::nvfp4_lean_repack_shape_ok(h, inter)
90 && config.shared_expert_intermediate_size == config.moe_intermediate_size,
91 "shape",
92 ),
93 (
94 self.gate_ptrs_t.is_some()
95 && self.up_ptrs_t.is_some()
96 && self.down_ptrs_t.is_some()
97 && self.shared_gate_t.is_some()
98 && self.shared_up_t.is_some()
99 && self.shared_down_t.is_some(),
100 "prefill reads the row-major tables (no transposed copies)",
101 ),
102 (
103 self.cutlass_grouped_host.is_none() && !self.unified_layout,
104 "CUTLASS grouped prefill or the unified layout",
105 ),
106 (
107 !self.weights.experts.is_empty()
108 && self
109 .weights
110 .experts
111 .iter()
112 .all(|e| local(&e.gate_proj) && local(&e.up_proj) && local(&e.down_proj))
113 && config.ep_world_size <= 1,
114 "experts not all local",
115 ),
116 (
117 local(&self.weights.shared_expert.gate_proj)
118 && local(&self.weights.shared_expert.up_proj)
119 && local(&self.weights.shared_expert.down_proj),
120 "no NVFP4 shared expert",
121 ),
122 (
123 self.experts_scale_kind == crate::weight_map::WeightQuantFormat::Nvfp4
124 && self.shared_experts_scale_kind
125 == crate::weight_map::WeightQuantFormat::Nvfp4,
126 "scale format",
127 ),
128 (
129 !levers.fp32_gate && !self.fp32_routing_active(levers),
130 "FP32 gate or routing",
131 ),
132 (
133 crate::layers::moe_grouped_decode_enabled(),
134 "METRALE_NO_MOE_GROUPED_DECODE",
135 ),
136 (self.lora.is_none(), "MoE LoRA"),
137 ];
138 checks.iter().find(|(ok, _)| !ok).map(|(_, why)| *why)
139 }
140
141 pub fn repack_nvfp4_experts_lean(
145 &mut self,
146 gpu: &dyn GpuBackend,
147 config: &metrale_config::ModelConfig,
148 stream: u64,
149 ) -> Result<bool> {
150 if let Some(why) = self.nvfp4_lean_refusal(config) {
151 tracing::debug!("NVFP4 experts keep the row-major layout: {why}");
152 return Ok(false);
153 }
154 let (h, inter) = (
155 config.hidden_size as u32,
156 config.moe_intermediate_size as u32,
157 );
158 let kernel = self.nvfp4_grouped.lean_repack;
159 let bad = gpu.alloc(16)?;
160 gpu.copy_h2d(&[0u8; 4], bad)?;
161 let sh = &self.weights.shared_expert;
162 let mats = self
163 .weights
164 .experts
165 .iter()
166 .chain(std::iter::once(sh))
167 .flat_map(|e| {
168 [
169 (&e.gate_proj, inter, h),
170 (&e.up_proj, inter, h),
171 (&e.down_proj, h, inter),
172 ]
173 });
174 let launched = mats
175 .map(|(q, n, k)| {
176 ops::nvfp4_tc_lean_repack(gpu, kernel, q.weight, q.weight_scale, n, k, bad, stream)
177 })
178 .collect::<Result<Vec<()>>>();
179 let synced = launched.and_then(|_| gpu.synchronize(stream));
180 let mut flag = [0u8; 4];
181 let read = synced.and_then(|()| gpu.copy_d2h(bad, &mut flag));
182 gpu.free(bad)?;
183 read.context("nvfp4_tc_lean_repack")?;
184 anyhow::ensure!(
185 flag == [0u8; 4],
186 "nvfp4_tc_lean_repack: a negative or NaN E4M3 block scale in this layer's NVFP4 experts"
187 );
188 self.nvfp4_grouped.lean = true;
189 Ok(true)
190 }
191}