Skip to content
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Organized by domain (model line / subsystem / playbook / lesson) instead of by l
| `models/glm52/ep4-gb300.md` | DP4/EP4 (`--moe-topo ep4`) on 4xGB300: 64 whole experts/rank (188 GiB weights/rank), second DeepEP shim instantiation, and a weight-only routed-expert chain (bf16×fp8 mma on the aligned recv layout — no act re-quant/relayout/remap) replacing the sm_90a DeepGEMM masked GEMMs. Bring-up green 2026-07-12 incl. layer-6 EP4 oracle 63/64 ×4 buckets; c=32 `559 tok/s` / TPOT p50 `39.05ms` untuned. Opt-in warm-cache pinned staging cuts full four-rank weight-load wall time `54.985→17.175s` and engine/HTTP-ready from `63.192→25.418s`; it stays off by default because a forced-cold network-filesystem A/B regressed `144.002→221.887s`. Needs `EP_DISABLE_GIN=1` on NIC-less nodes. |
| `models/glm52/cross-node-scaling.md` | Control plane SHIPPED (`feat/glm52-rank-host`): framed-TCP hub-and-spoke — coordinator `--rank-hosts host:port=N`, dumb `--glm52-rank-host` process, FIFO `Response` frames, fail-stop + 60s destroy watchdog. On GB300 NVL72 one rack = one NVLink/IMEX domain, so the single-LSA DeepEP shim works cross-tray with no GIN un-baking: 2-tray EP8 solo p50 23.61 / p99 24.00 ms (≈ loopback), EP widths {4,8,16,32,64} each a constexpr shim instantiation. Pitfalls: containers need the IMEX channel device; teardown must shutdown() the socket. GIN scale-out sections remain the design for IB/RoCE beyond one rack; >4 nodes upgrades control to the preserved SMR design. |
| `models/glm52/dspark-mtp.md` | DSpark speculative decoding (community `RedHatAI/GLM-5.2-speculator.dspark`, not native MTP): qwen3-arch 5-layer draft at hidden 6144 + rank-256 Markov head; verify span rides the decode buckets (span-4 default). M1 span-steps, M2 draft lane, M3 greedy round loop (sharegpt c1 1.52×), M4 sampled verify — non-greedy speculation via prefix-match over sampled tokens (c1 code 2.38× at temp 1; full rejection sampling probe-measured out at ≤ +1.5%). |
| `models/glm52/native-mtp-accuracy.md` | Native MTP must consume the target model's final-normalized hidden, matching official vLLM rather than the pre-final-norm residual. The fix raises matched c8 accepted length from `1.753` to `3.725` versus `3.786`; repeated matched c1 measures OpenInfer `7.749 ms` TPOT versus official vLLM `8.814 ms`. |
| `models/glm52/paged-kv-prefix-cache.md` | Static per-slot KV partitions → per-rank `BlockPool` of 64-token content-hashed pages (Kimi #239 pattern, zero kernel changes): full-lifetime admission reservation, coordinator-shipped `Glm52StepKv` page rows, prefix caching on by default (suffix-only prefill), DSpark × prefix-cache mutually exclusive, launch-ahead lease breaks at page boundaries. Merged as #588: all jz-38 gates green, warm prefix TTFT 14.12 s → 0.84 s (16.9×) byte-identical, step bench flat vs the D5 anchor. |
| `models/glm52/continuous-batching.md` | D2 + D2.5 execution record: multi-slot admission (8 requests/rank, least-loaded first) + {1,2,4,8} batch-bucket graphs (smallest bucket covering the fullest rank, per-bucket `Glm52BucketState`). Solo 22.4 ms/step; D2's c9 cliff killed (47.1 → 31.8 ms/step, 171 → 254 tok/s); poisson soaks clean; pinned slot-3/7 parity PASS. Known: buckets are distinct FP associations (bucket-crossing requests can greedy-diverge at near-ties); open anomaly: one-off silent request drop (#551). |
| `models/glm52/oracle-harness.md` | Self-contained accuracy oracle: `tools/accuracy/glm52_oracle.py` (pinned transformers 5.12.1 official `glm_moe_dsa`, fp8-precision-emulated) emits hardcodable Rust probe constants; `oracle/mla.rs` replays the seeded input and asserts. MLA gate green on jz38 (64/64 probes, diff RMS 1.8e-5), negative controls red. No MB fixtures in git. |
Expand Down
445 changes: 445 additions & 0 deletions docs/models/glm52/native-mtp-accuracy.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion openinfer-glm52/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use serde_json::Value;
pub(crate) const GLM52_HIDDEN: usize = 6144;
pub(crate) const GLM52_VOCAB: usize = 154_880;
pub(crate) const GLM52_LAYERS: usize = 78;
/// Checkpoint layer containing the native multi-token prediction decoder.
pub(crate) const GLM52_MTP_LAYER: usize = GLM52_LAYERS;
pub(crate) const GLM52_DENSE_LAYERS: usize = 3;
/// The checkpoint's `max_position_embeddings` — `probe_config_json` pins the
/// config to exactly this, so it doubles as the architecture ceiling any
Expand Down Expand Up @@ -41,7 +43,7 @@ pub(crate) const GLM52_EXPERT_INTERMEDIATE: usize = 2048;
pub(crate) const GLM52_ROUTED_EXPERTS: usize = 256;
pub(crate) const GLM52_TOPK: usize = 8;
const GLM52_SHARED_EXPERTS: usize = 1;
const GLM52_ROUTED_SCALING_FACTOR: f64 = 2.5;
pub(crate) const GLM52_ROUTED_SCALING_FACTOR: f64 = 2.5;
const GLM52_RMS_NORM_EPS: f64 = 1.0e-5;
/// The f32 the GPU norm kernels consume (every RMSNorm in the model shares
/// the one checkpoint eps that `probe_config_json` validates).
Expand Down
10 changes: 10 additions & 0 deletions openinfer-glm52/src/fp8.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,16 @@ impl Glm52MlpScratch {
.alloc_zeros::<f32>(rows * GLM52_GEMV_MMA_SCRATCH_FLOATS_PER_ROW)?,
})
}

#[cfg(test)]
pub(crate) fn gate_up(&self) -> &CudaSlice<bf16> {
&self.gate_up
}

#[cfg(test)]
pub(crate) fn silu_out(&self) -> &CudaSlice<bf16> {
&self.silu_out
}
}

/// A plain fp8 SwiGLU MLP over the scratch's `rows` tokens into a
Expand Down
41 changes: 37 additions & 4 deletions openinfer-glm52/src/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ pub(crate) struct Glm52LayerCaches {
pub(crate) index_k_cache: Option<CudaSlice<u8>>,
}

/// Sparse-index policy for one decoder-layer forward. Target layers compute
/// or inherit indices according to their checkpoint role. Native MTP computes
/// layer 78's indices on its first pass, then reuses the selected rows for
/// the remaining four proposal iterations (`index_share_for_mtp_iteration`).
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum Glm52LayerIndexMode {
Normal,
Reuse,
}

/// Everything one decode step shares across layers: the token position, the two
/// rotary tables (MLA interleaved; indexer half-split — different conventions,
/// same `[32]` cos/sin extent), and the paging plumbing common to every layer's
Expand Down Expand Up @@ -168,6 +178,7 @@ pub(crate) fn glm52_layer_attention_half(
parity: usize,
first_layer: bool,
tp_ar: Option<(&mut crate::moe_tp::Glm52MoeTpState, usize)>,
index_mode: Glm52LayerIndexMode,
) -> Result<()> {
// Attention-TP: a head-sharded layer (8 of 64 heads) produces an o_proj
// PARTIAL that must cross the AR brick before the residual add; holding
Expand Down Expand Up @@ -195,8 +206,15 @@ pub(crate) fn glm52_layer_attention_half(
let tokens = step.mla_sched.batch();
glm52_mla_front_q_into(ctx, &w.mla, &s.layer.normed, &mut s.mla_front)?;
let mut topk_ready = None;
match &w.indexer {
Glm52LayerIndexer::Full(indexer) => {
match (&w.indexer, index_mode) {
(Glm52LayerIndexer::Full(_), Glm52LayerIndexMode::Reuse) => {
ensure!(
caches.index_k_cache.is_some(),
"GLM5.2 reused full-indexer layer is missing its index-K cache"
);
*carry_ready = true;
}
(Glm52LayerIndexer::Full(indexer), Glm52LayerIndexMode::Normal) => {
let index_k_cache = caches
.index_k_cache
.as_mut()
Expand Down Expand Up @@ -227,12 +245,15 @@ pub(crate) fn glm52_layer_attention_half(
}
*carry_ready = true;
}
Glm52LayerIndexer::Shared => {
(Glm52LayerIndexer::Shared, Glm52LayerIndexMode::Normal) => {
ensure!(
caches.index_k_cache.is_none(),
"GLM5.2 shared-indexer layer unexpectedly owns an index-K cache"
);
}
(Glm52LayerIndexer::Shared, Glm52LayerIndexMode::Reuse) => {
anyhow::bail!("GLM5.2 cannot request explicit top-k reuse on a shared-indexer layer")
}
}
ensure!(
*carry_ready,
Expand Down Expand Up @@ -396,7 +417,19 @@ pub(crate) fn glm52_decoder_layer_forward(
tokens,
s.layer.normed.data_mut(),
)?;
glm52_layer_attention_half(ctx, None, w, caches, step, s, carry_ready, 0, true, None)?;
glm52_layer_attention_half(
ctx,
None,
w,
caches,
step,
s,
carry_ready,
0,
true,
None,
Glm52LayerIndexMode::Normal,
)?;
match &w.mlp {
Glm52LayerMlp::Dense(dense) => glm52_dense_mlp_forward_into(
ctx,
Expand Down
Loading
Loading