Skip to content
Merged
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
46 changes: 46 additions & 0 deletions configs/glm-5.2-dspark.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"architectures": ["DSparkDraftModel"],
"attention_bias": false,
"attention_dropout": 0.0,
"auto_map": {"AutoModel": "dspark.DSparkDraftModel"},
"block_size": 7,
"bos_token_id": null,
"dflash_config": {
"attention_mode": "gqa",
"confidence_head_alpha": 1.0,
"confidence_head_with_markov": true,
"enable_confidence_head": true,
"markov_head_type": "vanilla",
"markov_rank": 256,
"mask_token_id": 154856,
"projector_type": "dspark",
"target_layer_ids": [1, 19, 38, 57, 76]
},
"dtype": "bfloat16",
"eos_token_id": [154820, 154827, 154829],
"head_dim": 64,
"hidden_act": "silu",
"hidden_size": 6144,
"initializer_range": 0.02,
"intermediate_size": 12288,
"layer_types": [
"full_attention", "full_attention", "full_attention",
"full_attention", "full_attention"
],
"max_position_embeddings": 1048576,
"max_window_layers": 5,
"model_type": "qwen3",
"num_attention_heads": 64,
"num_hidden_layers": 5,
"num_key_value_heads": 16,
"num_target_layers": 78,
"pad_token_id": 154820,
"rms_norm_eps": 1e-05,
"rope_scaling": null,
"rope_theta": 8000000,
"sliding_window": null,
"tie_word_embeddings": false,
"use_cache": true,
"use_sliding_window": false,
"vocab_size": 154880
}
46 changes: 46 additions & 0 deletions configs/inkling-dspark.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"architectures": ["DSparkDraftModel"],
"attention_bias": false,
"attention_dropout": 0.0,
"auto_map": {"AutoModel": "dspark.DSparkDraftModel"},
"block_size": 7,
"bos_token_id": null,
"dflash_config": {
"attention_mode": "gqa",
"confidence_head_alpha": 1.0,
"confidence_head_with_markov": true,
"enable_confidence_head": true,
"markov_head_type": "vanilla",
"markov_rank": 256,
"mask_token_id": 200064,
"projector_type": "dspark",
"target_layer_ids": [5, 17, 35, 47, 59]
},
"dtype": "bfloat16",
"eos_token_id": 200006,
"head_dim": 64,
"hidden_act": "silu",
"hidden_size": 6144,
"initializer_range": 0.02,
"intermediate_size": 12288,
"layer_types": [
"full_attention", "full_attention", "full_attention",
"full_attention", "full_attention"
],
"max_position_embeddings": 1048576,
"max_window_layers": 5,
"model_type": "qwen3",
"num_attention_heads": 64,
"num_hidden_layers": 5,
"num_key_value_heads": 16,
"num_target_layers": 66,
"pad_token_id": 200006,
"rms_norm_eps": 1e-05,
"rope_scaling": null,
"rope_theta": 8000000,
"sliding_window": null,
"tie_word_embeddings": false,
"use_cache": true,
"use_sliding_window": false,
"vocab_size": 201024
}
1 change: 1 addition & 0 deletions configs/qwen3-4b-dspark.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"block_size": 7,
"bos_token_id": 151643,
"dflash_config": {
"attention_mode": "gqa",
"mask_token_id": 151669,
"target_layer_ids": [1, 9, 17, 25, 33],
"projector_type": "dspark",
Expand Down
23 changes: 10 additions & 13 deletions configs/qwen3-8b-dspark.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
{
"architectures": [
"DSparkDraftModel"
],
"architectures": ["DSparkDraftModel"],
"attention_bias": false,
"attention_dropout": 0.0,
"auto_map": {
"AutoModel": "dspark.DSparkDraftModel"
},
"block_size": 16,
"auto_map": {"AutoModel": "dspark.DSparkDraftModel"},
"block_size": 7,
"bos_token_id": 151643,
"dflash_config": {
"mask_token_id": 151669,
"target_layer_ids": [1, 9, 17, 25, 33],
"projector_type": "dspark",
"markov_rank": 256,
"markov_head_type": "vanilla",
"attention_mode": "gqa",
"confidence_head_alpha": 1.0,
"confidence_head_with_markov": true,
"enable_confidence_head": true,
"confidence_head_with_markov": true
"markov_head_type": "vanilla",
"markov_rank": 256,
"mask_token_id": 151669,
"projector_type": "dspark",
"target_layer_ids": [1, 9, 17, 25, 33]
},
"dtype": "bfloat16",
"eos_token_id": 151645,
Expand Down
7 changes: 4 additions & 3 deletions docs/basic_usage/disaggregated_training.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,10 @@ The online producer sends prompts to the URLs in
with the model, capture method, and auxiliary layer ids matching the draft
config. DFlash, Domino, and DSpark use the DFlash capture contract; EAGLE3 and
P-EAGLE use the EAGLE3 capture contract. Capture rejects chunked prefill and
radix-cache paths that can truncate the captured sequence. Online capture is
text-only: VLM training, including Qwen2.5-VL, is not supported. Online
evaluation is also not supported.
gives every request attempt a unique radix-cache namespace so cached prefixes
cannot truncate the captured sequence. Online capture is text-only: VLM
training, including Qwen2.5-VL, is not supported. Online evaluation is also not
supported.

The repository's strict e2e gate remains a full local test-stack orchestrator:

Expand Down
124 changes: 60 additions & 64 deletions docs/benchmarks/benchmark.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,83 @@
# Benchmarking speculative decoding
# Benchmarking inference serving

The repository keeps the model-quality and serving-performance benchmark suite
under `benchmarks/`. It is independent from training: train and export a draft
through the unified `specforge` CLI, then benchmark that exported artifact
against an SGLang server.
Use `benchmarks/bench_eagle3.py` to compare EAGLE3 serving configurations and
dataset quality. Use `specforge benchmark` to measure any existing
SGLang deployment without assuming a particular speculative algorithm.

## Run server and benchmarks together
| Runner | Server lifecycle | Measurements | Output |
| --- | --- | --- | --- |
| `python benchmarks/bench_eagle3.py` | Launches SGLang per configuration or uses an existing server | Latency, output throughput, acceptance length, and dataset accuracy when available | Timestamped JSON under `--output-dir` |
| `specforge benchmark` | Uses an existing SGLang server | Aggregate output throughput, acceptance length, and verification count when reported by SGLang | Console and optional `--output-json` |

From the repository root:
## EAGLE3 benchmark matrix

### Launch SGLang for each configuration

```bash
python benchmarks/bench_eagle3.py \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--speculative-draft-model-path /path/to/exported-draft \
--port 30000 \
--trust-remote-code \
--mem-fraction-static 0.8 \
--tp-size 1 \
--attention-backend fa3 \
--config-list 1,0,0,0 1,3,1,4 \
--benchmark-list mtbench gsm8k:5 ceval:5:accountant \
--dtype bfloat16
--model-path meta-llama/Llama-3.1-8B-Instruct \
--speculative-draft-model-path /path/to/exported-draft \
--port 30000 \
--trust-remote-code \
--mem-fraction-static 0.8 \
--tp-size 1 \
--attention-backend fa3 \
--config-list 1,0,0,0 1,3,1,4 \
--benchmark-list mtbench gsm8k:5 ceval:5:accountant \
--dtype bfloat16
```

Each `--config-list` entry is
`batch-size,num-steps,topk,num-draft-tokens`. Benchmark selectors use
`name[:num-prompts[:subset,...]]`. Available datasets include AIME, C-Eval,
FinanceQA, GPQA, GSM8K, HumanEval, LiveCodeBench, MATH-500, MBPP, MMLU,
MMStar, MT-Bench, and SimpleQA.

## Benchmark an existing server
- `--config-list` uses `batch-size,num-steps,topk,num-draft-tokens`; `1,0,0,0` is a target-only baseline.
- `--benchmark-list` uses `name[:num-prompts[:subset,...]]`.
- Supported datasets are AIME, C-Eval, FinanceQA, GPQA, GSM8K, HumanEval, LiveCodeBench, MATH-500, MBPP, MMLU, MMStar, MT-Bench, and SimpleQA.
- The runner starts a fresh server for each configuration, runs every requested dataset, flushes the cache between datasets, and writes results under `--output-dir`.

Start SGLang separately, then add `--skip-launch-server`:
### Use an existing SGLang server

```bash
python benchmarks/bench_eagle3.py \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--port 30000 \
--config-list 1,3,1,4 \
--benchmark-list mtbench:5 gsm8k:5 humaneval:5 math500:5 \
--skip-launch-server
--model-path meta-llama/Llama-3.1-8B-Instruct \
--port 30000 \
--config-list 1,3,1,4 \
--benchmark-list mtbench:5 gsm8k:5 humaneval:5 math500:5 \
--skip-launch-server
```

Results are written as timestamped JSON under `--output-dir`. The standalone
GPU microbenchmarks `bench_domino_mfu.py`,
`specforge/benchmarks/benchmark_flex_attention.py`, and
`specforge/benchmarks/benchmark_loss.py` cover trainer MFU, attention, and loss
kernel behavior respectively.

HumanEval and MBPP execute model-generated Python while scoring. Run those two
benchmarks only in an isolated container or devbox with no credentials or
production data mounted.

## Retained validation artifacts
With `--skip-launch-server`, the runner does not change the server's speculative settings. The first `--config-list` entry supplies only the request batch size.

The repository keeps the existing training-equivalence and convergence plots
as provenance for the runtime cutover. They are reference results, not a
substitute for rerunning the benchmark suite on the current commit.
## General SGLang benchmark

### DataFlow cutover equivalence
The existing-server runner supports target-only and speculative deployments on
GSM8K, MATH-500, HumanEval, MBPP, and MT-Bench.

These 100-step traces compare the former trainer (`main`) with colocated and
disaggregated DataFlow execution. Keeping them next to the benchmark suite
makes the behavioral evidence available after the duplicate trainers are
removed.

![Qwen2.5-0.5B DFlash legacy, colocated, and disaggregated loss equivalence](../../examples/assets/qwen2.5-0.5b-dflash-vs-main.png)

![Qwen2.5-0.5B EAGLE3 legacy, colocated, and disaggregated loss equivalence](../../examples/assets/qwen2.5-0.5b-eagle3-vs-main.png)
```bash
specforge benchmark \
--model Qwen/Qwen3-8B \
--dataset gsm8k \
--base-url http://127.0.0.1:30000 \
--num-prompts 1024 \
--concurrency 16 \
--output-json ./qwen3-8b-gsm8k.json
```

The [Qwen2.5-7B EAGLE3 offline parity record](eagle3-disaggregated-parity.md)
preserves the corresponding two-node metric comparison and its current
rank-dispatch command.
- Start SGLang with the target-only or speculative configuration you want to measure; this command does not launch or reconfigure the server.
- The runner flushes the server cache, runs one concurrency-sized warmup batch, excludes warmup from the measurement, and then sends `--num-prompts` requests.
- The report includes output-token throughput and includes average acceptance length and speculative verification count when SGLang returns those fields.

### Qwen3.6-27B DFlash training curves
## Comparing results

The retained curves cover both the eight-H200 colocated run and the two-GPU
online-disaggregated producer/consumer run.
Measure target-only and speculative decoding with the same target revision,
tokenizer and chat template, prompts, sampling parameters, output length,
hardware, tensor parallelism, and concurrency. For the EAGLE3 matrix, include a
zero-step configuration in one run. With the general SGLang runner, benchmark
matched target-only and speculative servers separately and compute speedup from
their throughput results.

The separate [Domino disaggregated performance findings](domino-disaggregated-performance.md)
preserve the measured one-server + DP7 tuning study, its MFU analysis, and the
canonical YAML form of the relevant controls.
Do not compare absolute throughput across the matrix and existing-server runners
because their batching and request scheduling differ.

![Qwen3.6-27B DFlash colocated training loss and draft-token accuracy](../../examples/assets/qwen36-27b-dflash-nemotron-6ep.png)
## Safety

![Qwen3.6-27B DFlash online-disaggregated training loss and draft-token accuracy](../../examples/disagg/assets/qwen36-27b-dflash-nemotron-disagg.png)
The EAGLE3 HumanEval and MBPP scorers execute model-generated Python. Run them
only in an isolated environment without credentials or production data. The
general SGLang runner measures decoding and does not execute generated code.
17 changes: 15 additions & 2 deletions examples/configs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ should make their training strategy and topology explicit.
| `model.torch_dtype` | `bfloat16` | `bfloat16`, `float16`, or `float32`. |
| `model.cache_dir` | `null` | Model/tokenizer download cache. This is distinct from `data.cache_dir`. |
| `model.mask_token_id` | `null` | DFlash-family/P-EAGLE mask token override. Otherwise it resolves from the draft config and then the tokenizer. |
| `model.tokenizer_pad_token_id` | `null` | Explicit non-negative tokenizer pad ID. Use it for released tokenizers that omit padding metadata. |
| `model.sglang_attention_backend` | `flashinfer` | SGLang attention implementation for an in-process or managed capture server. |
| `model.sglang_mem_fraction_static` | `0.4` | SGLang static-memory fraction in `(0, 1]`; inherited by managed capture servers unless they override it. |
| `model.sglang_context_length` | `null` | Positive explicit context limit. Managed capture requires at least `data.max_length + 7`; omitting it derives that value. |
Expand All @@ -165,6 +166,16 @@ should make their training strategy and topology explicit.
| `model.sglang_ep_size` | `1` | SGLang expert-parallel size; it must divide and not exceed every managed capture server's `tp_size`. |
| `model.sglang_max_running_requests` | `null` | Positive SGLang request-concurrency limit. |
| `model.sglang_max_total_tokens` | `null` | Positive SGLang token-pool limit. |
| `model.sglang_dp_size` | `null` | Optional SGLang data-parallel size. |
| `model.sglang_moe_a2a_backend` | `null` | Optional SGLang MoE all-to-all backend name. |
| `model.sglang_moe_runner_backend` | `null` | Optional SGLang MoE runner backend name. |
| `model.sglang_page_size` | `null` | Optional positive SGLang KV-cache page size. |
| `model.sglang_quantization` | `null` | Optional SGLang target quantization mode. |
| `model.sglang_fp4_gemm_runner_backend` | `null` | Optional SGLang FP4 GEMM runner backend. |
| `model.sglang_mamba_radix_cache_strategy` | `null` | Optional hybrid Mamba/radix cache strategy. |
| `model.sglang_max_mamba_cache_size` | `null` | Optional positive Mamba cache size. |
| `model.sglang_swa_full_tokens_ratio` | `null` | Optional SGLang sliding-window full-token ratio in `(0, 1]`. |
| `model.sglang_mamba_full_memory_ratio` | `null` | Optional SGLang Mamba full-memory ratio in `(0, 1]`. |

### `data`: choose exactly one training source

Expand Down Expand Up @@ -203,9 +214,11 @@ Common fields:
| `training.total_steps` | `null` | Positive optimizer/loss schedule horizon; it does not itself stop an online stream. A finite online disaggregated run may omit both fields: the producer publishes the exact horizon derived from prepared prompts, epochs, DP size, batch size, and accumulation. |
| `training.batch_size` | `1` | Per-rank microbatch size. P-EAGLE and USP require 1. |
| `training.accumulation_steps` | `1` | Positive microbatches per optimizer update. |
| `training.fsdp_sharding` | `SHARD_GRAD_OP` | Trainer FSDP mode: `SHARD_GRAD_OP`, `FULL_SHARD`, or `NO_SHARD`. |
| `training.learning_rate` | `1e-4` | Positive peak learning rate. |
| `training.warmup_ratio` | `0.015` | Fraction in `[0, 1]` used for scheduler warmup. |
| `training.max_grad_norm` | `0.5` | Positive gradient-clipping norm. |
| `training.optimizer_cpu_offload` | `false` | Keep the optimizer's FP32 master parameters and Adam state on CPU. |
| `training.attention_backend` | `flex_attention` | `eager`, `sdpa`, `flex_attention`, `fa`, or `usp`; the selected strategy must support it. |
| `training.tp_size` | `1` | Online disaggregated consumers must keep it at 1; configure target TP on capture servers. Offline non-USP ranks consume disjoint data. |
| `training.sp_ulysses_size` | `1` | Ulysses sequence-parallel factor for offline EAGLE3 USP. |
Expand All @@ -226,8 +239,8 @@ Strategy-specific fields should be written only when tuning that objective:
| Strategy | Fields and defaults |
| --- | --- |
| EAGLE3 | `training.ttt_length` (`7`), `training.lk_loss_type` (`null`; `lambda` or `alpha`), `training.kl_scale` (`1.0`), `training.kl_decay` (`1.0`) |
| DFlash / Domino / D-PACE | `training.num_anchors` (`512`), `training.loss_decay_gamma` (`null`), `training.loss_type` (`dflash`), `training.dpace_alpha` (`0.5`), `training.lambda_base_start` (`1.0`), `training.lambda_base_decay_ratio` (`0.5`) |
| DSpark | `training.dspark_ce_loss_alpha` (`0.1`), `training.dspark_l1_loss_alpha` (`0.9`), `training.dspark_confidence_head_alpha` (`1.0`) |
| DFlash / Domino / D-PACE | `training.num_anchors` (`512`), `training.loss_decay_gamma` (`null`), `training.objective_chunk_blocks` (`128`; `0` materializes all objective logits), `training.loss_type` (`dflash`), `training.dpace_alpha` (`0.5`), `training.lambda_base_start` (`1.0`), `training.lambda_base_decay_ratio` (`0.5`) |
| DSpark | Token-pooled objective with valid-first-target anchors and distributed ratio telemetry. Configure the shared `training.num_anchors` (`512`), `training.loss_decay_gamma` (`null`; production recipes use `4.0`), and `training.objective_chunk_blocks` (`128`; `0` materializes all objective logits), plus `training.dspark_ce_loss_alpha` (`0.1`), `training.dspark_l1_loss_alpha` (`0.9`), and `training.dspark_confidence_head_alpha` (`1.0`). |
| P-EAGLE | `training.num_depths` (`8`), `training.down_sample_ratio` (`0.8`), `training.down_sample_ratio_min` (`0.2`), `training.norm_before_residual` (`null`) |

New recipes must not write the loader-only migration fields
Expand Down
41 changes: 41 additions & 0 deletions examples/configs/glm-5.2-dspark-disaggregated.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
model:
target_model_path: zai-org/GLM-5.2-FP8
draft_model_config: configs/glm-5.2-dspark.json
target_backend: sglang
trust_remote_code: true
data:
train_data_path: ./cache/dataset/glm52_dspark_train.jsonl
max_length: 4096
chat_template: glm-5.2
cache_dir: cache
build_dataset_num_proc: 64
training:
strategy: dspark
num_epochs: 10
# Portable one-rank equivalent of the source recipe's global batch 512.
batch_size: 1
accumulation_steps: 512
learning_rate: 0.0006
warmup_ratio: 0.04
max_grad_norm: 1.0
num_anchors: 512
loss_decay_gamma: 4.0
objective_chunk_blocks: 128
# Optimizer-step equivalent of about 500 source microsteps at microbatch 16.
save_interval: 16
dist_timeout: 30
seed: 42
run_id: glm-5.2-dspark-disaggregated
output_dir: outputs/glm-5.2-dspark-disaggregated
deployment:
mode: disaggregated
trainer:
nnodes: 1
nproc_per_node: 1
disaggregated:
control_dir: outputs/glm-5.2-dspark-disaggregated/control
backend: mooncake
server_urls: [http://127.0.0.1:30000]
mooncake_metadata_server: http://127.0.0.1:35880/metadata
mooncake_master_server_addr: 127.0.0.1:35551
mooncake_protocol: tcp
Loading
Loading