Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
0010c9d
feat(dflash): add DFlash speculative decoding module for GPT-OSS
Jul 30, 2026
9b02fab
docs(dflash): add runnable GPT-OSS speculative decoding example
Jul 30, 2026
28993ae
test(dflash): add greedy-agreement eval harness
Jul 30, 2026
276fcf2
feat(glm): register glmmoedsa architecture (GlmMoeDsaForCausalLM, typ…
Aug 3, 2026
d797138
refactor(fp8): relocate dequant_fp8_blockwise to utils/fp8.py (back-c…
Aug 3, 2026
e6a982d
feat(glm): hf_config parse_moe_param/parse_expert_id GLM branch (MTP-…
Aug 3, 2026
b02c3bd
feat(glm): SyncGlmMoeDsaMoEBlock wrapper (HF-gate routing delegation)
Aug 3, 2026
5fdaab7
feat(glm): FP8 block dequant-on-load for GLM experts (honors modules_…
Aug 3, 2026
5ff3c27
feat(glm): DSA indexer classification + IndexShare ownership map
Aug 3, 2026
5b2c2b6
feat(glm): wire GlmMoeDsa into offload engine (patch/unpatch, isinsta…
Aug 3, 2026
b91f1b1
docs(glm): add GLM-5.2 to supported models + FP8 offload usage
Aug 3, 2026
c675f35
test(glm): verify MLA/DSA attention tensors stay resident (not misrou…
Aug 3, 2026
7e319b9
test(glm): base contract chain + RAM/GPU-gated e2e smoke (skips until…
Aug 3, 2026
cc6ad2e
test(glm): verify IndexShare needs no offload change (indexers only o…
Aug 3, 2026
16ea9ca
feat(glm): fp8-in-store Python layer (glm_fp8_in_store flag, scale si…
Aug 3, 2026
034406b
test(glm): budget-split coherence + gated 32k long-context prefill smoke
Aug 3, 2026
eb03ba8
test(glm): consolidated DSA/IndexShare/MLA integration test (real-con…
Aug 3, 2026
9b39a10
feat(glm): DFlash adapter hook (availability + pairing validation, re…
Aug 3, 2026
3167500
feat(glm): native FP8 e4m3 block-scale dequant kernel + set_scales bi…
Aug 3, 2026
48d5385
fix(glm): model-load fixes exposed by real load (DeepseekV2Moe rename…
Aug 3, 2026
3db718a
test(glm): tiny synthetic GLM reproducer (gated MOE_GLM_TINY) — repro…
Aug 3, 2026
a4ddae4
fix(glm): register routed experts (add experts ModuleList + first_k_d…
Aug 3, 2026
a80b3de
feat(glm): MTP speculative decoding (built-in layer 78 drafter, lossl…
Aug 3, 2026
ffd93d9
feat(glm): MTP spec-decode instrumentation (accept-length tau, per-st…
Aug 3, 2026
116d253
test(glm): forked spec-decode test runner (MTP lossless + stats + DFl…
Aug 3, 2026
adc4d7f
feat(glm): validate GLM serving via OpenAI api_server_v2 (works as-is…
Aug 3, 2026
951f3f2
test(glm): serving smoke covers completions + chat + streaming (one s…
Aug 3, 2026
c16cb46
feat(glm): native fp8-in-store dequant-on-copy (SetScales + MoEMLP de…
Aug 3, 2026
54a6b05
feat(perf-model): minimal roofline package + GLM-5.2 ModelParams/deco…
Aug 3, 2026
cdc510d
feat(perf-model): GLM benchmark harness (tiny-model decode + MTP on/o…
Aug 3, 2026
a1ba227
feat(perf-model): GLM perf validation/summary + roofline & throughput…
Aug 3, 2026
d8e5924
chore(glm): finalize fp8-in-store always-on tail
Aug 4, 2026
437f589
fix(offload): bind resident params to correct offload id (GLM q_a_lay…
Aug 4, 2026
03b03b9
refine(offload): scope resident-weight tensor_ids sort to DENSE nodes…
Aug 4, 2026
4b8b377
fix(offload): bind begin/end to stable tensor id (fixes intermittent …
Aug 4, 2026
d80febc
fix(glm): keep MTP-layer routed experts FP8 on fresh offload
Aug 5, 2026
a0b0973
refactor(engine): extract _generate_standard + add spec_strategy seam…
Aug 5, 2026
48f68f2
feat(engine): rich on-device forward helper + 5-layer hidden-state ca…
Aug 5, 2026
b0e1ed2
feat(dflash): accept-rule + block-build pure fns with hand-checked un…
Aug 5, 2026
1ecde09
feat(dflash): harden drafter loader (trust_remote_code + dim/vocab/ma…
Aug 5, 2026
7c96117
test(dflash): tiny synthetic gpt-oss target + drafter fixtures (CPU d…
Aug 5, 2026
ccac7a1
feat(dflash): native draft->verify->rollback state machine (bonus emi…
Aug 5, 2026
b1309d2
fix(dflash): tiny-target sliding_window 8->128 (>= block_size+1) for …
Aug 5, 2026
f6d4d1b
feat(dflash): edge cases (accept 0/9, EOS mid-block, max_new_tokens, …
Aug 5, 2026
0ea5015
feat(dflash): wire native speculator as GenerationEngine spec_strategy
Aug 5, 2026
29c544f
test(dflash): tiny-model E2E losslessness parity (native == plain gre…
Aug 5, 2026
9c176a5
test(engine): spec-off byte-identity regression + gpt-oss suite green
Aug 5, 2026
7f62bda
test(dflash): GPU-gated 120B validation harness (agreement-rate/accep…
Aug 5, 2026
d4a4c51
docs(dflash): native-path example + usage notes (trust_remote_code, T…
Aug 5, 2026
2d10994
fix(dflash): gather 5-layer feature + posterior to drafter device (mu…
Aug 6, 2026
ccb7cac
fix(gpt-oss): resident-load MXFP4 experts + sinks + router
Aug 6, 2026
c4c74e7
feat(dflash): route-ahead expert prefetch (A1-A5)
Aug 7, 2026
c97a8ad
feat(dflash): sampled (non-greedy) speculative decoding accept ops
Aug 7, 2026
671edda
feat(serving): DFlash speculative decoding in continuous-batching engine
Aug 7, 2026
99384cd
feat(dflash): speculator core — batched decoding, sampled/route-ahead…
Aug 7, 2026
efb4385
test(dflash): GPU-gated 20b losslessness + serving-vs-sync harnesses
Aug 7, 2026
ac2689a
style(dflash): apply ruff-format to new spec-decode/serving files
Aug 7, 2026
fe7410b
chore(format): pre-commit run --all-files (repo-wide ruff-format + cl…
Aug 7, 2026
4579a3f
chore(format): pre-commit convergence (ruff-format idempotency)
Aug 7, 2026
a0f75b2
fix(prefetch): guard null derefs in ReleaseTensor
Aug 9, 2026
86eb0b2
feat(qwen3.5): enable offloaded text-only serving
Aug 9, 2026
f9453ff
feat(dflash): offloaded Qwen3.5 speculative decoding + route-ahead
Aug 9, 2026
65bda13
chore(format): ruff check --fix + ruff-format convergence (0.6.9)
Aug 9, 2026
b1a553c
Merge branch 'dev' into feat/dflash-tracks-abcd
Aug 10, 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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ MoE-Infinity supports HuggingFace MoE checkpoints registered in [`moe_infinity/c
| [Mixtral](https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1) | `mistralai/Mixtral-8x7B-Instruct-v0.1`, `Mixtral-8x22B` |
| [Qwen3-MoE](https://huggingface.co/Qwen/Qwen3-30B-A3B) | `Qwen/Qwen3-30B-A3B` |
| [Qwen3.5-MoE](https://huggingface.co/Qwen/Qwen3.5-35B-A3B) | `Qwen/Qwen3.5-35B-A3B` (text-only; see note) |
| [GLM-5.2](https://huggingface.co/zai-org/GLM-5.2-FP8) | `zai-org/GLM-5.2-FP8` (requires `transformers` >= 5.12) |
| [GPT-OSS](https://huggingface.co/models?search=gpt-oss) | `openai/gpt-oss-*` |
| [DBRX](https://huggingface.co/models?search=dbrx) | `databricks/dbrx-instruct` |
| [Jamba](https://huggingface.co/models?search=jamba) | `ai21labs/Jamba-*` |
Expand All @@ -73,6 +74,8 @@ MoE-Infinity supports HuggingFace MoE checkpoints registered in [`moe_infinity/c

> Qwen3.5-MoE (`Qwen3_5MoeForConditionalGeneration`, requires `transformers` >= 5.12) is a vision-language checkpoint served **text-only**: its 256 routed experts are offloaded while the small text backbone — token embeddings, the hybrid linear (GatedDeltaNet) / full attention layers, shared expert, and `lm_head` — stays resident on GPU. The v5 packed expert tensors are expanded to per-expert on load. Vision and MTP weights are present but unused for text generation.

> GLM-5.2 (`GlmMoeDsaForCausalLM`, `model_type="glm_moe_dsa"`) requires `transformers` >= 5.12 and is registered only when that class is importable (otherwise skipped automatically). Its 256 routed FP8 experts (block-scale e4m3, dequantized to BF16 on load) are offloaded; the 3 dense layers, shared expert, MLA attention, DSA indexer, and MTP layer stay resident. Sparse attention uses `attn_implementation="eager"`.

## Installation

We recommend installing MoE-Infinity in a virtual environment. To install MoE-Infinity, you can either install it from PyPI or build it from source.
Expand Down Expand Up @@ -248,6 +251,21 @@ torchrun --nproc-per-node 4 examples/deepseek_v4_flash_example.py \

**Suggested hardware / environment (Path B):** **4x GPUs** (tensor-parallel mp4; mp1 exceeds the sparse-attention kernel's shared-memory limit), **>= ~140 GB pinned host RAM**, and the `v4flash` docker image (tilelang `fp4_gemm`; on Blackwell/SM120 the native `moe_infinity._v4_fp4` CUDA path is auto-selected and is 1.5–3.2x faster). The checkpoint must first be converted to the official mp-sharded format. See [`moe_infinity/models/deepseek_v4/README.md`](./moe_infinity/models/deepseek_v4/README.md) for checkpoint conversion, kernel selection, and validation details.

### GLM-5.2 (FP8 Expert Offloading)

GLM-5.2 (`zai-org/GLM-5.2-FP8`) runs through the drop-in `MoE` class:

```python
from moe_infinity import MoE

model = MoE("zai-org/GLM-5.2-FP8", {
"offload_path": "/ssd/moe-infinity/glm-5.2",
"device_memory_ratio": 0.5,
})
```

> **Memory note:** the FP8 block-scaled routed experts are kept FP8 in the host store (~753 GB) and dequantized on-device by the expert dispatcher, which requires the native `moe_infinity._v4_fp4` extension. Weights that run in PyTorch rather than the dispatcher — MLA attention, the DSA indexer, the dense-layer MLPs, and the shared expert — are dequantized to BF16 on load. Requires `transformers` >= 5.12.

### Benchmarking

For correct throughput and latency measurement, it is critical to separate **prefill time (TTFT)** from **decode throughput**. Including prefill in your throughput calculation will produce misleadingly low numbers.
Expand Down
8 changes: 8 additions & 0 deletions benchmarks/performance_model/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from benchmarks.performance_model.roofline import predict_decode
from benchmarks.performance_model.types import (
DemandResult,
ModelParams,
WorkloadPoint,
)

__all__ = ["ModelParams", "WorkloadPoint", "DemandResult", "predict_decode"]
122 changes: 122 additions & 0 deletions benchmarks/performance_model/bench_glm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from __future__ import annotations

import argparse
import csv
import os

import torch


def measure_tiny_glm(tmp_dir: str, gen_len: int = 16) -> dict:
from benchmarks.performance_model.model_config import extract_model_params
from benchmarks.performance_model.roofline import predict_decode
from benchmarks.performance_model.types import WorkloadPoint
from moe_infinity import MoE
from moe_infinity.spec_decode.glm_mtp import GlmMtpSpeculator
from tests.python.integration._glm_tiny import build_tiny_glm

ckpt_dir = os.path.join(tmp_dir, "tiny_glm_ckpt")
off_dir = os.path.join(tmp_dir, "tiny_glm_off")
os.makedirs(ckpt_dir, exist_ok=True)
os.makedirs(off_dir, exist_ok=True)

build_tiny_glm(ckpt_dir)

model = MoE(ckpt_dir, {"offload_path": off_dir, "device_memory_ratio": 0.8})

input_ids = torch.tensor([[1, 2, 3, 4]], device="cuda")
batch = input_ids.shape[0]
seq_len = input_ids.shape[1]

torch.cuda.reset_peak_memory_stats()
for _ in range(2):
with torch.no_grad():
model.generate(input_ids, max_new_tokens=gen_len)
torch.cuda.synchronize()

torch.cuda.reset_peak_memory_stats()
start_evt = torch.cuda.Event(enable_timing=True)
end_evt = torch.cuda.Event(enable_timing=True)

start_evt.record()
with torch.no_grad():
model.generate(input_ids, max_new_tokens=gen_len)
end_evt.record()
torch.cuda.synchronize()

elapsed_ms = start_evt.elapsed_time(end_evt)
decode_tok_s = gen_len / (elapsed_ms / 1000.0)
peak_mem_bytes = torch.cuda.max_memory_allocated()

spec = GlmMtpSpeculator(model)
mtp_input = input_ids.clone()

mtp_start = torch.cuda.Event(enable_timing=True)
mtp_end = torch.cuda.Event(enable_timing=True)

mtp_start.record()
spec.generate(mtp_input, max_new_tokens=gen_len, temperature=0.0)
mtp_end.record()
torch.cuda.synchronize()

mtp_elapsed_ms = mtp_start.elapsed_time(mtp_end)
mtp_tok_s = gen_len / (mtp_elapsed_ms / 1000.0)
mean_accept_len = spec.last_stats.get("mean_accept_len", 1.0)

params = extract_model_params(ckpt_dir)
wp = WorkloadPoint(batch=batch, seq_len=seq_len, gen_len=gen_len)
demand = predict_decode(params, wp)

return {
"model": "tiny_glm",
"batch": batch,
"seq_len": seq_len,
"gen_len": gen_len,
"decode_tok_s": decode_tok_s,
"mtp_tok_s": mtp_tok_s,
"mean_accept_len": mean_accept_len,
"peak_mem_bytes": peak_mem_bytes,
"pred_flops_per_token": demand.flops_per_token,
"pred_hbm_bytes_per_token": demand.hbm_bytes_per_token,
"pred_bound": demand.bound,
}


def run(out_csv: str, quick: bool = True, gen_len: int = 16) -> None:
import tempfile

os.makedirs(
os.path.dirname(out_csv) if os.path.dirname(out_csv) else ".",
exist_ok=True,
)

with tempfile.TemporaryDirectory() as tmp_dir:
row = measure_tiny_glm(tmp_dir, gen_len=gen_len)

fieldnames = [
"model",
"batch",
"seq_len",
"gen_len",
"decode_tok_s",
"mtp_tok_s",
"mean_accept_len",
"peak_mem_bytes",
"pred_flops_per_token",
"pred_hbm_bytes_per_token",
"pred_bound",
]

with open(out_csv, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerow(row)


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--out", default="results/perf_model/glm_bench.csv")
parser.add_argument("--quick", action="store_true", default=True)
parser.add_argument("--gen", type=int, default=16)
args = parser.parse_args()
run(args.out, quick=args.quick, gen_len=args.gen)
89 changes: 89 additions & 0 deletions benchmarks/performance_model/model_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from __future__ import annotations

from benchmarks.performance_model.types import ModelParams


def _expert_dtype_from_config(config) -> str:
qc = getattr(config, "quantization_config", None)
if qc is None:
return "bf16"
if isinstance(qc, dict):
method = qc.get("quant_method", "")
else:
method = getattr(qc, "quant_method", "")
return "fp8" if "fp8" in str(method).lower() else "bf16"


def _extract_glm(config) -> ModelParams:
return ModelParams(
name=getattr(config, "_name_or_path", "glm_moe_dsa"),
num_layers=config.num_hidden_layers,
num_attn_heads=config.num_attention_heads,
num_kv_heads=getattr(
config, "num_key_value_heads", config.num_attention_heads
),
head_dim=getattr(
config, "head_dim", config.hidden_size // config.num_attention_heads
),
hidden_size=config.hidden_size,
vocab_size=config.vocab_size,
num_experts=config.n_routed_experts,
top_k=config.num_experts_per_tok,
shared_experts=getattr(config, "n_shared_experts", 1),
expert_intermediate_size=config.moe_intermediate_size,
first_k_dense=getattr(config, "first_k_dense_replace", 0),
expert_dtype=_expert_dtype_from_config(config),
attn_dtype="bf16",
kv_lora_rank=getattr(config, "kv_lora_rank", None),
q_lora_rank=getattr(config, "q_lora_rank", None),
)


def _extract_generic(config) -> ModelParams:
num_experts = (
getattr(config, "num_local_experts", None)
or getattr(config, "n_routed_experts", None)
or getattr(config, "num_experts", 1)
)
top_k = getattr(config, "num_experts_per_tok", None) or getattr(
config, "top_k", 1
)
return ModelParams(
name=getattr(config, "_name_or_path", "unknown"),
num_layers=config.num_hidden_layers,
num_attn_heads=config.num_attention_heads,
num_kv_heads=getattr(
config, "num_key_value_heads", config.num_attention_heads
),
head_dim=getattr(
config, "head_dim", config.hidden_size // config.num_attention_heads
),
hidden_size=config.hidden_size,
vocab_size=config.vocab_size,
num_experts=num_experts,
top_k=top_k,
shared_experts=getattr(config, "n_shared_experts", 0),
expert_intermediate_size=getattr(
config,
"moe_intermediate_size",
getattr(config, "intermediate_size", config.hidden_size * 4),
),
first_k_dense=getattr(config, "first_k_dense_replace", 0),
expert_dtype=_expert_dtype_from_config(config),
attn_dtype="bf16",
kv_lora_rank=getattr(config, "kv_lora_rank", None),
q_lora_rank=getattr(config, "q_lora_rank", None),
)


def extract_model_params(model_name_or_path: str) -> ModelParams:
from transformers import AutoConfig

config = AutoConfig.from_pretrained(
model_name_or_path, trust_remote_code=True
)
arch = (getattr(config, "architectures", None) or [""])[0].lower()

if "glmmoedsa" in arch:
return _extract_glm(config)
return _extract_generic(config)
Loading
Loading