use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use super::attention::generation_attn_flops;
use super::gemm::quant_tc_flops;
use super::interpolation::Grid3;
use super::perf_interp::{self, LeafValue, Node, OpInterpConfig};
use super::{SourceResolver, kernel_source_ok};
use crate::common::enums::{FmhaQuantMode, GemmQuantMode, KvCacheQuantMode};
use crate::common::error::AicError;
use crate::common::system_spec::SystemSpec;
use crate::config::{PerfDbSources, PerfSource};
use crate::operators::base::SolComponents;
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"];
const BMM_AXES: &[&str] = &["num_tokens"];
pub struct MlaTable {
data_root: PathBuf,
system_spec: SystemSpec,
context_mla_sources: Vec<PerfSource>,
generation_mla_sources: Vec<PerfSource>,
mla_bmm_sources: Vec<PerfSource>,
mla_context_module_sources: Vec<PerfSource>,
mla_generation_module_sources: Vec<PerfSource>,
context: OnceLock<Result<ContextMlaGrids, AicError>>,
generation: OnceLock<Result<GenerationMlaGrids, AicError>>,
bmm: OnceLock<Result<BmmGrids, AicError>>,
context_module: OnceLock<Result<ModuleGrids, AicError>>,
generation_module: OnceLock<Result<GenModuleGrids, AicError>>,
}
struct ContextMlaGrids {
by_keys: BTreeMap<ContextKey, Node>,
}
struct GenerationMlaGrids {
by_keys: BTreeMap<KvOnlyKey, Node>,
}
struct ModuleGrids {
by_keys: BTreeMap<ModuleKey, BTreeMap<u32, Node>>,
}
struct GenModuleGrids {
by_keys: BTreeMap<GenModuleKey, BTreeMap<u32, Node>>,
}
pub(crate) fn mla_module_native_heads(model: &str) -> Option<u32> {
match model {
"deepseek-ai/DeepSeek-V3" => Some(128),
"deepseek-ai/DeepSeek-R1" => Some(128),
"nvidia/DeepSeek-V3.1-NVFP4" => Some(128),
_ => None,
}
}
fn resolve_module_native<T>(buckets: &BTreeMap<u32, T>, native_heads: Option<u32>) -> Option<&T> {
if buckets.is_empty() {
return None;
}
let native = match native_heads {
None => {
return if buckets.len() == 1 {
buckets.values().next()
} else {
None
};
}
Some(native) => native,
};
if let Some(node) = buckets.get(&native) {
return Some(node);
}
if buckets.len() == 1 {
return buckets.values().next();
}
buckets
.range(..=native)
.next_back()
.map(|(_, v)| v)
.or_else(|| buckets.values().next())
}
struct BmmGrids {
by_keys: BTreeMap<BmmKey, BTreeMap<u32, Node>>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct ContextKey {
fmha_quant: String,
kv_quant: String,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct KvOnlyKey {
kv_quant: String,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct ModuleKey {
fmha_quant: String,
kv_quant: String,
gemm_quant: String,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct GenModuleKey {
kv_quant: String,
gemm_quant: String,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct BmmKey {
bmm_quant: String,
pre_or_post: String,
}
impl MlaTable {
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_mla_sources = resolver.sources_for("context_mla_perf.parquet", &data_root)?;
let generation_mla_sources =
resolver.sources_for("generation_mla_perf.parquet", &data_root)?;
let mla_bmm_sources = resolver.sources_for("mla_bmm_perf.parquet", &data_root)?;
let mla_context_module_sources =
resolver.sources_for("mla_context_module_perf.parquet", &data_root)?;
let mla_generation_module_sources =
resolver.sources_for("mla_generation_module_perf.parquet", &data_root)?;
Ok(Self {
data_root,
system_spec,
context_mla_sources,
generation_mla_sources,
mla_bmm_sources,
mla_context_module_sources,
mla_generation_module_sources,
context: OnceLock::new(),
generation: OnceLock::new(),
bmm: OnceLock::new(),
context_module: OnceLock::new(),
generation_module: OnceLock::new(),
})
}
pub fn query_context(
&self,
b: u32,
full_seq_tokens: u32,
num_heads: u32,
kv_quant: KvCacheQuantMode,
fmha_quant: FmhaQuantMode,
) -> Result<LeafValue, AicError> {
let attn_flops = quant_tc_flops(&self.system_spec, fmha_quant.mapping())?;
let grids = self.load_context()?;
let key = ContextKey {
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("context MLA", &self.data_root, format!("{key:?}")))?;
let spec = &self.system_spec;
let sol = move |c: &[f64]| context_mla_sol_ms(spec, kv_quant, c[0], c[1], c[2], attn_flops);
let cfg = OpInterpConfig::grid_sqrt_axis(CONTEXT_AXES, 1, &sol);
perf_interp::query_value(
&cfg,
node,
&[num_heads as f64, full_seq_tokens as f64, b as f64],
)
}
pub fn query_generation(
&self,
b: u32,
s: u32,
num_heads: u32,
kv_quant: KvCacheQuantMode,
) -> Result<LeafValue, AicError> {
let attn_flops = generation_attn_flops(&self.system_spec, kv_quant)?;
let grids = self.load_generation()?;
let key = KvOnlyKey {
kv_quant: kv_quant.name().to_string(),
};
let node = grids
.by_keys
.get(&key)
.ok_or_else(|| missing("generation MLA", &self.data_root, format!("{key:?}")))?;
let spec = &self.system_spec;
let sol =
move |c: &[f64]| generation_mla_sol_ms(spec, kv_quant, c[0], c[1], c[2], attn_flops);
let cfg = OpInterpConfig::grid(GENERATION_AXES, &sol);
perf_interp::query_value(&cfg, node, &[num_heads as f64, b as f64, s as f64])
}
pub fn query_bmm(
&self,
num_tokens: u32,
num_heads: u32,
quant: GemmQuantMode,
is_pre: bool,
) -> Result<LeafValue, AicError> {
let bmm_flops = quant_tc_flops(&self.system_spec, quant.mapping())?;
let grids = self.load_bmm()?;
let pre_or_post = if is_pre {
"mla_gen_pre"
} else {
"mla_gen_post"
};
let key = BmmKey {
bmm_quant: quant.name().to_string(),
pre_or_post: pre_or_post.to_string(),
};
let chosen = grids.by_keys.get(&key).or_else(|| {
let fallback = BmmKey {
bmm_quant: GemmQuantMode::Bfloat16.name().to_string(),
pre_or_post: pre_or_post.to_string(),
};
grids.by_keys.get(&fallback)
});
let by_heads = chosen.ok_or_else(|| {
missing(
"MLA BMM",
&self.data_root,
format!("quant={}, {pre_or_post}", quant.name()),
)
})?;
let node = by_heads.get(&num_heads).ok_or_else(|| {
AicError::PerfDatabase(format!(
"MLA BMM data missing for num_heads={num_heads} at {}",
self.data_root.display()
))
})?;
let spec = &self.system_spec;
let sol = move |c: &[f64]| mla_bmm_sol_ms(spec, quant, num_heads as f64, c[0], bmm_flops);
let cfg = OpInterpConfig::grid(BMM_AXES, &sol);
perf_interp::query_value(&cfg, node, &[num_tokens as f64])
}
pub fn query_context_module(
&self,
b: u32,
full_seq_tokens: u32,
num_heads: u32,
kv_quant: KvCacheQuantMode,
fmha_quant: FmhaQuantMode,
gemm_quant: GemmQuantMode,
native_heads: Option<u32>,
) -> Result<LeafValue, AicError> {
let attn_flops = quant_tc_flops(&self.system_spec, fmha_quant.mapping())?;
let grids = self.load_context_module()?;
let key = ModuleKey {
fmha_quant: fmha_quant.name().to_string(),
kv_quant: kv_quant.name().to_string(),
gemm_quant: gemm_quant.name().to_string(),
};
let buckets = grids
.by_keys
.get(&key)
.ok_or_else(|| missing("context MLA module", &self.data_root, format!("{key:?}")))?;
let node = resolve_module_native(buckets, native_heads).ok_or_else(|| {
missing(
"context MLA module",
&self.data_root,
format!("{key:?} native_heads={native_heads:?}"),
)
})?;
let spec = &self.system_spec;
let sol = move |c: &[f64]| context_mla_sol_ms(spec, kv_quant, c[0], c[1], c[2], attn_flops);
let cfg = OpInterpConfig::grid_sqrt_axis(CONTEXT_AXES, 1, &sol);
perf_interp::query_value(
&cfg,
node,
&[num_heads as f64, full_seq_tokens as f64, b as f64],
)
}
pub fn query_generation_module(
&self,
b: u32,
s: u32,
num_heads: u32,
kv_quant: KvCacheQuantMode,
gemm_quant: GemmQuantMode,
native_heads: Option<u32>,
) -> Result<LeafValue, AicError> {
let attn_flops = generation_attn_flops(&self.system_spec, kv_quant)?;
let bmm_flops = quant_tc_flops(&self.system_spec, gemm_quant.mapping())?;
let grids = self.load_generation_module()?;
let key = GenModuleKey {
kv_quant: kv_quant.name().to_string(),
gemm_quant: gemm_quant.name().to_string(),
};
let buckets = grids
.by_keys
.get(&key)
.ok_or_else(|| missing("generation MLA module", &self.data_root, format!("{key:?}")))?;
let node = resolve_module_native(buckets, native_heads).ok_or_else(|| {
missing(
"generation MLA module",
&self.data_root,
format!("{key:?} native_heads={native_heads:?}"),
)
})?;
let spec = &self.system_spec;
let sol = move |c: &[f64]| {
generation_mla_module_sol_ms(
spec, kv_quant, gemm_quant, c[0], c[1], c[2], attn_flops, bmm_flops,
)
};
let cfg = OpInterpConfig::grid(GENERATION_AXES, &sol);
perf_interp::query_value(&cfg, node, &[num_heads as f64, b as f64, s as f64])
}
pub fn context_points(
&self,
kv_quant: KvCacheQuantMode,
fmha_quant: FmhaQuantMode,
) -> Result<Vec<(Vec<f64>, f64)>, AicError> {
let grids = self.load_context()?;
let key = ContextKey {
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("context MLA", &self.data_root, format!("{key:?}")))?;
non_empty_points(node, "context MLA", &self.data_root)
}
pub fn generation_points(
&self,
kv_quant: KvCacheQuantMode,
) -> Result<Vec<(Vec<f64>, f64)>, AicError> {
let grids = self.load_generation()?;
let key = KvOnlyKey {
kv_quant: kv_quant.name().to_string(),
};
let node = grids
.by_keys
.get(&key)
.ok_or_else(|| missing("generation MLA", &self.data_root, format!("{key:?}")))?;
non_empty_points(node, "generation MLA", &self.data_root)
}
pub fn bmm_selected_quant(&self, quant: GemmQuantMode) -> Result<GemmQuantMode, AicError> {
let grids = self.load_bmm()?;
let has_quant = grids
.by_keys
.keys()
.any(|key| key.bmm_quant == quant.name());
Ok(if has_quant {
quant
} else {
GemmQuantMode::Bfloat16
})
}
pub fn bmm_points(
&self,
quant: GemmQuantMode,
is_pre: bool,
num_heads: u32,
) -> Result<Vec<(Vec<f64>, f64)>, AicError> {
let grids = self.load_bmm()?;
let pre_or_post = if is_pre {
"mla_gen_pre"
} else {
"mla_gen_post"
};
let key = BmmKey {
bmm_quant: quant.name().to_string(),
pre_or_post: pre_or_post.to_string(),
};
let node = grids
.by_keys
.get(&key)
.and_then(|by_heads| by_heads.get(&num_heads))
.ok_or_else(|| {
missing(
"MLA BMM",
&self.data_root,
format!(
"quant={}, {pre_or_post}, num_heads={num_heads}",
quant.name()
),
)
})?;
non_empty_points(node, "MLA BMM", &self.data_root)
}
pub fn bmm_has_heads(
&self,
quant: GemmQuantMode,
is_pre: bool,
num_heads: u32,
) -> Result<bool, AicError> {
let grids = self.load_bmm()?;
let key = BmmKey {
bmm_quant: quant.name().to_string(),
pre_or_post: if is_pre {
"mla_gen_pre"
} else {
"mla_gen_post"
}
.to_string(),
};
Ok(grids
.by_keys
.get(&key)
.is_some_and(|by_heads| by_heads.contains_key(&num_heads)))
}
pub fn context_module_points(
&self,
kv_quant: KvCacheQuantMode,
fmha_quant: FmhaQuantMode,
gemm_quant: GemmQuantMode,
native_heads: Option<u32>,
) -> Result<Vec<(Vec<f64>, f64)>, AicError> {
let grids = self.load_context_module()?;
let key = ModuleKey {
fmha_quant: fmha_quant.name().to_string(),
kv_quant: kv_quant.name().to_string(),
gemm_quant: gemm_quant.name().to_string(),
};
let buckets = grids
.by_keys
.get(&key)
.ok_or_else(|| missing("context MLA module", &self.data_root, format!("{key:?}")))?;
let node = resolve_module_native(buckets, native_heads).ok_or_else(|| {
missing(
"context MLA module",
&self.data_root,
format!("{key:?} native_heads={native_heads:?}"),
)
})?;
non_empty_points(node, "context MLA module", &self.data_root)
}
pub fn generation_module_points(
&self,
kv_quant: KvCacheQuantMode,
gemm_quant: GemmQuantMode,
native_heads: Option<u32>,
) -> Result<Vec<(Vec<f64>, f64)>, AicError> {
let grids = self.load_generation_module()?;
let key = GenModuleKey {
kv_quant: kv_quant.name().to_string(),
gemm_quant: gemm_quant.name().to_string(),
};
let buckets = grids
.by_keys
.get(&key)
.ok_or_else(|| missing("generation MLA module", &self.data_root, format!("{key:?}")))?;
let node = resolve_module_native(buckets, native_heads).ok_or_else(|| {
missing(
"generation MLA module",
&self.data_root,
format!("{key:?} native_heads={native_heads:?}"),
)
})?;
non_empty_points(node, "generation MLA module", &self.data_root)
}
fn load_context(&self) -> Result<&ContextMlaGrids, AicError> {
let cell = self
.context
.get_or_init(|| load_op_parquet(&self.context_mla_sources, true));
cell.as_ref().map_err(clone_err)
}
fn load_generation(&self) -> Result<&GenerationMlaGrids, AicError> {
let cell = self
.generation
.get_or_init(|| load_op_gen_parquet(&self.generation_mla_sources));
cell.as_ref().map_err(clone_err)
}
fn load_bmm(&self) -> Result<&BmmGrids, AicError> {
let cell = self
.bmm
.get_or_init(|| load_bmm_parquet(&self.mla_bmm_sources));
cell.as_ref().map_err(clone_err)
}
fn load_context_module(&self) -> Result<&ModuleGrids, AicError> {
let cell = self
.context_module
.get_or_init(|| load_context_module_parquet(&self.mla_context_module_sources));
cell.as_ref().map_err(clone_err)
}
fn load_generation_module(&self) -> Result<&GenModuleGrids, AicError> {
let cell = self
.generation_module
.get_or_init(|| load_generation_module_parquet(&self.mla_generation_module_sources));
cell.as_ref().map_err(clone_err)
}
}
pub(crate) fn context_mla_sol_ms(
spec: &SystemSpec,
kv_quant: KvCacheQuantMode,
n: f64,
s: f64,
b: f64,
attn_flops: f64,
) -> f64 {
context_mla_sol_prefix_ms(spec, kv_quant, n, s, 0.0, b, attn_flops)
}
pub(crate) fn context_mla_sol_prefix(
spec: &SystemSpec,
kv_quant: KvCacheQuantMode,
n: f64,
s: f64,
prefix: f64,
b: f64,
attn_flops: f64,
) -> SolComponents {
let full_s = s + prefix;
let ops = b * n * 2.0 / 2.0 * (192.0 + 128.0) * (full_s * full_s - prefix * prefix);
let mem_bytes =
b * n * (kv_quant.mapping().memory * full_s * (192.0 + 128.0) + 2.0 * s * (192.0 + 128.0));
let sol_math = ops / attn_flops * 1000.0;
let sol_mem = mem_bytes / spec.gpu.mem_bw * 1000.0;
SolComponents::new(sol_math, sol_mem)
}
pub(crate) fn context_mla_sol_prefix_ms(
spec: &SystemSpec,
kv_quant: KvCacheQuantMode,
n: f64,
s: f64,
prefix: f64,
b: f64,
attn_flops: f64,
) -> f64 {
context_mla_sol_prefix(spec, kv_quant, n, s, prefix, b, attn_flops).time_ms()
}
pub(crate) fn generation_mla_sol(
spec: &SystemSpec,
kv_quant: KvCacheQuantMode,
n: f64,
b: f64,
s: f64,
attn_flops: f64,
) -> SolComponents {
let ops = 2.0 * b * n * 1088.0 * s;
let mem_bytes = b * (n * 1088.0 * 2.0 + (s - 1.0) * 576.0 * kv_quant.mapping().memory);
let sol_math = ops / attn_flops * 1000.0;
let sol_mem = mem_bytes / spec.gpu.mem_bw * 1000.0;
SolComponents::new(sol_math, sol_mem)
}
pub(crate) fn generation_mla_sol_ms(
spec: &SystemSpec,
kv_quant: KvCacheQuantMode,
n: f64,
b: f64,
s: f64,
attn_flops: f64,
) -> f64 {
generation_mla_sol(spec, kv_quant, n, b, s, attn_flops).time_ms()
}
pub(crate) fn mla_bmm_sol(
spec: &SystemSpec,
quant: GemmQuantMode,
n: f64,
t: f64,
bmm_flops: f64,
) -> SolComponents {
let ops = 2.0 * t * n * 128.0 * 512.0;
let mem_bytes = n * (t * 640.0 + 128.0 * 512.0) * quant.mapping().memory;
let sol_math = ops / bmm_flops * 1000.0;
let sol_mem = mem_bytes / spec.gpu.mem_bw * 1000.0;
SolComponents::new(sol_math, sol_mem)
}
pub(crate) fn mla_bmm_sol_ms(
spec: &SystemSpec,
quant: GemmQuantMode,
n: f64,
t: f64,
bmm_flops: f64,
) -> f64 {
mla_bmm_sol(spec, quant, n, t, bmm_flops).time_ms()
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn generation_mla_module_sol(
spec: &SystemSpec,
kv_quant: KvCacheQuantMode,
gemm_quant: GemmQuantMode,
n: f64,
b: f64,
s: f64,
attn_flops: f64,
bmm_flops: f64,
) -> SolComponents {
let attn_ops = 2.0 * b * n * 1088.0 * s;
let mem_bytes = b * (n * 1088.0 * 2.0 + (s - 1.0) * 576.0 * kv_quant.mapping().memory);
let mut sol_math = attn_ops / attn_flops * 1000.0;
let mut sol_mem = mem_bytes / spec.gpu.mem_bw * 1000.0;
let bmm_ops = 2.0 * 2.0 * b * n * 128.0 * 512.0; let bmm_mem = 2.0 * n * (b * 640.0 + 128.0 * 512.0) * gemm_quant.mapping().memory;
let bmm_math = bmm_ops / bmm_flops * 1000.0;
let bmm_mem_time = bmm_mem / spec.gpu.mem_bw * 1000.0;
sol_math += bmm_math;
sol_mem += bmm_mem_time;
SolComponents::new(sol_math, sol_mem)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn generation_mla_module_sol_ms(
spec: &SystemSpec,
kv_quant: KvCacheQuantMode,
gemm_quant: GemmQuantMode,
n: f64,
b: f64,
s: f64,
attn_flops: f64,
bmm_flops: f64,
) -> f64 {
generation_mla_module_sol(spec, kv_quant, gemm_quant, n, b, s, attn_flops, bmm_flops).time_ms()
}
fn grid3_to_node(grid: &Grid3<LeafValue>) -> Node {
let mut node = Node::branch();
for (&a, by_b) in grid {
for (&b, by_c) in by_b {
for (&c, &leaf) in by_c {
node.insert_value(&[a, b, c], leaf);
}
}
}
node
}
fn curve_to_node(curve: &BTreeMap<u32, LeafValue>) -> Node {
let mut node = Node::branch();
for (&t, &leaf) in curve {
node.insert_value(&[t], leaf);
}
node
}
fn load_op_parquet(sources: &[PerfSource], is_context: bool) -> Result<ContextMlaGrids, AicError> {
let mut raw: BTreeMap<ContextKey, Grid3<LeafValue>> = 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 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 step_col = reader.col("step")?;
let latency_col = reader.col("latency")?;
let power_col = reader.col_optional("power");
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 {
fmha_quant: row.str_owned(mla_dtype_col)?,
kv_quant: row.str_owned(kv_cache_dtype_col)?,
};
let isl = row.u32(isl_col)?;
let y_axis = if is_context {
isl
} else {
isl + row.u32(step_col)?
};
let latency = row.f64(latency_col)?;
let power = row.f64_optional(power_col)?.unwrap_or(0.0);
raw.entry(key)
.or_default()
.entry(row.u32(num_heads_col)?)
.or_default()
.entry(y_axis)
.or_default()
.entry(row.u32(batch_size_col)?)
.or_insert(LeafValue::with_power(latency, power));
}
}
if !any_source || raw.is_empty() {
return Err(AicError::PerfDatabase(format!(
"no MLA op 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(ContextMlaGrids { by_keys })
}
fn load_op_gen_parquet(sources: &[PerfSource]) -> Result<GenerationMlaGrids, AicError> {
let mut raw: BTreeMap<KvOnlyKey, Grid3<LeafValue>> = 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 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 power_col = reader.col_optional("power");
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 = KvOnlyKey {
kv_quant: row.str_owned(kv_cache_dtype_col)?,
};
let sequence_tokens = row.u32(isl_col)? + row.u32(step_col)?;
let latency = row.f64(latency_col)?;
let power = row.f64_optional(power_col)?.unwrap_or(0.0);
raw.entry(key)
.or_default()
.entry(row.u32(num_heads_col)?)
.or_default()
.entry(row.u32(batch_size_col)?)
.or_default()
.entry(sequence_tokens)
.or_insert(LeafValue::with_power(latency, power));
}
}
if !any_source || raw.is_empty() {
return Err(AicError::PerfDatabase(format!(
"no 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(GenerationMlaGrids { by_keys })
}
fn module_row_native_heads(
model: &str,
num_heads: u32,
tp_size: u32,
path: &std::path::Path,
) -> Result<u32, AicError> {
if model.is_empty() {
return Err(AicError::PerfDatabase(format!(
"MLA module row in {} carries no model value; the module table keys its \
native-head identity off the model pin (#1458)",
path.display()
)));
}
let native_heads = mla_module_native_heads(model).ok_or_else(|| {
AicError::PerfDatabase(format!(
"MLA module row in {} names unpinned model {model:?}; add its native head \
count to the module native-head pin when landing the data (#1458)",
path.display()
))
})?;
if tp_size > 1 && num_heads * tp_size != native_heads {
return Err(AicError::PerfDatabase(format!(
"MLA module row in {} for model {model:?} has num_heads={num_heads} at \
tp_size={tp_size}, inconsistent with native {native_heads} (num_heads must \
be rank-local, #1429/#1458)",
path.display()
)));
}
Ok(native_heads)
}
fn load_context_module_parquet(sources: &[PerfSource]) -> Result<ModuleGrids, AicError> {
let mut raw: BTreeMap<ModuleKey, BTreeMap<u32, Grid3<LeafValue>>> = 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 mla_dtype_col = reader.col("mla_dtype")?;
let kv_cache_dtype_col = reader.col("kv_cache_dtype")?;
let gemm_type_col = reader.col("gemm_type")?;
let model_col = reader.col("model")?;
let num_heads_col = reader.col("num_heads")?;
let tp_size_col = reader.col_optional("tp_size");
let batch_size_col = reader.col("batch_size")?;
let isl_col = reader.col("isl")?;
let latency_col = reader.col("latency")?;
let power_col = reader.col_optional("power");
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 = ModuleKey {
fmha_quant: row.str_owned(mla_dtype_col)?,
kv_quant: row.str_owned(kv_cache_dtype_col)?,
gemm_quant: row.str_owned(gemm_type_col)?,
};
let num_heads = row.u32(num_heads_col)?;
let tp_size = match tp_size_col {
Some(col) => row.u32(col)?.max(1),
None => 1,
};
let model = row.str_owned(model_col)?;
let native_heads = module_row_native_heads(&model, num_heads, tp_size, path)?;
let batch_size = row.u32(batch_size_col)?;
let isl = row.u32(isl_col)?;
let latency = row.f64(latency_col)?;
let power = row.f64_optional(power_col)?.unwrap_or(0.0);
let inner = raw
.entry(key)
.or_default()
.entry(native_heads)
.or_default()
.entry(num_heads)
.or_default()
.entry(isl)
.or_default();
inner
.entry(batch_size)
.or_insert(LeafValue::with_power(latency, power));
}
}
if !any_source || raw.is_empty() {
return Err(AicError::PerfDatabase(format!(
"no MLA module 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, by_native)| {
(
key,
by_native
.into_iter()
.map(|(native, grid)| (native, grid3_to_node(&grid)))
.collect(),
)
})
.collect();
Ok(ModuleGrids { by_keys })
}
fn load_generation_module_parquet(sources: &[PerfSource]) -> Result<GenModuleGrids, AicError> {
let mut raw: BTreeMap<GenModuleKey, BTreeMap<u32, Grid3<LeafValue>>> = 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 kv_cache_dtype_col = reader.col("kv_cache_dtype")?;
let gemm_type_col = reader.col("gemm_type")?;
let model_col = reader.col("model")?;
let num_heads_col = reader.col("num_heads")?;
let tp_size_col = reader.col_optional("tp_size");
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 power_col = reader.col_optional("power");
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 = GenModuleKey {
kv_quant: row.str_owned(kv_cache_dtype_col)?,
gemm_quant: row.str_owned(gemm_type_col)?,
};
let num_heads = row.u32(num_heads_col)?;
let tp_size = match tp_size_col {
Some(col) => row.u32(col)?.max(1),
None => 1,
};
let model = row.str_owned(model_col)?;
let native_heads = module_row_native_heads(&model, num_heads, tp_size, path)?;
let batch_size = row.u32(batch_size_col)?;
let isl = row.u32(isl_col)?;
let latency = row.f64(latency_col)?;
let power = row.f64_optional(power_col)?.unwrap_or(0.0);
let sequence_tokens = isl + row.u32(step_col)?;
let inner = raw
.entry(key)
.or_default()
.entry(native_heads)
.or_default()
.entry(num_heads)
.or_default()
.entry(batch_size)
.or_default();
inner
.entry(sequence_tokens)
.or_insert(LeafValue::with_power(latency, power));
}
}
if !any_source || raw.is_empty() {
return Err(AicError::PerfDatabase(format!(
"no MLA module 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, by_native)| {
(
key,
by_native
.into_iter()
.map(|(native, grid)| (native, grid3_to_node(&grid)))
.collect(),
)
})
.collect();
Ok(GenModuleGrids { by_keys })
}
fn load_bmm_parquet(sources: &[PerfSource]) -> Result<BmmGrids, AicError> {
let mut raw: BTreeMap<BmmKey, BTreeMap<u32, BTreeMap<u32, LeafValue>>> = 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 op_name_col = reader.col("op_name")?;
let bmm_dtype_col = reader.col("bmm_dtype")?;
let num_tokens_col = reader.col("num_tokens")?;
let num_heads_col = reader.col("num_heads")?;
let latency_col = reader.col("latency")?;
let power_col = reader.col_optional("power");
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 = BmmKey {
bmm_quant: row.str_owned(bmm_dtype_col)?,
pre_or_post: row.str_owned(op_name_col)?,
};
let latency = row.f64(latency_col)?;
let power = row.f64_optional(power_col)?.unwrap_or(0.0);
raw.entry(key)
.or_default()
.entry(row.u32(num_heads_col)?)
.or_default()
.entry(row.u32(num_tokens_col)?)
.or_insert(LeafValue::with_power(latency, power));
}
}
if !any_source || raw.is_empty() {
return Err(AicError::PerfDatabase(format!(
"no MLA BMM 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, by_heads)| {
let converted = by_heads
.into_iter()
.map(|(heads, curve)| (heads, curve_to_node(&curve)))
.collect();
(key, converted)
})
.collect();
Ok(BmmGrids { 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");
#[test]
fn resolve_module_native_ladder() {
let mut two: BTreeMap<u32, &str> = BTreeMap::new();
two.insert(64, "a");
two.insert(128, "b");
let mut one: BTreeMap<u32, &str> = BTreeMap::new();
one.insert(128, "b");
let empty: BTreeMap<u32, &str> = BTreeMap::new();
assert_eq!(resolve_module_native(&two, Some(128)), Some(&"b")); assert_eq!(resolve_module_native(&two, Some(96)), Some(&"a")); assert_eq!(resolve_module_native(&two, Some(32)), Some(&"a")); assert_eq!(resolve_module_native(&one, Some(64)), Some(&"b")); assert_eq!(resolve_module_native(&one, None), Some(&"b")); assert_eq!(resolve_module_native(&two, None), None); assert_eq!(resolve_module_native(&empty, Some(128)), None);
}
#[test]
fn module_row_native_heads_pins_and_guards() {
let path = Path::new("test.parquet");
assert_eq!(
module_row_native_heads("deepseek-ai/DeepSeek-V3", 16, 1, path).unwrap(),
128
);
assert_eq!(
module_row_native_heads("deepseek-ai/DeepSeek-V3", 64, 2, path).unwrap(),
128
);
let unpinned = module_row_native_heads("unknown/NewModel", 16, 1, path);
assert!(
matches!(&unpinned, Err(AicError::PerfDatabase(msg)) if msg.contains("unpinned model")),
"got {unpinned:?}"
);
let stale = module_row_native_heads("deepseek-ai/DeepSeek-V3", 128, 2, path);
assert!(
matches!(&stale, Err(AicError::PerfDatabase(msg)) if msg.contains("rank-local")),
"got {stale:?}"
);
let empty = module_row_native_heads("", 16, 1, path);
assert!(
matches!(&empty, Err(AicError::PerfDatabase(msg)) if msg.contains("no model value")),
"got {empty:?}"
);
}
fn b200_vllm_data_root() -> PathBuf {
PathBuf::from(REPO_ROOT_HINT)
.join("../..")
.join("python/aisimulate/src/aiconfigurator_core/systems/data/b200_sxm/vllm/0.19.0")
}
fn gb200_trtllm_data_root() -> PathBuf {
PathBuf::from(REPO_ROOT_HINT)
.join("../..")
.join("python/aisimulate/src/aiconfigurator_core/systems/data/gb200/trtllm/1.3.0rc10")
}
fn h200_trtllm_data_root() -> PathBuf {
PathBuf::from(REPO_ROOT_HINT).join("../..").join(
"python/aisimulate/src/aiconfigurator_core/systems/data/h200_sxm/trtllm/1.3.0rc10",
)
}
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 op_level_context_mla_absent_on_vllm_b200() {
let table = MlaTable::new(b200_vllm_data_root(), load_spec("b200_sxm"));
let err = table
.query_context(
1,
1024,
128,
KvCacheQuantMode::Bfloat16,
FmhaQuantMode::Bfloat16,
)
.unwrap_err();
match err {
AicError::Io { .. } | AicError::PerfDatabase(_) => {}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn module_level_context_mla_exact_hit() {
let table = MlaTable::new(b200_vllm_data_root(), load_spec("b200_sxm"));
let latency = table
.query_context_module(
1,
1,
128,
KvCacheQuantMode::Bfloat16,
FmhaQuantMode::Bfloat16,
GemmQuantMode::Bfloat16,
None,
)
.expect("module context MLA query must succeed")
.latency;
assert!(
(latency - 0.1351).abs() < 1e-6,
"expected recorded module latency, got {latency}"
);
}
#[test]
fn module_level_generation_mla_smoke() {
let table = MlaTable::new(b200_vllm_data_root(), load_spec("b200_sxm"));
let result = table.query_generation_module(
1,
1024,
128,
KvCacheQuantMode::Bfloat16,
GemmQuantMode::Bfloat16,
None,
);
match result {
Ok(value) => assert!(value.latency > 0.0, "expected positive latency"),
Err(AicError::PerfDatabase(_)) => {
}
Err(other) => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn module_level_generation_mla_fp8_kv_anchor() {
let table = MlaTable::new(h200_trtllm_data_root(), load_spec("h200_sxm"));
let cases: &[(u32, u32, f64)] = &[(8, 4097, 0.0693), (64, 4096, 0.1146884765625)];
for &(b, s, expected) in cases {
let got = table
.query_generation_module(
b,
s,
16,
KvCacheQuantMode::Fp8,
GemmQuantMode::Fp8Block,
None,
)
.unwrap()
.latency;
let rel = ((got - expected) / expected.max(1e-12)).abs();
assert!(
rel < 1e-9,
"gen_mla_module_fp8kv(b={b}, s={s}): got {got:.16}, expected {expected:.16}"
);
}
}
#[test]
fn mla_bmm_falls_back_to_bfloat16() {
let table = MlaTable::new(gb200_trtllm_data_root(), load_spec("gb200"));
let result = table.query_bmm(64, 128, GemmQuantMode::Sq, true);
match result {
Ok(value) => assert!(value.latency.is_finite() && value.latency > 0.0),
Err(AicError::PerfDatabase(_)) => {}
Err(other) => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn mla_queries_match_python_v2_engine() {
let table = MlaTable::new(gb200_trtllm_data_root(), load_spec("gb200"));
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, 2.4523092905680337), (4, 5000, 3.551457374840901), (4, 100000, 1392.4843754587866), ];
for &(b, s, expected) in ctx_cases {
let got = table
.query_context(
b,
s,
128,
KvCacheQuantMode::Bfloat16,
FmhaQuantMode::Bfloat16,
)
.unwrap()
.latency;
assert_rel(got, expected, &format!("context_mla(b={b}, s={s})"));
}
let gen_cases: &[(u32, u32, f64)] = &[
(1, 4096, 0.02057066683967908), (1, 3000, 0.018758271161156394), (1, 500000, 0.19686579992539105), ];
for &(b, s, expected) in gen_cases {
let got = table
.query_generation(b, s, 128, KvCacheQuantMode::Bfloat16)
.unwrap()
.latency;
assert_rel(got, expected, &format!("generation_mla(b={b}, s={s})"));
}
let bmm_cases: &[(u32, f64)] = &[
(256, 0.010847999900579452), (100, 0.008607199974358081), (20000, 0.5326748099591996), ];
for &(t, expected) in bmm_cases {
let got = table
.query_bmm(t, 128, GemmQuantMode::Bfloat16, true)
.unwrap()
.latency;
assert_rel(got, expected, &format!("mla_bmm(t={t})"));
}
let got = table
.query_bmm(20000, 128, GemmQuantMode::Fp8, true)
.unwrap()
.latency;
assert_rel(got, 0.5326748099591996, "mla_bmm fp8 fallback (t=20000)");
let ctx_mod_cases: &[(u32, u32, f64)] = &[
(2, 4096, 2.6503), (2, 5000, 3.532393382077576), (2, 100000, 705.5935422351143), ];
for &(b, s, expected) in ctx_mod_cases {
let got = table
.query_context_module(
b,
s,
128,
KvCacheQuantMode::Bfloat16,
FmhaQuantMode::Bfloat16,
GemmQuantMode::Bfloat16,
None,
)
.unwrap()
.latency;
assert_rel(got, expected, &format!("context_mla_module(b={b}, s={s})"));
}
let gen_mod_cases: &[(u32, u32, f64)] = &[
(8, 4097, 0.0938), (8, 3000, 0.0918716796875), (8, 500000, 1.0705697352947636), ];
for &(b, s, expected) in gen_mod_cases {
let got = table
.query_generation_module(
b,
s,
128,
KvCacheQuantMode::Bfloat16,
GemmQuantMode::Bfloat16,
None,
)
.unwrap()
.latency;
assert_rel(
got,
expected,
&format!("generation_mla_module(b={b}, s={s})"),
);
}
}
#[test]
fn generation_mla_energy_matches_python_oracle() {
use crate::perf_database::energy_test_fixtures::{Col, energy_test_spec, write_parquet};
let tmp = tempfile::tempdir().expect("tmpdir");
write_parquet(
&tmp.path().join("generation_mla_perf.parquet"),
&[
Col::Str("kv_cache_dtype", vec!["bfloat16", "bfloat16"]),
Col::I64("num_heads", vec![128, 128]),
Col::I64("batch_size", vec![1, 1]),
Col::I64("isl", vec![1023, 2047]),
Col::I64("step", vec![1, 1]),
Col::F64("latency", vec![1.0, 3.0]),
Col::F64("power", vec![100.0, 200.0]),
],
);
let table = MlaTable::new(tmp.path().to_path_buf(), energy_test_spec());
let v = table
.query_generation(1, 1536, 128, KvCacheQuantMode::Bfloat16)
.unwrap();
assert!((v.latency - 2.0).abs() < 1e-9, "latency {}", v.latency);
assert!(
(v.energy - 300.0).abs() < 1e-9 * 300.0,
"energy {}",
v.energy
);
}
}