Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d66ea9a
feat: preserve native Hopper dispatch in DeepSeek V4.1 baselines
Harrilee Sep 21, 2026
f201a39
Add distinct native Humming W4A16 profile identity and qualification
Harrilee Sep 21, 2026
69b1ca9
Verify Humming profile selection through V4.1 prediction
Harrilee Sep 21, 2026
7e1f86a
Declare official Hopper SXM scalar FP32 throughput
Harrilee Sep 21, 2026
f9973d7
feat(collector): measure isolated native DeepSeek V4.1 operators
Harrilee Sep 22, 2026
906500a
feat: collect native DeepSeek V4.1 attention without whole-model resi…
Harrilee Sep 22, 2026
9706d22
fix(collector): bind isolated attention provenance and admission
Harrilee Sep 22, 2026
4ec97f3
fix: preserve runtime identity in collection history admission
Harrilee Sep 22, 2026
4f102fa
test: align public provenance rejection with supported runtime fields
Harrilee Sep 22, 2026
847819f
data(sglang): add DeepSeek V4.1 full profiles for four GPUs
Harrilee Sep 22, 2026
f46496d
fix(collector): register DeepSeek V4.1 measured backend facts
Harrilee Sep 22, 2026
8744b81
docs(collector): clarify DeepSeek V4.1 launch environment
Harrilee Sep 22, 2026
ce7f276
feat(collector): export admitted bounded attention alongside full rows
Harrilee Sep 22, 2026
e7fcf71
feat(collector): admit audited bounded attention owner means
Harrilee Sep 22, 2026
08a8e64
fix: model native Hopper V4.1 index scores with BF16 compute
Harrilee Sep 22, 2026
5cfe282
data: add bounded DeepSeek V4.1 attention profiles on four GPUs
Harrilee Sep 22, 2026
37c48c0
Merge branch 'main' into harrli/dsv41-h100-h200-b200-gb200-op
Harrilee Sep 28, 2026
56cb881
Merge branch 'main' into harrli/dsv41-h100-h200-b200-gb200-op
Harrilee Sep 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -492,15 +492,28 @@ SGLang's serving architecture at immutable commit
- `python/sglang/srt/layers/attention/deepseek_v4_backend.py`
- `python/sglang/srt/mem_cache/deepseek_v4_memory_pool.py`
- `python/sglang/kernels/ops/attention/dsv4_attn_metadata_kernels.py`
- `python/sglang/kernels/ops/attention/dsv4/sm90_fp4_indexer.py`
- `python/sglang/benchmark/one_batch.py`

The original integration adapter
`collector/sglang/dsv41_native_runner.py` calls that pinned benchmark's model
builder and request lifecycle. Its component boundaries are modified from
the serving contracts above; it does not copy framework metadata builders.
The original attention-only adapter `collector/sglang/dsv41_attention_runner.py`,
its `tests/unit/collector/test_dsv41_attention.py` fixtures and the attention
section of `collector/sglang/README.dsv41.md` also adapt those API boundaries
(modified), including native dummy-loader post-load order from
`python/sglang/srt/model_loader/loader.py` and random initialization from
`python/sglang/srt/model_loader/weight_utils.py` at the same immutable revision.
They call native attention, pool and request builders; they do not copy those
implementations or represent the isolated stack as the complete model.
The matching loaded-dimension guards in `collector/sglang/dsv41_contract.py`
and their CPU fixtures in `tests/unit/collector/test_dsv41_contract.py` are
modified analytical adaptations of the indexer layout in `dsv41_sparse.py`.
The SM90 index-score arithmetic and query-width formulas in
`crates/core/src/perfmodel/operators/dsv41.rs`, their Rust regression tests,
and `docs/deepseek-v41-storage.md` are independently expressed, modified
analytical adaptations of those same pinned BF16 indexer contracts.

The measured operator databases and adjacent documentation under
`src/aisimulate_core/systems/profiles/dsv41/` contain AISimulate timings
Expand Down
34 changes: 33 additions & 1 deletion crates/core/src/perfmodel/common/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,11 @@ pub enum MoeQuantMode {
/// `w4a16_mxfp4` (mirrors Python `load_moe_data`).
W4a16Mxfp4Cutlass,
/// Scale-aware NVFP4 weights dequantized into the BF16 MoE compute lane.
/// Kept last so existing serialized enum discriminants remain stable.
/// Append-only extension preserving older serialized discriminants.
W4a16Nvfp4,
/// Native Humming MXFP4 experts with unquantized BF16 activations.
/// Distinct from the Triton and FlashInfer CUTLASS W4A16 kernels.
W4a16Mxfp4Humming,
}

impl MoeQuantMode {
Expand Down Expand Up @@ -396,6 +399,12 @@ impl MoeQuantMode {
name: "w4a16_nvfp4",
compute_dtype: Some(ComputeDtype::Bfloat16),
},
Self::W4a16Mxfp4Humming => QuantMapping {
memory: 0.5,
compute: 1.0,
name: "w4a16_mxfp4_humming",
compute_dtype: Some(ComputeDtype::Bfloat16),
},
}
}

Expand Down Expand Up @@ -709,6 +718,29 @@ mod tests {
);
}

#[test]
fn humming_moe_wire_identity_is_additive() {
let mode: MoeQuantMode = serde_json::from_str("\"w4a16_mxfp4_humming\"").unwrap();
assert_eq!(mode, MoeQuantMode::W4a16Mxfp4Humming);
assert_eq!(
serde_json::to_string(&mode).unwrap(),
"\"w4a16_mxfp4_humming\""
);
// Historical positional discriminants must remain stable.
assert_eq!(
bincode::serialize(&MoeQuantMode::W4a16Mxfp4Cutlass).unwrap(),
10_u32.to_le_bytes()
);
assert_eq!(
bincode::serialize(&MoeQuantMode::W4a16Nvfp4).unwrap(),
11_u32.to_le_bytes()
);
let bytes = bincode::serialize(&mode).unwrap();
assert_eq!(bincode::deserialize::<MoeQuantMode>(&bytes).unwrap(), mode);
assert_eq!(mode.mapping().memory, 0.5);
assert_eq!(mode.mapping().compute_dtype, Some(ComputeDtype::Bfloat16));
}

#[test]
fn moe_quant_payloads_match_python_quant_mapping() {
assert_eq!(
Expand Down
2 changes: 2 additions & 0 deletions crates/core/src/perfmodel/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ pub enum DataType {
// Append-only wire extension: keep existing bincode discriminants stable.
#[serde(rename = "w4a16_nvfp4")]
W4a16Nvfp4,
#[serde(rename = "w4a16_mxfp4_humming")]
W4a16Mxfp4Humming,
}

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/perfmodel/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ fn dtype_str(dt: &crate::DataType) -> &'static str {
W4a8Mxfp4Mxfp8 => "w4a8_mxfp4_mxfp8",
W4a8Mxfp4Mxfp8Trtllm => "w4a8_mxfp4_mxfp8_trtllm",
W4a16Mxfp4Cutlass => "w4a16_mxfp4_cutlass",
W4a16Mxfp4Humming => "w4a16_mxfp4_humming",
W4a16Nvfp4 => "w4a16_nvfp4",
}
}
Expand Down
87 changes: 83 additions & 4 deletions crates/core/src/perfmodel/operators/dsv41.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
//! Serving layout/scoring source: sgl-project/sglang at
//! 1aa0e962b206102b7c439a4a0c4981cfec6e87bc,
//! python/sglang/srt/layers/attention/deepseek_v4_backend.py and
//! python/sglang/srt/mem_cache/deepseek_v4_memory_pool.py (Apache-2.0,
//! python/sglang/srt/mem_cache/deepseek_v4_memory_pool.py, plus
//! python/sglang/kernels/ops/attention/dsv4/sm90_fp4_indexer.py (Apache-2.0,
//! Copyright SGLang contributors). Independently expressed analytical
//! adaptations; see THIRD_PARTY_NOTICES.md and docs/deepseek-v41-storage.md.

Expand Down Expand Up @@ -290,14 +291,27 @@ impl Dsv41AttentionOp {
// two_level_decode_logits / _mask_topk_scores masks candidates.
// candidate_limit describes eligibility, not a pre-GEMM gather.
let index_len = compressed_len;
let fp4 = quant_tc_flops(spec, GemmQuantMode::Nvfp4.mapping())?;
// Pinned SGLang SM90 prefill uses BF16 einsum; decode unpacks
// the same FP4 K payload inside a BF16 tl.dot kernel. Storage
// precision does not imply native FP4 tensor-core arithmetic.
let (index_rate, query_bytes) = if self.kv_cache_layout
== Dsv41KvCacheLayout::SglangFp8Bf16
&& spec.gpu.sm_version == Some(90)
{
(bf16, 2.0)
} else {
(
quant_tc_flops(spec, GemmQuantMode::Nvfp4.mapping())?,
0.53125,
)
};
result = result.plus(leaf(
spec,
2.0 * tokens * inh * ihd * index_len,
batch * index_len * ihd * 0.53125
+ tokens * inh * ihd * 0.53125
+ tokens * inh * ihd * query_bytes
+ tokens * index_len * 4.0,
fp4,
index_rate,
));
// Materialized scores, top-k positions, and optional coarse candidate blocks.
let candidate_bytes = if self.is_candidate_source {
Expand Down Expand Up @@ -747,6 +761,71 @@ mod tests {
assert!((hi.mem_ms - lo.mem_ms - 25.0625 / 1e3).abs() < 1e-10);
}

#[test]
fn sglang_hopper_index_scores_use_bf16_compute_and_query_payload() {
let mut blackwell = unit_spec();
blackwell.gpu.sm_version = Some(100);
blackwell.gpu.fp4_tc_flops = Some(4e9);
let mut hopper = blackwell.clone();
hopper.gpu.sm_version = Some(90);
hopper.gpu.fp4_tc_flops = None;
for (role, ratio) in [("full", 2), ("reindex", 1)] {
for is_context in [true, false] {
let mut op = attention(role, ratio);
op.is_context = is_context;
op.head_dim = 512;
op.index_head_dim = 128;
op.kv_cache_layout = Dsv41KvCacheLayout::SglangFp8Bf16;
let (batch, s, prefix) = if is_context {
(2.0, 8.0, 1024.0)
} else {
(2.0, 1024.0, 0.0)
};
let native_fp4 = op.sol(&blackwell, batch, s, prefix).unwrap().sol.unwrap();
let native_bf16 = op.sol(&hopper, batch, s, prefix).unwrap().sol.unwrap();
let queries = if is_context { 16.0 } else { 2.0 };
let index_len = (s + prefix) / f64::from(ratio);
// Independent score ledger: Q=[queries,4,128], K=[L,128].
// K remains packed68B/row. Only Q changes from68B to256B
// per head, and score arithmetic changes from4e9 to1e9.
let score_flops = 2.0 * queries * 4.0 * 128.0 * index_len;
let math_delta = score_flops * (1.0 / 1e9 - 1.0 / 4e9) * 1e3;
let memory_delta = queries * 4.0 * (256.0 - 68.0) / 1e3;
assert!((native_bf16.math_ms - native_fp4.math_ms - math_delta).abs() < 1e-10);
assert!((native_bf16.mem_ms - native_fp4.mem_ms - memory_delta).abs() < 1e-10);
assert!(native_bf16.math_ms.is_finite() && native_bf16.math_ms > 0.0);
// A speculative FP4 entry cannot affect the actual SM90 path.
let mut misleading = hopper.clone();
misleading.gpu.fp4_tc_flops = Some(1.0);
assert_eq!(
op.sol(&misleading, batch, s, prefix).unwrap().sol.unwrap(),
native_bf16
);
}
}
}

#[test]
fn hopper_index_support_does_not_supply_missing_fp4_to_other_contracts() {
let mut op = attention("reindex", 1);
op.head_dim = 512;
op.index_head_dim = 128;
let mut spec = unit_spec();
spec.gpu.fp4_tc_flops = None;
for (layout, sm) in [
(Dsv41KvCacheLayout::LogicalFp4, Some(90)),
(Dsv41KvCacheLayout::SglangFp8Bf16, Some(100)),
(Dsv41KvCacheLayout::SglangFp8Bf16, None),
] {
op.kv_cache_layout = layout;
spec.gpu.sm_version = sm;
assert!(matches!(
op.sol(&spec, 1.0, 128.0, 0.0),
Err(AicError::MissingSystemFlops(_))
));
}
}

#[test]
fn sglang_physical_reads_and_publication_count_one_persistent_record() {
let spec = unit_spec();
Expand Down
78 changes: 78 additions & 0 deletions crates/core/src/perfmodel/operators/fpm_forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,84 @@ mod tests {
assert!((b - blend).abs() < 1e-12, "{b}");
}

#[test]
fn hopper_dsv41_roofline_supports_fpm_site_transfer_without_fp4_hardware() {
use crate::operators::Dsv41AttentionOp;
use crate::operators::dsv41::Dsv41KvCacheLayout;

// Synthetic table-selection fixture, not measured Hopper timings.
let tmp = tempfile::tempdir().unwrap();
write_pair(tmp.path(), &default_rows());
let hardware =
std::fs::read_to_string(std::path::Path::new(SYSTEMS_ROOT).join("h200_sxm.yaml"))
.unwrap();
let hardware_db = |known_sm: bool| {
let root = tmp.path().join(if known_sm { "hopper" } else { "unknown" });
std::fs::create_dir_all(&root).unwrap();
let mut yaml: serde_yaml::Value = serde_yaml::from_str(&hardware).unwrap();
if !known_sm {
yaml["gpu"].as_mapping_mut().unwrap().remove("sm_version");
}
std::fs::write(
root.join("h200_sxm.yaml"),
serde_yaml::to_string(&yaml).unwrap(),
)
.unwrap();
let mut db = PerfDatabase::load_with_sources_opts(
&root,
"h200_sxm",
"sglang",
"dev-test",
&Default::default(),
true,
)
.unwrap();
db.set_fpm_forward_for_test(crate::perf_database::FpmForwardTable::new(
tmp.path().to_path_buf(),
"b200_sxm",
"vllm",
"0.25.1",
));
db
};
let db = hardware_db(true);
assert_eq!(db.system_spec.gpu.sm_version, Some(90));
assert!(db.system_spec.gpu.fp4_tc_flops.is_none());
let mut o = op(FpmPhase::Decode);
o.sol_ops = vec![Op::Dsv41Attention(Dsv41AttentionOp {
name: "native_hopper_attention".into(),
is_context: false,
role: "reindex".into(),
compress_ratio: 1,
hidden_size: 5120,
num_heads: 32,
head_dim: 512,
q_lora_rank: 1280,
o_lora_rank: 1024,
o_groups: 4,
index_n_heads: 32,
index_head_dim: 128,
index_topk: 512,
window_size: 128,
candidate_limit: 0,
is_candidate_source: false,
bounded_prefill: false,
gemm_quant_mode: crate::common::enums::GemmQuantMode::Fp8Block,
fmha_quant_mode: crate::common::enums::FmhaQuantMode::Fp8,
kv_cache_layout: Dsv41KvCacheLayout::SglangFp8Bf16,
})];
assert_eq!(o.query(&db, &ctx(8, 512, 0)).unwrap().latency_ms, 7.0);
// Batch12 is absent, so this exercises the SOL-dependent site path.
let result = o.query(&db, &ctx(12, 512, 0)).unwrap();
assert!(result.latency_ms.is_finite() && result.latency_ms > 0.0);
assert_eq!(result.source, Source::Silicon);
// An unspecified architecture must not silently acquire SM90 support.
let db = hardware_db(false);
assert_eq!(o.query(&db, &ctx(8, 512, 0)).unwrap().latency_ms, 7.0);
let error = o.query(&db, &ctx(12, 512, 0)).unwrap_err();
assert!(error.to_string().contains("fp4_tc_flops"), "{error}");
}

/// SOL support is lazy (mirrors Python, whose SOL view answers every op
/// family): an unported family (e.g. the MLA-BMM module used below)
/// must NOT block exact hits or in-curve lerps — only sol-dependent
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/perfmodel/operators/moe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const ALL_MOE_QUANTS: &[MoeQuantMode] = &[
MoeQuantMode::W4a8Mxfp4Mxfp8Trtllm,
MoeQuantMode::W4a16Mxfp4Cutlass,
MoeQuantMode::W4a16Nvfp4,
MoeQuantMode::W4a16Mxfp4Humming,
];

fn moe_quant_from_name(name: &str) -> Option<MoeQuantMode> {
Expand Down
43 changes: 43 additions & 0 deletions crates/core/src/perfmodel/perf_database/moe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ pub(crate) fn moe_kernel_quant_rewrite(raw_quant: String, kernel_source: &str) -
"w4a8_mxfp4_mxfp8_trtllm".to_string()
}
("w4a16_mxfp4", "sglang_flashinfer_cutlass_moe") => "w4a16_mxfp4_cutlass".to_string(),
("w4a16_mxfp4", "sglang_mxfp4_humming_moe") => "w4a16_mxfp4_humming".to_string(),
_ => raw_quant,
}
}
Expand Down Expand Up @@ -764,6 +765,48 @@ mod tests {
.join("python/aisimulate/src/aisimulate_core/systems/data/b200_sxm/vllm/0.19.0")
}

#[test]
fn humming_rows_remain_distinct_from_triton_and_cutlass() {
use crate::perf_database::energy_test_fixtures::{Col, write_parquet};
let tmp = tempfile::tempdir().unwrap();
// Distinct fixture values are row-selection witnesses, not performance
// predictions: all three implementations share the physical shape.
write_parquet(
&tmp.path().join("moe_perf.parquet"),
&[
Col::Str("moe_dtype", vec!["w4a16_mxfp4"; 3]),
Col::I64("num_tokens", vec![1; 3]),
Col::I64("hidden_size", vec![5120; 3]),
Col::I64("inter_size", vec![2304; 3]),
Col::I64("topk", vec![6; 3]),
Col::I64("num_experts", vec![384; 3]),
Col::I64("moe_tp_size", vec![4; 3]),
Col::I64("moe_ep_size", vec![1; 3]),
Col::Str("distribution", vec!["uniform"; 3]),
Col::Str(
"kernel_source",
vec![
"sglang_triton_kernels_moe",
"sglang_flashinfer_cutlass_moe",
"sglang_mxfp4_humming_moe",
],
),
Col::F64("latency", vec![1.0, 2.0, 3.0]),
],
);
let table = MoeTable::new(tmp.path().to_path_buf());
for (mode, expected) in [
(MoeQuantMode::W4a16Mxfp4, 1.0),
(MoeQuantMode::W4a16Mxfp4Cutlass, 2.0),
(MoeQuantMode::W4a16Mxfp4Humming, 3.0),
] {
let value = table
.query(1, 5120, 2304, 6, 384, 4, 1, mode, "uniform", &proxy_sol)
.unwrap();
assert_eq!(value.latency, expected);
}
}

#[test]
fn moe_table_loads_b200_vllm() {
let table = MoeTable::new(b200_vllm_data_root());
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/perfmodel/py.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1711,6 +1711,7 @@ fn moe_quant_name(dtype: Option<&DataType>) -> Option<&'static str> {
DataType::Int4 => Some("int4_wo"),
DataType::W4afp8 => Some("w4afp8"),
DataType::W4a16Mxfp4 => Some("w4a16_mxfp4"),
DataType::W4a16Mxfp4Humming => Some("w4a16_mxfp4_humming"),
DataType::W4a8Mxfp4Mxfp8 => Some("w4a8_mxfp4_mxfp8"),
DataType::W4a16Nvfp4 => Some("w4a16_nvfp4"),
_ => None,
Expand Down
13 changes: 13 additions & 0 deletions docs/deepseek-v41-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ key reads, or the materialized score array at contexts 16384/131072. Selection
still respects `index_topk`. No runtime-specific pre-GEMM optimization is assumed
for the other backends' theoretical analytical graph.

For the pinned SGLang SM90 path, FP4 index **storage** is separate from
BF16 score arithmetic. Prefill calls the BF16 `scores` einsum in
`dsv4/dsv41_sparse.py`; decode uses the BF16 `tl.dot` operations in
[`sm90_fp4_indexer.py`](https://github.com/sgl-project/sglang/blob/1aa0e962b206102b7c439a4a0c4981cfec6e87bc/python/sglang/kernels/ops/attention/dsv4/sm90_fp4_indexer.py).
SOL therefore uses the system's BF16 tensor-core throughput and two-byte query
elements, retaining the packed 68-byte index-K rows. This also supplies the
roofline needed for FPM interpolation between collected sites on H100/H200.
It does not add an FP4 capability to either system or change measured latencies.
Other SM versions and the theoretical `logical_fp4` contract keep their existing
FP4 throughput requirement; a missing throughput field remains an error.

## Persistent layout and ownership

[`deepseek_v4_memory_pool.py`](https://github.com/sgl-project/sglang/blob/1aa0e962b206102b7c439a4a0c4981cfec6e87bc/python/sglang/srt/mem_cache/deepseek_v4_memory_pool.py)
Expand Down Expand Up @@ -48,6 +59,8 @@ one compressed main/index record per completed group; no extra persistent
FP4 main record or extra fused main store is counted. Low-ratio incomplete-group
or padding writes in fallback kernels and intermediate traffic are not measured;
these remain SOL lower bounds, not an exact kernel traffic trace.
SM90 prefill's temporary BF16 K and per-head score materializations are likewise
outside this ideal traffic model; SM90 decode unpacks K within its score kernel.

Other backends retain the explicitly unqualified `logical_fp4` inventory and
traffic, avoiding a guessed physical cache precision. The operator serializes
Expand Down
Loading
Loading