metrale_model_engine/model/
lm_head_fp8_rows.rs1use anyhow::{Context, Result, ensure};
18use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
19use metrale_model_layers::layers::W8a8Ctx;
20use metrale_model_layers::layers::ops::{self, W8a8Weight};
21use metrale_model_layers::weight_map::{Fp8Weight, WeightQuantFormat};
22
23use super::types::TransformerModel;
24
25pub(crate) struct LmHeadRows {
29 rt8: KernelHandle,
30 rt16: KernelHandle,
31 w8a8: Option<(W8a8Ctx, W8a8Weight)>,
32 pub(super) nvfp4_rows: bool,
33}
34
35impl LmHeadRows {
36 pub(crate) fn resolve(gpu: &dyn GpuBackend) -> Self {
37 let rt = |f| metrale_model_layers::layers::try_kernel(gpu, "fp8_gemv_rt", f);
38 Self {
39 rt8: rt("fp8_gemv_rowscale_batch8_rt2"),
40 rt16: rt("fp8_gemv_rowscale_batch16_rt2"),
41 w8a8: None,
42 nvfp4_rows: false,
43 }
44 }
45}
46
47impl TransformerModel {
48 pub fn install_declared_lm_head_w8a8(&mut self) -> Result<()> {
52 let policy = metrale_config::WeightQuantPolicy::for_checkpoint(
53 metrale_model_layers::layers::weight_quantization(),
54 self.config.quantization_config.as_ref(),
55 metrale_model_layers::layers::kernel_caps(),
56 );
57 let fp8 = policy.fp8_decode_act("lm_head")
58 == Some(metrale_config::weight_quantization::ActFormat::Fp8);
59 if !fp8 || self.lm_head_fp8.is_none() {
60 return Ok(());
61 }
62 let ctx = W8a8Ctx::new(self.gpu.as_ref(), self.config.hidden_size as u32)?;
63 self.set_lm_head_w8a8(ctx)?;
64 tracing::info!("W8A8 lm_head (declared FP8 W8A8, per-row E4M3 read in place)");
65 Ok(())
66 }
67
68 pub fn set_lm_head_w8a8(&mut self, ctx: W8a8Ctx) -> Result<()> {
71 let fp8 = self
72 .lm_head_fp8
73 .context("W8A8 lm_head needs the FP8 head (lm_head_fp8)")?;
74 let w = W8a8Weight::new(&[Fp8Weight {
75 weight: fp8.weight,
76 row_scale: fp8.row_scale,
77 n: self.config.vocab_size as u32,
78 k: self.config.hidden_size as u32,
79 scale_format: WeightQuantFormat::Fp8PerRow,
80 }])?;
81 ensure!(
82 ctx.available(&w, 1),
83 "W8A8 lm_head kernels or scratch unavailable"
84 );
85 self.lm_head_rows.w8a8 = Some((ctx, w));
86 Ok(())
87 }
88
89 pub(super) fn lm_head_fp8_run(
92 &self,
93 hidden: DevicePtr,
94 rows: usize,
95 logits: DevicePtr,
96 stream: u64,
97 ) -> Result<bool> {
98 let Some(ref fp8) = self.lm_head_fp8 else {
99 return Ok(false);
100 };
101 let gpu = self.gpu.as_ref();
102 let (h, v) = (self.config.hidden_size, self.config.vocab_size);
103 if let Some((ref ctx, ref w)) = self.lm_head_rows.w8a8 {
104 let mut done = 0;
107 while done < rows {
108 let m = (rows - done).min(ops::W8A8_MAX_ROWS);
109 let (x, out) = (hidden.offset(done * h * 2), logits.offset(done * v * 2));
110 ensure!(
111 ctx.proj(gpu, w, x, h as u32, m, out, v as u32, stream)?,
112 "W8A8 lm_head: {m} rows not servable"
113 );
114 done += m;
115 }
116 return Ok(true);
117 }
118 let k = &self.lm_head_rows;
119 let mut done = 0;
120 while done < rows {
121 let (x, out, left) = (
122 hidden.offset(done * h * 2),
123 logits.offset(done * v * 2),
124 rows - done,
125 );
126 let step = match left {
127 1 => {
128 ops::dense_gemv_fp8w(
129 gpu,
130 self.dense_gemv_fp8w_kernel,
131 x,
132 fp8,
133 out,
134 v as u32,
135 h as u32,
136 stream,
137 )?;
138 1
139 }
140 2 if self.dense_gemv_fp8w_batch2_kernel.0 != 0 => {
141 let kh = self.dense_gemv_fp8w_batch2_kernel;
142 ops::dense_gemv_fp8w_batch2(gpu, kh, x, fp8, out, v as u32, h as u32, stream)?;
143 2
144 }
145 3..=8 if k.rt8.0 != 0 => {
146 let m = left as u32;
147 ops::fp8_gemv_rowscale_batch8_rt2(
148 gpu, k.rt8, x, fp8, out, m, v as u32, h as u32, stream,
149 )?;
150 left
151 }
152 _ if left > 2 && k.rt16.0 != 0 => {
153 let m = left.min(16);
154 let (mu, (vu, hu)) = (m as u32, (v as u32, h as u32));
155 ops::fp8_gemv_rowscale_batch16_rt2(
156 gpu, k.rt16, x, fp8, out, mu, vu, hu, stream,
157 )?;
158 m
159 }
160 _ => {
161 ops::dense_gemv_fp8w(
162 gpu,
163 self.dense_gemv_fp8w_kernel,
164 x,
165 fp8,
166 out,
167 v as u32,
168 h as u32,
169 stream,
170 )?;
171 1
172 }
173 };
174 done += step;
175 }
176 Ok(true)
177 }
178}