Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ The accuracy of simulation results relies heavily on the kernel benchmark result

## Supported Features

- **Attention**: MHA/GQA, MLA. Benchmarked on FlashInfer, FlashAttention-3, FlashMLA.
- **Attention**: MHA/GQA, MLA, and table-driven DeepSeek-V4 C4/C128
compressed MQA. Benchmarked on FlashInfer, FlashAttention-3, FlashMLA.
- **MoE**: GroupedGEMM. Benchmarked on DeepGEMM.
- **Linear**: GEMM. Benchmarked on DeepGEMM.
- **Parallelization**: DP Attn, EP MoE.
Expand Down
32 changes: 32 additions & 0 deletions bench_data/dsv4/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# DeepSeek-V4-Pro H20 TP8 measurements

These rows were collected for architecture fingerprint
`dc8a15770f7f8647` on one node with eight NVIDIA H20 GPUs and SGLang
0.5.15.

Each value is a per-base-decoder-layer CUDA-event median. For each timed
forward, latency is first reduced to the maximum across TP ranks, then the
median is taken across timed forwards. Communication is excluded and modeled
separately by InferSim.

The serving keys are:

- attention: `dsv4`, FP8 weights, `fp8_e4m3` KV cache and FP32 compression
state;
- mHC: `fused_post_pre`;
- MoE: native packed-FP4 experts with `flashinfer_mxfp4`.

The exact measured shapes are prefill `B=1, Q=4096` and decode
`B=128, Q=1, past=5120`. InferSim intentionally rejects other fingerprints,
parallel configurations, precisions and shapes instead of silently
extrapolating these values.

Run the matching end-to-end simulation with:

```bash
python3 main.py \
--config-path /path/to/DeepSeek-V4-Pro/config.json \
--device-type H20 --world-size 8 --tp-size 8 --num-nodes 1 \
--max-prefill-tokens 4096 --target-isl 4096 --target-osl 2048 \
--decode-bs 128 --dsv4-decode-past-len 5120
```
3 changes: 3 additions & 0 deletions bench_data/dsv4/decode/h20/attention.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
architecture_fingerprint,tp_size,backend,attention_weight_dtype,kv_dtype,compress_state_dtype,ratio,batch_size,q_len,past_len,latency_us
dc8a15770f7f8647,8,dsv4,fp8,fp8_e4m3,fp32,128,128,1,5120,895.235094
dc8a15770f7f8647,8,dsv4,fp8,fp8_e4m3,fp32,4,128,1,5120,1237.978661
2 changes: 2 additions & 0 deletions bench_data/dsv4/decode/h20/mhc.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
architecture_fingerprint,tp_size,backend,attention_weight_dtype,batch_size,q_len,mode,latency_us
dc8a15770f7f8647,8,dsv4,fp8,128,1,fused_post_pre,249.299934
2 changes: 2 additions & 0 deletions bench_data/dsv4/decode/h20/moe.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
architecture_fingerprint,tp_size,world_size,num_nodes,backend,expert_weight_dtype,batch_size,q_len,latency_us
dc8a15770f7f8647,8,8,1,flashinfer_mxfp4,fp4,128,1,1372.636325
3 changes: 3 additions & 0 deletions bench_data/dsv4/prefill/h20/attention.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
architecture_fingerprint,tp_size,backend,attention_weight_dtype,kv_dtype,compress_state_dtype,ratio,batch_size,q_len,past_len,latency_us
dc8a15770f7f8647,8,dsv4,fp8,fp8_e4m3,fp32,128,1,4096,0,3988.013406
dc8a15770f7f8647,8,dsv4,fp8,fp8_e4m3,fp32,4,1,4096,0,7267.319441
2 changes: 2 additions & 0 deletions bench_data/dsv4/prefill/h20/mhc.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
architecture_fingerprint,tp_size,backend,attention_weight_dtype,batch_size,q_len,mode,latency_us
dc8a15770f7f8647,8,dsv4,fp8,1,4096,fused_post_pre,778.451410
2 changes: 2 additions & 0 deletions bench_data/dsv4/prefill/h20/moe.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
architecture_fingerprint,tp_size,world_size,num_nodes,backend,expert_weight_dtype,batch_size,q_len,latency_us
dc8a15770f7f8647,8,8,1,flashinfer_mxfp4,fp4,1,4096,8966.938213
138 changes: 134 additions & 4 deletions config/model_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,60 @@
import hashlib
import json
from dataclasses import dataclass


@dataclass(frozen=True)
class AttentionLayerGroup:
"""A consecutive run of DeepSeek-V4 attention layers."""

compress_ratio: int
num_layers: int


def _normalize_dtype(value):
value = str(value).lower()
if value in {"fp8", "e4m3", "e5m2", "float8_e4m3fn", "float8_e5m2"}:
return "fp8"
if value in {"bf16", "bfloat16"}:
return "bf16"
if value in {"fp4", "nvfp4", "mxfp4"}:
return "fp4"
return value


def get_deepseek_v4_architecture_fingerprint(config):
"""Return a stable key for benchmark rows belonging to one V4 variant."""
if not config.is_deepseek_v4:
raise ValueError(
"DeepSeek-V4 architecture fingerprint requested for another model"
)
architecture = {
"model_type": config.model_type,
"hidden_size": config.hidden_size,
"num_hidden_layers": config.num_hidden_layers,
"compress_ratios": config.compress_ratios,
"num_attention_heads": config.num_attention_heads,
"num_key_value_heads": config.num_key_value_heads,
"head_dim": config.head_dim,
"q_lora_rank": config.q_lora_rank,
"o_lora_rank": config.o_lora_rank,
"o_groups": config.num_output_groups,
"qk_rope_head_dim": config.qk_rope_head_dim,
"index_n_heads": config.index_n_heads,
"index_head_dim": config.index_head_dim,
"index_topk": config.index_topk,
"sliding_window": config.sliding_window,
"hc_mult": config.hc_mult,
"hc_sinkhorn_iters": config.hc_sinkhorn_iters,
"routed_experts": config.num_routed_experts,
"shared_experts": config.num_shared_experts,
"experts_per_tok": config.num_experts_per_tok,
"moe_intermediate_size": config.intermediate_size,
"attention_weight_dtype": config.attention_weight_dtype,
"expert_dtype": config.expert_dtype,
}
serialized = json.dumps(architecture, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(serialized.encode("utf-8")).hexdigest()[:16]


class ModelConfig:
Expand All @@ -15,9 +71,9 @@ def __init__(
d = d["text_config"]

self.model_type = d["model_type"]
self.is_deepseek_v4 = self.model_type == "deepseek_v4"
self.is_qwen3_5_moe = (
root_model_type == "qwen3_5_moe"
and self.model_type == "qwen3_5_moe_text"
root_model_type == "qwen3_5_moe" and self.model_type == "qwen3_5_moe_text"
)

self.hidden_size = d["hidden_size"]
Expand All @@ -41,7 +97,9 @@ def __init__(
self.linear_num_value_heads = d["linear_num_value_heads"]

self.attn_type = "MHA/GQA"
if "kv_lora_rank" in d:
if self.is_deepseek_v4:
self.attn_type = "DSV4"
elif "kv_lora_rank" in d:
self.attn_type = "MLA"

# attn
Expand All @@ -62,11 +120,60 @@ def __init__(
self.v_head_dim = d["v_head_dim"]
self.index_topk = d.get("index_topk")
self.qk_head_dim = self.qk_nope_head_dim + self.qk_rope_head_dim
elif self.attn_type == "DSV4":
self.num_attention_heads = d["num_attention_heads"]
self.num_key_value_heads = d["num_key_value_heads"]
self.head_dim = d["head_dim"]
self.q_lora_rank = d["q_lora_rank"]
self.o_lora_rank = d["o_lora_rank"]
self.num_output_groups = d["o_groups"]
self.qk_rope_head_dim = d["qk_rope_head_dim"]
self.qk_nope_head_dim = self.head_dim - self.qk_rope_head_dim
self.index_topk = d["index_topk"]
self.index_n_heads = d.get("index_n_heads", 64)
self.index_head_dim = d.get("index_head_dim", 128)
self.sliding_window = d.get("sliding_window", 128)
self.hc_mult = d.get("hc_mult", 4)
self.hc_sinkhorn_iters = d.get("hc_sinkhorn_iters", 20)
quantization_config = d.get("quantization_config", {})
self.attention_weight_dtype = _normalize_dtype(
d.get(
"attention_weight_dtype",
quantization_config.get("fmt", d.get("torch_dtype", "bf16")),
)
)
self.expert_dtype = _normalize_dtype(d.get("expert_dtype", "bf16"))

all_compress_ratios = list(d["compress_ratios"])
if len(all_compress_ratios) < self.num_hidden_layers:
raise ValueError(
"DeepSeek-V4 compress_ratios must contain one entry for "
"each transformer layer"
)
self.compress_ratios = all_compress_ratios[: self.num_hidden_layers]
self.nextn_compress_ratios = all_compress_ratios[self.num_hidden_layers :]
invalid_ratios = set(self.compress_ratios).difference({0, 4, 128})
if invalid_ratios:
raise ValueError(
"Unsupported DeepSeek-V4 compression ratio(s): "
+ ", ".join(str(ratio) for ratio in sorted(invalid_ratios))
)
self.attention_layer_groups = tuple(
self._group_compress_ratios(self.compress_ratios)
)
self.attention_ratio_counts = {}
for group in self.attention_layer_groups:
self.attention_ratio_counts[group.compress_ratio] = (
self.attention_ratio_counts.get(group.compress_ratio, 0)
+ group.num_layers
)

# FFN/MoE
self.is_moe = True
if "num_routed_experts" in d:
self.num_routed_experts = d["num_routed_experts"]
elif "n_routed_experts" in d:
self.num_routed_experts = d["n_routed_experts"]
elif "num_experts" in d:
self.num_routed_experts = d["num_experts"]
else:
Expand All @@ -89,7 +196,9 @@ def __init__(
self.shared_expert_intermediate_size // self.intermediate_size
)
else:
self.num_shared_experts = d.get("num_shared_experts", 0)
self.num_shared_experts = d.get(
"num_shared_experts", d.get("n_shared_experts", 0)
)
self.shared_expert_intermediate_size = (
self.num_shared_experts * self.intermediate_size
)
Expand All @@ -98,3 +207,24 @@ def __init__(
self.intermediate_size = d["intermediate_size"]
self.num_shared_experts = 0
self.shared_expert_intermediate_size = 0

if self.is_deepseek_v4:
self.dsv4_architecture_fingerprint = (
get_deepseek_v4_architecture_fingerprint(self)
)

@staticmethod
def _group_compress_ratios(compress_ratios):
if not compress_ratios:
return []
groups = []
current_ratio = compress_ratios[0]
count = 0
for ratio in compress_ratios:
if ratio != current_ratio:
groups.append(AttentionLayerGroup(current_ratio, count))
current_ratio = ratio
count = 0
count += 1
groups.append(AttentionLayerGroup(current_ratio, count))
return groups
Loading