use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use super::attention::generation_attn_mode;
use super::gemm::quant_tc_flops;
use super::interpolation::Grid3;
use super::perf_interp::{self, Node, OpInterpConfig};
use super::{SourceResolver, kernel_source_ok};
use crate::common::enums::{FmhaQuantMode, KvCacheQuantMode};
use crate::common::error::AicError;
use crate::common::system_spec::SystemSpec;
use crate::config::{PerfDbSources, PerfSource};
use crate::perf_database::parquet_loader::PerfReader;
const CONTEXT_AXES: &[&str] = &["num_heads", "seq_len", "batch"];
const GENERATION_AXES: &[&str] = &["num_heads", "batch", "seq_len"];
pub struct WideEpMlaTable {
data_root: PathBuf,
system_spec: SystemSpec,
context_sources: Vec<PerfSource>,
generation_sources: Vec<PerfSource>,
context: OnceLock<Result<WideEpContextMlaGrids, AicError>>,
generation: OnceLock<Result<WideEpGenerationMlaGrids, AicError>>,
}
pub struct WideEpContextMlaGrids {
pub by_keys: BTreeMap<ContextKey, Node>,
}
pub struct WideEpGenerationMlaGrids {
pub by_keys: BTreeMap<GenerationKey, Node>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ContextKey {
pub kernel_source: String,
pub fmha_quant: String,
pub kv_quant: String,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct GenerationKey {
pub kernel_source: String,
pub kv_quant: String,
}
impl WideEpMlaTable {
pub fn new(data_root: PathBuf, system_spec: SystemSpec) -> Self {
Self::with_sources(
data_root,
system_spec,
&SourceResolver::fixed(PerfDbSources::default()),
)
.expect("fixed-map resolution is infallible")
}
pub fn with_sources(
data_root: PathBuf,
system_spec: SystemSpec,
resolver: &SourceResolver,
) -> Result<Self, AicError> {
let context_sources =
resolver.sources_for("wideep_context_mla_perf.parquet", &data_root)?;
let generation_sources =
resolver.sources_for("wideep_generation_mla_perf.parquet", &data_root)?;
Ok(Self {
data_root,
system_spec,
context_sources,
generation_sources,
context: OnceLock::new(),
generation: OnceLock::new(),
})
}
pub fn query_context(
&self,
b: u32,
full_seq_tokens: u32,
num_heads: u32,
kv_quant: KvCacheQuantMode,
fmha_quant: FmhaQuantMode,
kernel_source: &str,
) -> Result<f64, AicError> {
let main_flops = quant_tc_flops(&self.system_spec, fmha_quant.mapping())?;
let bf16_flops = quant_tc_flops(&self.system_spec, FmhaQuantMode::Bfloat16.mapping())?;
let grids = self.load_context()?;
let key = ContextKey {
kernel_source: kernel_source.to_string(),
fmha_quant: fmha_quant.name().to_string(),
kv_quant: kv_quant.name().to_string(),
};
let node = grids
.by_keys
.get(&key)
.ok_or_else(|| missing("WideEP context MLA", &self.data_root, format!("{key:?}")))?;
let _ = kv_quant;
let spec = &self.system_spec;
let sol = move |c: &[f64]| {
wideep_context_mla_sol_ms(
spec,
fmha_quant,
wideep_num_head(c[0]),
c[1],
0.0,
c[2],
main_flops,
bf16_flops,
)
};
let cfg = OpInterpConfig::grid_sqrt_axis(CONTEXT_AXES, 1, &sol);
perf_interp::query(
&cfg,
node,
&[num_heads as f64, full_seq_tokens as f64, b as f64],
)
}
pub fn query_generation(
&self,
b: u32,
sequence_tokens: u32,
num_heads: u32,
kv_quant: KvCacheQuantMode,
kernel_source: &str,
) -> Result<f64, AicError> {
let fmha_quant = generation_attn_mode(&self.system_spec, kv_quant);
let main_flops = quant_tc_flops(&self.system_spec, fmha_quant.mapping())?;
let bf16_flops = quant_tc_flops(&self.system_spec, FmhaQuantMode::Bfloat16.mapping())?;
let grids = self.load_generation()?;
let key = GenerationKey {
kernel_source: kernel_source.to_string(),
kv_quant: kv_quant.name().to_string(),
};
let node = grids
.by_keys
.get(&key)
.ok_or_else(|| missing("WideEP generation MLA", &self.data_root, format!("{key:?}")))?;
let spec = &self.system_spec;
let sol = move |c: &[f64]| {
wideep_generation_mla_sol_ms(
spec,
fmha_quant,
wideep_num_head(c[0]),
c[1],
c[2],
main_flops,
bf16_flops,
)
};
let cfg = OpInterpConfig::grid(GENERATION_AXES, &sol);
perf_interp::query(
&cfg,
node,
&[num_heads as f64, b as f64, sequence_tokens as f64],
)
}
pub fn context_points(
&self,
kernel_source: &str,
kv_quant: KvCacheQuantMode,
fmha_quant: FmhaQuantMode,
) -> Result<Vec<(Vec<f64>, f64)>, AicError> {
let grids = self.load_context()?;
let key = ContextKey {
kernel_source: kernel_source.to_string(),
fmha_quant: fmha_quant.name().to_string(),
kv_quant: kv_quant.name().to_string(),
};
let node = grids
.by_keys
.get(&key)
.ok_or_else(|| missing("WideEP context MLA", &self.data_root, format!("{key:?}")))?;
non_empty_points(node, "WideEP context MLA", &self.data_root)
}
pub fn generation_points(
&self,
kernel_source: &str,
kv_quant: KvCacheQuantMode,
) -> Result<Vec<(Vec<f64>, f64)>, AicError> {
let grids = self.load_generation()?;
let key = GenerationKey {
kernel_source: kernel_source.to_string(),
kv_quant: kv_quant.name().to_string(),
};
let node = grids
.by_keys
.get(&key)
.ok_or_else(|| missing("WideEP generation MLA", &self.data_root, format!("{key:?}")))?;
non_empty_points(node, "WideEP generation MLA", &self.data_root)
}
pub fn ensure_context_loaded(&self) -> Result<(), AicError> {
self.load_context().map(|_| ())
}
pub fn ensure_generation_loaded(&self) -> Result<(), AicError> {
self.load_generation().map(|_| ())
}
fn load_context(&self) -> Result<&WideEpContextMlaGrids, AicError> {
let cell = self
.context
.get_or_init(|| load_context_parquet(&self.context_sources));
cell.as_ref().map_err(clone_err)
}
fn load_generation(&self) -> Result<&WideEpGenerationMlaGrids, AicError> {
let cell = self
.generation
.get_or_init(|| load_generation_parquet(&self.generation_sources));
cell.as_ref().map_err(clone_err)
}
}
pub(crate) fn wideep_num_head(n: f64) -> f64 {
let tp_size = (128.0 / n).floor();
(128.0 / tp_size).floor()
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn wideep_context_mla_sol_ms(
spec: &SystemSpec,
fmha_quant: FmhaQuantMode,
num_head: f64,
s: f64,
prefix: f64,
b: f64,
main_flops: f64,
bf16_flops: f64,
) -> f64 {
let hidden_size = 7168.0_f64;
let q_lora_rank = 1536.0_f64;
let kv_lora_rank = 512.0_f64;
let qk_rope_head_dim = 64.0_f64;
let qk_nope_head_dim = 128.0_f64;
let v_head_dim = 128.0_f64;
let q_b_flop = 2.0 * q_lora_rank * num_head * (qk_rope_head_dim + qk_nope_head_dim) * b * s;
let q_b_mem = b * q_lora_rank * s
+ q_lora_rank * num_head * (qk_rope_head_dim + qk_nope_head_dim)
+ 2.0 * b * num_head * (qk_rope_head_dim + qk_nope_head_dim) * s;
let kv_b_flop = 2.0 * kv_lora_rank * num_head * (qk_nope_head_dim + v_head_dim) * b * s;
let kv_b_mem = b * s * kv_lora_rank
+ num_head * (qk_nope_head_dim + v_head_dim) * kv_lora_rank
+ 2.0 * b * num_head * (qk_nope_head_dim + v_head_dim) * s;
let full_s = s + prefix;
let attn_flop = (2.0
* num_head
* (qk_nope_head_dim * 2.0 + qk_rope_head_dim)
* b
* (full_s * full_s - prefix * prefix)
/ 2.0)
.floor();
let attn_mem = b * s * num_head * (qk_nope_head_dim + qk_rope_head_dim) + b * full_s * num_head * (qk_nope_head_dim + qk_rope_head_dim) + b * full_s * num_head * qk_nope_head_dim + b * s * num_head * qk_nope_head_dim;
let attn_out_flop = 2.0 * num_head * v_head_dim * hidden_size * b * s;
let attn_out_mem = b * num_head * v_head_dim * s
+ num_head * v_head_dim * hidden_size
+ 2.0 * b * hidden_size * s;
let ops = q_b_flop + kv_b_flop + attn_out_flop;
let mem_bytes =
(q_b_mem + kv_b_mem + attn_mem * 2.0 + attn_out_mem) * fmha_quant.mapping().memory;
let mut sol_math = ops / main_flops * 1000.0;
sol_math += attn_flop / bf16_flops * 1000.0;
let sol_mem = mem_bytes / spec.gpu.mem_bw * 1000.0;
sol_math.max(sol_mem)
}
pub(crate) fn wideep_generation_mla_sol_ms(
spec: &SystemSpec,
fmha_quant: FmhaQuantMode,
num_head: f64,
b: f64,
s: f64,
main_flops: f64,
bf16_flops: f64,
) -> f64 {
let hidden_size = 7168.0_f64;
let q_lora_rank = 1536.0_f64;
let kv_lora_rank = 512.0_f64;
let qk_rope_head_dim = 64.0_f64;
let qk_nope_head_dim = 128.0_f64;
let v_head_dim = 128.0_f64;
let q_b_flop = 2.0 * q_lora_rank * num_head * (qk_rope_head_dim + qk_nope_head_dim) * b;
let q_b_mem = b * q_lora_rank
+ q_lora_rank * num_head * (qk_rope_head_dim + qk_nope_head_dim)
+ 2.0 * b * num_head * (qk_rope_head_dim + qk_nope_head_dim);
let q_w_kc_flop = 2.0 * num_head * qk_nope_head_dim * kv_lora_rank * b;
let q_w_kc_mem = b * num_head * qk_nope_head_dim
+ num_head * kv_lora_rank * qk_nope_head_dim
+ 2.0 * b * num_head * kv_lora_rank;
let attn_flop = 2.0 * b * s * num_head * (qk_rope_head_dim + kv_lora_rank * 2.0);
let attn_mem = b * num_head * (kv_lora_rank + qk_rope_head_dim)
+ b * s * (qk_rope_head_dim + kv_lora_rank)
+ b * num_head * kv_lora_rank;
let s_w_vc_flop = 2.0 * b * num_head * kv_lora_rank * v_head_dim;
let s_w_vc_mem = b * num_head * kv_lora_rank
+ num_head * v_head_dim * kv_lora_rank
+ 2.0 * b * num_head * v_head_dim;
let attn_out_flop = 2.0 * num_head * v_head_dim * hidden_size * b;
let attn_out_mem =
b * num_head * v_head_dim + num_head * v_head_dim * hidden_size + 2.0 * b * hidden_size;
let ops = q_b_flop + q_w_kc_flop + s_w_vc_flop + attn_out_flop;
let mem_bytes = (q_b_mem + q_w_kc_mem + attn_mem * 2.0 + s_w_vc_mem + attn_out_mem)
* fmha_quant.mapping().memory;
let mut sol_math = ops / main_flops * 1000.0;
sol_math += attn_flop / bf16_flops * 1000.0;
let sol_mem = mem_bytes / spec.gpu.mem_bw * 1000.0;
sol_math.max(sol_mem)
}
fn grid3_to_node(grid: &Grid3<f64>) -> Node {
let mut node = Node::branch();
for (&a, by_b) in grid {
for (&b, by_c) in by_b {
for (&c, &lat) in by_c {
node.insert(&[a, b, c], lat);
}
}
}
node
}
fn load_context_parquet(sources: &[PerfSource]) -> Result<WideEpContextMlaGrids, AicError> {
let mut raw: BTreeMap<ContextKey, Grid3<f64>> = BTreeMap::new();
let mut any_source = false;
for source in sources {
let path = source.path();
if !path.exists() {
continue;
}
any_source = true;
let reader = PerfReader::open(path)?;
let kernel_source_col = reader.col("kernel_source")?;
let mla_dtype_col = reader.col("mla_dtype")?;
let kv_cache_dtype_col = reader.col("kv_cache_dtype")?;
let num_heads_col = reader.col("num_heads")?;
let batch_size_col = reader.col("batch_size")?;
let isl_col = reader.col("isl")?;
let latency_col = reader.col("latency")?;
let ks_col = reader.col_optional("kernel_source");
for row in reader.rows()? {
let row = row?;
if !kernel_source_ok(source.kernel_sources(), ks_col, &row)? {
continue;
}
let key = ContextKey {
kernel_source: row.str_owned(kernel_source_col)?,
fmha_quant: row.str_owned(mla_dtype_col)?,
kv_quant: row.str_owned(kv_cache_dtype_col)?,
};
raw.entry(key)
.or_default()
.entry(row.u32(num_heads_col)?)
.or_default()
.entry(row.u32(isl_col)?)
.or_default()
.entry(row.u32(batch_size_col)?)
.or_insert(row.f64(latency_col)?);
}
}
if !any_source || raw.is_empty() {
return Err(AicError::PerfDatabase(format!(
"no WideEP context MLA rows loaded from {} source(s) (first: {})",
sources.len(),
sources
.first()
.map(|s| s.path().display().to_string())
.unwrap_or_default()
)));
}
let by_keys = raw
.into_iter()
.map(|(key, grid)| (key, grid3_to_node(&grid)))
.collect();
Ok(WideEpContextMlaGrids { by_keys })
}
fn load_generation_parquet(sources: &[PerfSource]) -> Result<WideEpGenerationMlaGrids, AicError> {
let mut raw: BTreeMap<GenerationKey, Grid3<f64>> = BTreeMap::new();
let mut any_source = false;
for source in sources {
let path = source.path();
if !path.exists() {
continue;
}
any_source = true;
let reader = PerfReader::open(path)?;
let kernel_source_col = reader.col("kernel_source")?;
let kv_cache_dtype_col = reader.col("kv_cache_dtype")?;
let num_heads_col = reader.col("num_heads")?;
let batch_size_col = reader.col("batch_size")?;
let isl_col = reader.col("isl")?;
let step_col = reader.col("step")?;
let latency_col = reader.col("latency")?;
let ks_col = reader.col_optional("kernel_source");
for row in reader.rows()? {
let row = row?;
if !kernel_source_ok(source.kernel_sources(), ks_col, &row)? {
continue;
}
let key = GenerationKey {
kernel_source: row.str_owned(kernel_source_col)?,
kv_quant: row.str_owned(kv_cache_dtype_col)?,
};
let seq = row.u32(isl_col)? + row.u32(step_col)?;
raw.entry(key)
.or_default()
.entry(row.u32(num_heads_col)?)
.or_default()
.entry(row.u32(batch_size_col)?)
.or_default()
.entry(seq)
.or_insert(row.f64(latency_col)?);
}
}
if !any_source || raw.is_empty() {
return Err(AicError::PerfDatabase(format!(
"no WideEP generation MLA rows loaded from {} source(s) (first: {})",
sources.len(),
sources
.first()
.map(|s| s.path().display().to_string())
.unwrap_or_default()
)));
}
let by_keys = raw
.into_iter()
.map(|(key, grid)| (key, grid3_to_node(&grid)))
.collect();
Ok(WideEpGenerationMlaGrids { by_keys })
}
fn missing(table: &str, data_root: &Path, descriptor: String) -> AicError {
AicError::PerfDatabase(format!(
"{table} data missing for {descriptor} at {}",
data_root.display()
))
}
fn non_empty_points(
node: &Node,
table: &str,
data_root: &Path,
) -> Result<Vec<(Vec<f64>, f64)>, AicError> {
let points = perf_interp::node_points(node);
if points.is_empty() {
return Err(AicError::PerfDatabase(format!(
"{table} perf data empty for the requested slice at {}",
data_root.display()
)));
}
Ok(points)
}
fn clone_err(err: &AicError) -> AicError {
AicError::PerfDatabase(err.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
const REPO_ROOT_HINT: &str = env!("CARGO_MANIFEST_DIR");
fn b200_sglang_data_root() -> PathBuf {
PathBuf::from(REPO_ROOT_HINT)
.join("../..")
.join("python/aisimulate/src/aiconfigurator_core/systems/data/b200_sxm/sglang/0.5.10")
}
fn h200_sglang_data_root() -> PathBuf {
PathBuf::from(REPO_ROOT_HINT)
.join("../..")
.join("python/aisimulate/src/aiconfigurator_core/systems/data/h200_sxm/sglang/0.5.10")
}
fn load_spec(name: &str) -> SystemSpec {
let systems_yaml = PathBuf::from(REPO_ROOT_HINT).join("../..").join(format!(
"python/aisimulate/src/aiconfigurator_core/systems/{name}.yaml"
));
SystemSpec::load(&systems_yaml).unwrap_or_else(|_| panic!("{name}.yaml must parse"))
}
#[test]
fn wideep_context_mla_exact_hit() {
let table = WideEpMlaTable::new(b200_sglang_data_root(), load_spec("b200_sxm"));
let latency = table
.query_context(
1,
1,
128,
KvCacheQuantMode::Fp8,
FmhaQuantMode::Fp8Block,
"trtllm_mla",
)
.expect("WideEP context MLA query must succeed");
assert!(
(latency - 0.5470).abs() < 1e-3,
"expected recorded latency, got {latency}"
);
}
#[test]
fn wideep_generation_mla_exact_hit() {
let table = WideEpMlaTable::new(b200_sglang_data_root(), load_spec("b200_sxm"));
let latency = table
.query_generation(1, 1, 128, KvCacheQuantMode::Fp8, "trtllm_mla")
.expect("WideEP generation MLA query must succeed");
assert!(
(latency - 0.1049).abs() < 1e-3,
"expected recorded latency, got {latency}"
);
}
#[test]
fn wideep_mla_queries_match_python_v2_engine() {
let table = WideEpMlaTable::new(h200_sglang_data_root(), load_spec("h200_sxm"));
let assert_rel = |got: f64, expected: f64, what: &str| {
assert!(
((got - expected) / expected).abs() < 1e-9,
"{what}: rust {got} vs python {expected}"
);
};
let ctx_cases: &[(u32, u32, f64)] = &[
(4, 4096, 9.6274), (4, 6000, 16.671686220608603), (4, 50000, 697.4521946410698), ];
for &(b, s, expected) in ctx_cases {
let got = table
.query_context(
b,
s,
128,
KvCacheQuantMode::Fp8,
FmhaQuantMode::Fp8Block,
"flashinfer",
)
.unwrap();
assert_rel(got, expected, &format!("wideep_context_mla(b={b}, s={s})"));
}
let gen_cases: &[(u32, u32, f64)] = &[
(1, 4096, 0.1017), (1, 3000, 0.09988046874999999), (1, 100000, 0.18319659221424073), ];
for &(b, s, expected) in gen_cases {
let got = table
.query_generation(b, s, 128, KvCacheQuantMode::Fp8, "flashinfer")
.unwrap();
assert_rel(
got,
expected,
&format!("wideep_generation_mla(b={b}, s={s})"),
);
}
}
}