Add files using upload-large-folder tool
Browse files- README.md +106 -0
- chat_template.jinja +251 -0
- config.json +792 -0
- generation_config.json +12 -0
- model-00001-of-00041.safetensors +3 -0
- model-00005-of-00041.safetensors +3 -0
- model-00009-of-00041.safetensors +3 -0
- model-00010-of-00041.safetensors +3 -0
- model-00011-of-00041.safetensors +3 -0
- model-00013-of-00041.safetensors +3 -0
- model-00014-of-00041.safetensors +3 -0
- model-00019-of-00041.safetensors +3 -0
- model-00020-of-00041.safetensors +3 -0
- model-00021-of-00041.safetensors +3 -0
- model-00023-of-00041.safetensors +3 -0
- model-00024-of-00041.safetensors +3 -0
- model-00030-of-00041.safetensors +3 -0
- model-00031-of-00041.safetensors +3 -0
- model-00032-of-00041.safetensors +3 -0
- model-00034-of-00041.safetensors +3 -0
- model-00035-of-00041.safetensors +3 -0
- quant/build.sh +14 -0
- quant/exl3_farm.py +185 -0
- quant/make_recipe.py +53 -0
- quant/orchestrate.py +378 -0
- quant/patch_aarch64.py +77 -0
- quant/patch_farm.py +122 -0
- quant/recipe.yaml +0 -0
- quant/start-orch.sh +2 -0
- serving/Dockerfile.dcp +21 -0
- serving/Dockerfile.keys +2 -0
- serving/Dockerfile.keys3 +3 -0
- serving/Dockerfile.mul1 +2 -0
- serving/Dockerfile.v145 +6 -0
- serving/ablit_runtime.py +460 -0
- serving/donor_diff.py +14 -0
- serving/env.tp4.example +47 -0
- serving/exl3.py +862 -0
- serving/finalize_config.py +81 -0
- serving/glm53_probe.py +32 -0
- serving/keys-launch.sh +15 -0
- serving/mla_attn_dcp.py +0 -0
- serving/patch_ablit_dsv2.py +55 -0
- serving/patch_overlay_mtp.py +40 -0
- serving/patch_overlay_mul1.py +201 -0
- serving/patch_tp4_launcher.py +49 -0
- serving/sm120_dcp.py +284 -0
- serving/start-tp4.sh +1776 -0
- serving/taskbench.py +52 -0
- tokenizer_config.json +33 -0
README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
base_model: zai-org/GLM-5.3
|
| 4 |
+
tags:
|
| 5 |
+
- exl3
|
| 6 |
+
- exllamav3
|
| 7 |
+
- glm-5.3
|
| 8 |
+
- dgx-spark
|
| 9 |
+
- gb10
|
| 10 |
+
- vllm
|
| 11 |
+
- 1m-context
|
| 12 |
+
- mtp
|
| 13 |
+
library_name: vllm
|
| 14 |
+
pipeline_tag: text-generation
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
# keys-GLM-5.3-EXL3 — full GLM-5.3 (753B MoE) as a 3-bit EXL3, quantized *for* a serving container
|
| 18 |
+
|
| 19 |
+
**What this is:** the complete `zai-org/GLM-5.3` (78 layers, 256 routed experts, DeepSeek-style sparse MLA attention with indexer, one MTP block) with the **routed experts quantized to 3-bit EXL3 trellis** and everything else kept bf16/fp16. 308 GB in 41 safetensors shards. It serves on **four NVIDIA DGX Spark (GB10) with TP=4 + decode-context-parallel 4 at a 1,000,000-token context**, inside Mia AI Lab's vLLM container for the GLM-5.3-*Flash* EXL3 (`ghcr.io/miaai-lab/glm-5.3-flash-2x-dgx-sparks:exl3`) plus the small patch set shipped in `serving/`.
|
| 20 |
+
|
| 21 |
+
| | |
|
| 22 |
+
|---|---|
|
| 23 |
+
| Source | `zai-org/GLM-5.3` (FP8 block-scaled, 756 GB, 141 shards) |
|
| 24 |
+
| Quantizer | exllamav3 **1.4.5** (`convert.py`, `-hb 16 -mb 16 -cpi 0`, calibration 250 rows × 2048) |
|
| 25 |
+
| Routed experts | 57,600 matrices (75 MoE layers × 256 experts × gate/up/down) at **3.00 bpw**, `out_scales: always` |
|
| 26 |
+
| Codebook | layer 3 = `mcg`, layers 4–77 = `mul1` (see *Codebook note*); the serving overlay handles both per layer |
|
| 27 |
+
| Everything else | attention (q_a/q_b/kv_a/o_proj/indexer), dense MLP (layers 0–2), shared experts, router, norms, embed, lm_head: **bf16/fp16 (unquantized)** |
|
| 28 |
+
| MTP block (layer 78) | **bf16**, kept — usable as an MTP speculator |
|
| 29 |
+
| Per-rank footprint (TP=4) | ~75 GiB weights (+~5 GiB with the MTP block loaded) |
|
| 30 |
+
| Context verified | needle-in-haystack PASS at 8.5K / 25K / 50K / 105K / 263K / **979K** tokens (DCP=4 sharded KV) |
|
| 31 |
+
|
| 32 |
+
## Why we made it this way
|
| 33 |
+
|
| 34 |
+
Every previous EXL3 GLM-5.3 we tried to serve on DGX Spark cost hours-to-days of patching: the published quants were made for TabbyAPI/exllamav3 (full-model EXL3, 2-bit heads, mul1 everywhere) and vLLM has no full-model EXL3 — Mia's container only implements EXL3 for the **routed experts**, with attention/dense/head native. So this time we quantized **to the container's ABI first**:
|
| 35 |
+
|
| 36 |
+
* scope = routed experts only (the only thing the overlay's `Exl3Config` quantizes), uniform integer bits in the overlay's allowed set {3,4,5,6} → **3-bit**, because 4-bit experts (≈101 GB/rank) do not fit four 128 GB GB10s next to a 1M KV cache; 3-bit ≈ 78 GB/rank does.
|
| 37 |
+
* `-hb 16 -mb 16`: head and MTP stay bf16 so the stock vLLM paths load them.
|
| 38 |
+
* suffix layout `trellis/suh/svh/<codebook marker>` exactly as the overlay's loader expects, `quantization_config.json` with `quant_method=exl3, bits=3, scope=glm53_routed_experts_only`.
|
| 39 |
+
|
| 40 |
+
It still needed a handful of patches (all in `serving/`), but they are small, mechanical, and now documented — the point of this repo.
|
| 41 |
+
|
| 42 |
+
## How it was made (4× DGX Spark "encode farm")
|
| 43 |
+
|
| 44 |
+
One 753B model does not fit a single 128 GB Spark for quantization, and exllamav3 has no multi-GPU convert. We built a rolling pipeline (`quant/orchestrate.py`):
|
| 45 |
+
|
| 46 |
+
1. **Rolling source shards.** The FP8 source is 756 GB and not layer-ordered; the orchestrator keeps a 5-shard download window (sha-checked), runs `convert.py` for **two layers at a time** in a fresh container (`--max_module L+1`, then `-r` resume), and after each step rewrites finished shards **dropping only `model.layers.L.mlp.experts.*`** (norms/attention stay — the compile step needs them). Peak disk ≈ 660 GB; permanent shards = embed/lm_head, final norm, and the layer-78 MTP shard (must exist at every resume).
|
| 47 |
+
2. **Remote expert-encoding farm** (`quant/exl3_farm.py` + `quant/patch_farm.py`). exllamav3's per-layer H (Hessian) capture is serial, but the 768 expert matrices per layer are independent once H is final. The patch adds remote work threads to `quantize_linears_parallel`: it ships each matrix + its finalized H over TCP (length-prefixed `torch.save`) to worker containers on the other three Sparks, which return `trellis/suh/svh/marker`. Verified bit-identical to local encoding. **One MoE layer: 49 min single-GPU → ~20–22 min on four.** Whole model: ~27 wall-clock hours including a power-cut restart.
|
| 48 |
+
3. **Resume-safe.** A power interruption at 60/78 lost nothing: exllamav3 checkpoints after every layer; the in-flight layer is simply redone.
|
| 49 |
+
4. **Compile.** `compile.py` needs `marisa_trie` (not in the base image) — `pip install marisa-trie` in the quant container.
|
| 50 |
+
|
| 51 |
+
`quant/recipe.yaml` (58,167 tensor entries) is included; `quant/build.sh` + `quant/patch_aarch64.py` build exllamav3 1.4.5 on aarch64/CUDA 13 inside the Mia container image (AVX all-reduce/CPU-MoE stubs + pause intrinsic).
|
| 52 |
+
|
| 53 |
+
### Codebook note (read before you quantize with exllamav3 ≥ 1.4)
|
| 54 |
+
|
| 55 |
+
`convert.py -r` (resume) lets the **command-line default win over the stored args** for the codebook (`("codebook", True, "mul1")` in the arg table). We passed `-cb mcg` on the first run only; every resumed 2-layer run silently quantized with `mul1`. Result: layer 3 is `mcg`, layers 4–77 are `mul1`. Quality is identical (both are 3-bit trellis codebooks), but Mia's overlay was **mcg-only** — which is why `serving/exl3.py` now detects the codebook per layer and drives the kernels' `(mcg, mul1)` flag pairs. **If you resume an exllamav3 job, re-pass every quant flag.**
|
| 56 |
+
|
| 57 |
+
## Serving on 4× DGX Spark (what `serving/` contains and why)
|
| 58 |
+
|
| 59 |
+
Base: `ghcr.io/miaai-lab/glm-5.3-flash-2x-dgx-sparks:exl3` + its `start-tp4.sh` launcher (github.com/MiaAI-Lab/GLM-5.3-Flash-EXL3-2x-DGX-Sparks). Image layers we add (Dockerfiles included, build order `dcp → mul1 → v145 → keys → keys3`):
|
| 60 |
+
|
| 61 |
+
| Patch | Why |
|
| 62 |
+
|---|---|
|
| 63 |
+
| `exl3.py` (overlay) | + `mul1` marker tensors and per-layer codebook detection; markers may be 0-d scalars; `NullConfig/InferParams` stub for exllamav3 ≥ 1.x's `LinearEXL3`; **MTP block exempt from EXL3** so `SPEC_METHOD=mtp` loads the bf16 MTP experts through the stock path |
|
| 64 |
+
| exllamav3 **1.4.5** swap (`Dockerfile.v145`) | the image's exllamav3 0.0.43 `exl3_moe` kernel hard-rejects `mul1` (`TORCH_CHECK`); 1.4.5 has the same 29-arg signature (+`num_active`) and accepts it. Built against the same base torch, so the `.so` drops in |
|
| 65 |
+
| `sparse_attn_indexer.py` top-k fallback (`Dockerfile.keys`) | prefill `persistent_topk` for top-k 2048 oversubscribes the GB10's 48 SMs; Mia's runtime patch only covers the decode ("kpool") path |
|
| 66 |
+
| `sm120_dcp.py` + `mla_attn_dcp.py` (`Dockerfile.dcp`) | decode-context-parallel port of the SM120 sparse-MLA backend (KV sharded across TP ranks → 1M context on four GB10s; the stock stack replicates KV and tops out ~300K) |
|
| 67 |
+
| `patch_ablit_dsv2.py` + `ablit_runtime.py` | optional load-time o_proj transplant hook for the full-model classes (`DeepseekV2Model`/`DeepSeekMTP`); off by default |
|
| 68 |
+
| `patch_tp4_launcher.py` | adds `LOCAL_MODEL_DIR`/`LOCAL_DFLASH_DIR` (serve from an NFS dir) and `ABLIT_HOST_DIR` to `start-tp4.sh` |
|
| 69 |
+
| `keys-launch.sh`, `env.tp4.example` | our launch wrapper (drops page caches on all ranks first) and the working `.env.tp4` |
|
| 70 |
+
|
| 71 |
+
Launcher-level gotchas we hit (all in `env.tp4.example` comments): `--ulimit nofile=1048576` (NCCL "Too many open files" at 4 ranks), a rank whose fabric IP sits on the *second* CX7 port needs its own `WORKER_CX7_IF/IB`, the NFS server node must bind-mount its own export at the shared path, `gpu_memory_utilization` above ~0.82 is refused by vLLM's startup guard (the worker already holds ~14 GiB of CUDA/NCCL state at check time) — use `--kv-cache-memory-bytes` to size the KV cache explicitly.
|
| 72 |
+
|
| 73 |
+
### Measured (4× GB10, TP=4, DCP=4, CUDA graphs, fp8 KV)
|
| 74 |
+
|
| 75 |
+
| Config | C1 per-stream | C2 | C4 | C8 aggregate | TTFT @C8 |
|
| 76 |
+
|---|---|---|---|---|---|
|
| 77 |
+
| no speculation | 8–9 tok/s | 16 | 25 | 38 | ~2 s |
|
| 78 |
+
| **MTP k=3** (bf16 MTP block) — prose | 10.4 | 15.9 | 21.9 | 31.5 | 2.1 s |
|
| 79 |
+
| **MTP k=3** — code | 16.0 | 22.0 | 28.2 | 43.1 | 1.9 s |
|
| 80 |
+
|
| 81 |
+
256-token outputs, thinking off. Prefill ≈ 320–410 tok/s (the limiter at depth: a 979K prompt takes ~52 min to first token; decode there is 12 tok/s). DFlash2 draft: loads, but the draft's sliding-window layers are rejected by vLLM's DCP path (`DCP not support sliding window`) — parked.
|
| 82 |
+
|
| 83 |
+
## Quick start (4 Sparks)
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
git clone https://github.com/MiaAI-Lab/GLM-5.3-Flash-EXL3-2x-DGX-Sparks glm53-mia && cd glm53-mia
|
| 87 |
+
python3 serving/patch_tp4_launcher.py start-tp4.sh # LOCAL_MODEL_DIR + ABLIT_HOST_DIR knobs
|
| 88 |
+
# build the image chain on EVERY rank (see serving/Dockerfile.* ; ~1 min each on top of the Mia base)
|
| 89 |
+
cp serving/env.tp4.example .env.tp4 # set rank IPs / NIC pins / LOCAL_MODEL_DIR=<this repo on shared storage>
|
| 90 |
+
SKIP_PULL=1 SKIP_SHIP=1 SKIP_BUILD=1 SKIP_DOWNLOAD=1 SKIP_SYNC=1 SKIP_OVERLAY_VERIFY=1 ./start-tp4.sh
|
| 91 |
+
```
|
| 92 |
+
OpenAI-compatible API on `:8888`, served model name `GLM-5.3-EXL3`. Sampling: temperature 1.0, top_p 0.95, thinking on (GLM defaults).
|
| 93 |
+
|
| 94 |
+
## Lessons for "quantize with the serving container in mind"
|
| 95 |
+
|
| 96 |
+
1. Read the serving overlay's **loader** first: allowed scope, bits set, tensor suffixes, marker dtype/shape, codebook — and quantize to that ABI, not to the quantizer's defaults.
|
| 97 |
+
2. Keep anything the container serves natively (attention, dense, head, MTP) unquantized; the memory cost is small next to the experts and it removes whole classes of patches.
|
| 98 |
+
3. Verify tensor suffixes after the **first resumed** step, not at the end (see the codebook note).
|
| 99 |
+
4. Budget memory per rank for weights + KV + the ~14 GiB the process holds before vLLM measures anything; size KV explicitly.
|
| 100 |
+
5. A per-layer encode farm turns a multi-day single-GPU convert into an overnight job on the same boxes that will serve the model.
|
| 101 |
+
|
| 102 |
+
## Credits
|
| 103 |
+
|
| 104 |
+
Zhipu / zai-org for GLM-5.3 · turboderp for exllamav3 · Mia AI Lab for the GB10 EXL3 vLLM container and launcher · Blackfrost-Research for the DERISKED GLM-5.3 (source of the optional ablit donors; **not applied** in these weights) · Quantized and served by Kevin (@drowzeys) on a 4× DGX Spark cluster, 2026-09.
|
| 105 |
+
|
| 106 |
+
License: the weights inherit the `zai-org/GLM-5.3` license (MIT). Patches in `serving/` are MIT; they modify Apache-2.0 vLLM/SGLang/exllamav3 code — see their headers.
|
chat_template.jinja
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[gMASK]<sop>
|
| 2 |
+
{%- set effective_reasoning_effort = reasoning_effort if reasoning_effort is defined and reasoning_effort in ['low', 'high'] else 'max' -%}
|
| 3 |
+
{%- if effective_reasoning_effort is not none -%}<|system|>Reasoning Effort: {{ effective_reasoning_effort | capitalize }}{%- endif -%}
|
| 4 |
+
{%- set clear_thinking = clear_thinking if clear_thinking is defined else false -%}
|
| 5 |
+
{%- if tools -%}
|
| 6 |
+
{%- macro tool_to_json(tool) -%}
|
| 7 |
+
{%- set ns_tool = namespace(first=true) -%}
|
| 8 |
+
{{ '{' -}}
|
| 9 |
+
{%- for k, v in tool.items() -%}
|
| 10 |
+
{%- if k != 'defer_loading' and k != 'strict' -%}
|
| 11 |
+
{%- if not ns_tool.first -%}{{- ', ' -}}{%- endif -%}
|
| 12 |
+
{%- set ns_tool.first = false -%}
|
| 13 |
+
"{{ k }}": {{ v | tojson(ensure_ascii=False) }}
|
| 14 |
+
{%- endif -%}
|
| 15 |
+
{%- endfor -%}
|
| 16 |
+
{{- '}' -}}
|
| 17 |
+
{%- endmacro -%}
|
| 18 |
+
{%- macro tool_references_to_response(refs) -%}
|
| 19 |
+
{{- '<tool_response><tools>\n' -}}
|
| 20 |
+
{%- for tr in refs -%}
|
| 21 |
+
{%- for tool in tools -%}
|
| 22 |
+
{%- if 'function' in tool -%}
|
| 23 |
+
{%- set tool = tool['function'] -%}
|
| 24 |
+
{%- endif -%}
|
| 25 |
+
{%- if tool.name == tr.name -%}
|
| 26 |
+
{{- tool_to_json(tool) + '\n' -}}
|
| 27 |
+
{%- endif -%}
|
| 28 |
+
{%- endfor -%}
|
| 29 |
+
{%- endfor -%}
|
| 30 |
+
{{- '</tools></tool_response>' -}}
|
| 31 |
+
{%- endmacro -%}
|
| 32 |
+
<|system|>
|
| 33 |
+
# Tools
|
| 34 |
+
|
| 35 |
+
You may call one or more functions to assist with the user query.
|
| 36 |
+
|
| 37 |
+
You are provided with function signatures within <tools></tools> XML tags:
|
| 38 |
+
<tools>
|
| 39 |
+
{% for tool in tools %}
|
| 40 |
+
{%- if 'function' in tool -%}
|
| 41 |
+
{%- set tool = tool['function'] -%}
|
| 42 |
+
{%- endif -%}
|
| 43 |
+
{% if tool.defer_loading is not defined or not tool.defer_loading %}
|
| 44 |
+
{{ tool_to_json(tool) }}
|
| 45 |
+
{% endif %}
|
| 46 |
+
{% endfor %}
|
| 47 |
+
</tools>
|
| 48 |
+
|
| 49 |
+
For each function call, output the function name and arguments within the following XML format:
|
| 50 |
+
<tool_call>{function-name}<arg_key>{arg-key-1}</arg_key><arg_value>{arg-value-1}</arg_value><arg_key>{arg-key-2}</arg_key><arg_value>{arg-value-2}</arg_value>...</tool_call>{%- endif -%}
|
| 51 |
+
{%- macro visible_text(content) -%}
|
| 52 |
+
{%- if content is string -%}
|
| 53 |
+
{{- content }}
|
| 54 |
+
{%- elif content is iterable and content is not mapping -%}
|
| 55 |
+
{%- for item in content -%}
|
| 56 |
+
{%- if item is mapping and item.type == 'text' -%}
|
| 57 |
+
{{- item.text }}
|
| 58 |
+
{%- elif item is string -%}
|
| 59 |
+
{{- item }}
|
| 60 |
+
{%- elif item is mapping and item.type in ['image', 'image_url', 'video', 'video_url', 'audio', 'audio_url', 'input_audio'] -%}
|
| 61 |
+
{%- set media_type = item.type | replace('_url', '') | replace('input_', '') -%}
|
| 62 |
+
{{- "<reminder>You are unable to process this " ~ media_type ~ " because you don't have multi-modal input ability. Try different methods.</reminder>" }}
|
| 63 |
+
{%- endif -%}
|
| 64 |
+
{%- endfor -%}
|
| 65 |
+
{%- else -%}
|
| 66 |
+
{{- content }}
|
| 67 |
+
{%- endif -%}
|
| 68 |
+
{%- endmacro -%}
|
| 69 |
+
{%- macro tool_response(text) -%}
|
| 70 |
+
{{- '<tool_response>' + text + '</tool_response>' -}}
|
| 71 |
+
{%- endmacro -%}
|
| 72 |
+
{%- macro render_tool_response(m) -%}
|
| 73 |
+
{%- if m.content is string -%}
|
| 74 |
+
{{- tool_response(m.content) -}}
|
| 75 |
+
{%- elif m.content and m.content is not mapping and m.content.0.type == "tool_reference" -%}
|
| 76 |
+
{{- tool_references_to_response(m.content) -}}
|
| 77 |
+
{%- elif is_list_of_outputs(m) -%}
|
| 78 |
+
{%- for tr in m.content -%}
|
| 79 |
+
{%- if tr.output is iterable and tr.output is not string and tr.output is not mapping and tr.output and tr.output.0.type == "tool_reference" -%}
|
| 80 |
+
{{- tool_references_to_response(tr.output) -}}
|
| 81 |
+
{%- else -%}
|
| 82 |
+
{{- tool_response(visible_text(tr.output)) -}}
|
| 83 |
+
{%- endif -%}
|
| 84 |
+
{%- endfor -%}
|
| 85 |
+
{%- else -%}
|
| 86 |
+
{{- tool_response(visible_text(m.content)) -}}
|
| 87 |
+
{%- endif -%}
|
| 88 |
+
{%- endmacro -%}
|
| 89 |
+
{%- macro id_of(obj) -%}
|
| 90 |
+
{%- if obj.tool_call_id -%}
|
| 91 |
+
{{- obj.tool_call_id -}}
|
| 92 |
+
{%- elif obj.id -%}
|
| 93 |
+
{{- obj.id -}}
|
| 94 |
+
{%- endif -%}
|
| 95 |
+
{%- endmacro -%}
|
| 96 |
+
{%- macro is_list_of_outputs(m) -%}
|
| 97 |
+
{%- if m.content and m.content.0.output is defined -%}1{%- endif -%}
|
| 98 |
+
{%- endmacro -%}
|
| 99 |
+
{%- macro has_dup_tool_result_id(lo, hi, target) -%}
|
| 100 |
+
{%- set ns_cnt = namespace(n=0) -%}
|
| 101 |
+
{%- for k in range(lo, hi + 1) -%}
|
| 102 |
+
{%- set m = messages[k] -%}
|
| 103 |
+
{%- if is_list_of_outputs(m) -%}
|
| 104 |
+
{%- for entry in m.content -%}
|
| 105 |
+
{%- if id_of(entry) == target -%}
|
| 106 |
+
{%- set ns_cnt.n = ns_cnt.n + 1 -%}
|
| 107 |
+
{%- endif -%}
|
| 108 |
+
{%- endfor -%}
|
| 109 |
+
{%- elif id_of(m) == target -%}
|
| 110 |
+
{%- set ns_cnt.n = ns_cnt.n + 1 -%}
|
| 111 |
+
{%- endif -%}
|
| 112 |
+
{%- if ns_cnt.n > 1 -%}{%- break -%}{%- endif -%}
|
| 113 |
+
{%- endfor -%}
|
| 114 |
+
{%- if ns_cnt.n > 1 -%}1{%- endif -%}
|
| 115 |
+
{%- endmacro -%}
|
| 116 |
+
{%- macro tc_id_exists(tcs, target) -%}
|
| 117 |
+
{%- set ns_f = namespace(found=false) -%}
|
| 118 |
+
{%- for tc in tcs -%}
|
| 119 |
+
{%- if id_of(tc) == target -%}
|
| 120 |
+
{%- set ns_f.found = true -%}
|
| 121 |
+
{%- break -%}
|
| 122 |
+
{%- endif -%}
|
| 123 |
+
{%- endfor -%}
|
| 124 |
+
{%- if ns_f.found -%}1{%- endif -%}
|
| 125 |
+
{%- endmacro -%}
|
| 126 |
+
{%- set ns = namespace(last_user_index=-1) -%}
|
| 127 |
+
{%- for m in messages %}
|
| 128 |
+
{%- if m.role == 'user' %}
|
| 129 |
+
{%- set ns.last_user_index = loop.index0 -%}
|
| 130 |
+
{%- endif %}
|
| 131 |
+
{%- endfor %}
|
| 132 |
+
{%- for m in messages -%}
|
| 133 |
+
{%- if m.role == 'user' -%}<|user|>{{ visible_text(m.content) }}
|
| 134 |
+
{%- elif m.role == 'assistant' -%}
|
| 135 |
+
<|assistant|>
|
| 136 |
+
{%- set content = visible_text(m.content) %}
|
| 137 |
+
{%- if m.reasoning_content is string %}
|
| 138 |
+
{%- set reasoning_content = m.reasoning_content %}
|
| 139 |
+
{%- elif '</think>' in content %}
|
| 140 |
+
{%- set reasoning_content = content.split('</think>')[0].split('<think>')[-1] %}
|
| 141 |
+
{%- set content = content.split('</think>')[-1] %}
|
| 142 |
+
{%- endif %}
|
| 143 |
+
{%- if (not clear_thinking or loop.index0 > ns.last_user_index) and reasoning_content is defined -%}
|
| 144 |
+
{{ '<think>' + reasoning_content + '</think>'}}
|
| 145 |
+
{%- else -%}
|
| 146 |
+
{{ '<think></think>' }}
|
| 147 |
+
{%- endif -%}
|
| 148 |
+
{%- if content.strip() -%}
|
| 149 |
+
{{ content.strip() }}
|
| 150 |
+
{%- endif -%}
|
| 151 |
+
{% if m.tool_calls %}
|
| 152 |
+
{% for tc in m.tool_calls %}
|
| 153 |
+
{%- if tc.function %}
|
| 154 |
+
{%- set tc = tc.function %}
|
| 155 |
+
{%- endif %}
|
| 156 |
+
{{- '<tool_call>' + tc.name -}}
|
| 157 |
+
{% set _args = tc.arguments %}{% for k, v in _args.items() %}<arg_key>{{ k }}</arg_key><arg_value>{{ v | tojson(ensure_ascii=False) if v is not string else v }}</arg_value>{% endfor %}</tool_call>{% endfor %}
|
| 158 |
+
{% endif %}
|
| 159 |
+
{%- elif m.role == 'tool' -%}
|
| 160 |
+
{%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
|
| 161 |
+
{{- '<|observation|>' -}}
|
| 162 |
+
{%- set block_start = loop.index0 -%}
|
| 163 |
+
{%- set ns_blk = namespace(end=block_start) -%}
|
| 164 |
+
{%- for j in range(block_start, messages|length) -%}
|
| 165 |
+
{%- if messages[j].role == 'tool' -%}
|
| 166 |
+
{%- set ns_blk.end = j -%}
|
| 167 |
+
{%- else -%}
|
| 168 |
+
{%- break -%}
|
| 169 |
+
{%- endif -%}
|
| 170 |
+
{%- endfor -%}
|
| 171 |
+
{%- set ns_a = namespace(tool_calls=none) -%}
|
| 172 |
+
{%- if block_start > 0 and messages[block_start - 1].role == 'assistant' and messages[block_start - 1].tool_calls -%}
|
| 173 |
+
{%- set ns_a.tool_calls = messages[block_start - 1].tool_calls -%}
|
| 174 |
+
{%- endif -%}
|
| 175 |
+
{%- set ns_chk = namespace(can_sort=true) -%}
|
| 176 |
+
{%- if not ns_a.tool_calls -%}
|
| 177 |
+
{%- set ns_chk.can_sort = false -%}
|
| 178 |
+
{%- else -%}
|
| 179 |
+
{%- for k in range(block_start, ns_blk.end + 1) -%}
|
| 180 |
+
{%- set m = messages[k] -%}
|
| 181 |
+
{%- if is_list_of_outputs(m) -%}
|
| 182 |
+
{%- for entry in m.content -%}
|
| 183 |
+
{%- set eid = id_of(entry) -%}
|
| 184 |
+
{%- if not eid -%}
|
| 185 |
+
{%- set ns_chk.can_sort = false -%}
|
| 186 |
+
{%- elif has_dup_tool_result_id(block_start, ns_blk.end, eid) -%}
|
| 187 |
+
{%- set ns_chk.can_sort = false -%}
|
| 188 |
+
{%- elif not tc_id_exists(ns_a.tool_calls, eid) -%}
|
| 189 |
+
{%- set ns_chk.can_sort = false -%}
|
| 190 |
+
{%- endif -%}
|
| 191 |
+
{%- endfor -%}
|
| 192 |
+
{%- else -%}
|
| 193 |
+
{%- set tk_id = id_of(m) -%}
|
| 194 |
+
{%- if not tk_id -%}
|
| 195 |
+
{%- set ns_chk.can_sort = false -%}
|
| 196 |
+
{%- elif has_dup_tool_result_id(block_start, ns_blk.end, tk_id) -%}
|
| 197 |
+
{%- set ns_chk.can_sort = false -%}
|
| 198 |
+
{%- elif not tc_id_exists(ns_a.tool_calls, tk_id) -%}
|
| 199 |
+
{%- set ns_chk.can_sort = false -%}
|
| 200 |
+
{%- endif -%}
|
| 201 |
+
{%- endif -%}
|
| 202 |
+
{%- endfor -%}
|
| 203 |
+
{%- for i in range(ns_a.tool_calls | length) -%}
|
| 204 |
+
{%- set tc_id = id_of(ns_a.tool_calls[i]) -%}
|
| 205 |
+
{%- if not tc_id -%}
|
| 206 |
+
{%- set ns_chk.can_sort = false -%}
|
| 207 |
+
{%- endif -%}
|
| 208 |
+
{%- for j in range(i + 1, ns_a.tool_calls | length) -%}
|
| 209 |
+
{%- if id_of(ns_a.tool_calls[j]) == tc_id -%}
|
| 210 |
+
{%- set ns_chk.can_sort = false -%}
|
| 211 |
+
{%- endif -%}
|
| 212 |
+
{%- endfor -%}
|
| 213 |
+
{%- endfor -%}
|
| 214 |
+
{%- endif -%}
|
| 215 |
+
{%- if ns_chk.can_sort -%}
|
| 216 |
+
{%- for tc in ns_a.tool_calls -%}
|
| 217 |
+
{%- set tc_id = id_of(tc) -%}
|
| 218 |
+
{%- for k in range(block_start, ns_blk.end + 1) -%}
|
| 219 |
+
{%- set m = messages[k] -%}
|
| 220 |
+
{%- if is_list_of_outputs(m) -%}
|
| 221 |
+
{%- for entry in m.content -%}
|
| 222 |
+
{%- set eid = id_of(entry) -%}
|
| 223 |
+
{%- if eid == tc_id -%}
|
| 224 |
+
{%- if entry.output is iterable and entry.output is not string and entry.output is not mapping and entry.output and entry.output.0.type == "tool_reference" -%}
|
| 225 |
+
{{- tool_references_to_response(entry.output) -}}
|
| 226 |
+
{%- else -%}
|
| 227 |
+
{{- tool_response(visible_text(entry.output)) -}}
|
| 228 |
+
{%- endif -%}
|
| 229 |
+
{%- endif -%}
|
| 230 |
+
{%- endfor -%}
|
| 231 |
+
{%- else -%}
|
| 232 |
+
{%- set tk_id = id_of(m) -%}
|
| 233 |
+
{%- if tk_id == tc_id -%}
|
| 234 |
+
{{- render_tool_response(m) -}}
|
| 235 |
+
{%- endif -%}
|
| 236 |
+
{%- endif -%}
|
| 237 |
+
{%- endfor -%}
|
| 238 |
+
{%- endfor -%}
|
| 239 |
+
{%- else -%}
|
| 240 |
+
{%- for k in range(block_start, ns_blk.end + 1) -%}
|
| 241 |
+
{{- render_tool_response(messages[k]) -}}
|
| 242 |
+
{%- endfor -%}
|
| 243 |
+
{%- endif -%}
|
| 244 |
+
{% endif -%}
|
| 245 |
+
{%- elif m.role == 'system' -%}
|
| 246 |
+
<|system|>{{ visible_text(m.content) }}
|
| 247 |
+
{%- endif -%}
|
| 248 |
+
{%- endfor -%}
|
| 249 |
+
{%- if add_generation_prompt -%}
|
| 250 |
+
<|assistant|>{{- '<think>' -}}
|
| 251 |
+
{%- endif -%}
|
config.json
ADDED
|
@@ -0,0 +1,792 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"GlmMoeDsaForCausalLM"
|
| 4 |
+
],
|
| 5 |
+
"attention_bias": false,
|
| 6 |
+
"attention_dropout": 0.0,
|
| 7 |
+
"dtype": "bfloat16",
|
| 8 |
+
"eos_token_id": [
|
| 9 |
+
154820,
|
| 10 |
+
154827,
|
| 11 |
+
154829
|
| 12 |
+
],
|
| 13 |
+
"ep_size": 1,
|
| 14 |
+
"first_k_dense_replace": 3,
|
| 15 |
+
"head_dim": 192,
|
| 16 |
+
"hidden_act": "silu",
|
| 17 |
+
"hidden_size": 6144,
|
| 18 |
+
"index_head_dim": 128,
|
| 19 |
+
"index_n_heads": 32,
|
| 20 |
+
"index_share_for_mtp_iteration": true,
|
| 21 |
+
"index_skip_topk_offset": 3,
|
| 22 |
+
"index_topk": 2048,
|
| 23 |
+
"index_topk_freq": 4,
|
| 24 |
+
"index_topk_pattern": null,
|
| 25 |
+
"indexer_rope_interleave": true,
|
| 26 |
+
"indexer_types": [
|
| 27 |
+
"full",
|
| 28 |
+
"full",
|
| 29 |
+
"full",
|
| 30 |
+
"shared",
|
| 31 |
+
"shared",
|
| 32 |
+
"shared",
|
| 33 |
+
"full",
|
| 34 |
+
"shared",
|
| 35 |
+
"shared",
|
| 36 |
+
"shared",
|
| 37 |
+
"full",
|
| 38 |
+
"shared",
|
| 39 |
+
"shared",
|
| 40 |
+
"shared",
|
| 41 |
+
"full",
|
| 42 |
+
"shared",
|
| 43 |
+
"shared",
|
| 44 |
+
"shared",
|
| 45 |
+
"full",
|
| 46 |
+
"shared",
|
| 47 |
+
"shared",
|
| 48 |
+
"shared",
|
| 49 |
+
"full",
|
| 50 |
+
"shared",
|
| 51 |
+
"shared",
|
| 52 |
+
"shared",
|
| 53 |
+
"full",
|
| 54 |
+
"shared",
|
| 55 |
+
"shared",
|
| 56 |
+
"shared",
|
| 57 |
+
"full",
|
| 58 |
+
"shared",
|
| 59 |
+
"shared",
|
| 60 |
+
"shared",
|
| 61 |
+
"full",
|
| 62 |
+
"shared",
|
| 63 |
+
"shared",
|
| 64 |
+
"shared",
|
| 65 |
+
"full",
|
| 66 |
+
"shared",
|
| 67 |
+
"shared",
|
| 68 |
+
"shared",
|
| 69 |
+
"full",
|
| 70 |
+
"shared",
|
| 71 |
+
"shared",
|
| 72 |
+
"shared",
|
| 73 |
+
"full",
|
| 74 |
+
"shared",
|
| 75 |
+
"shared",
|
| 76 |
+
"shared",
|
| 77 |
+
"full",
|
| 78 |
+
"shared",
|
| 79 |
+
"shared",
|
| 80 |
+
"shared",
|
| 81 |
+
"full",
|
| 82 |
+
"shared",
|
| 83 |
+
"shared",
|
| 84 |
+
"shared",
|
| 85 |
+
"full",
|
| 86 |
+
"shared",
|
| 87 |
+
"shared",
|
| 88 |
+
"shared",
|
| 89 |
+
"full",
|
| 90 |
+
"shared",
|
| 91 |
+
"shared",
|
| 92 |
+
"shared",
|
| 93 |
+
"full",
|
| 94 |
+
"shared",
|
| 95 |
+
"shared",
|
| 96 |
+
"shared",
|
| 97 |
+
"full",
|
| 98 |
+
"shared",
|
| 99 |
+
"shared",
|
| 100 |
+
"shared",
|
| 101 |
+
"full",
|
| 102 |
+
"shared",
|
| 103 |
+
"shared",
|
| 104 |
+
"shared"
|
| 105 |
+
],
|
| 106 |
+
"initializer_range": 0.02,
|
| 107 |
+
"intermediate_size": 12288,
|
| 108 |
+
"kv_lora_rank": 512,
|
| 109 |
+
"max_position_embeddings": 1048576,
|
| 110 |
+
"mlp_layer_types": [
|
| 111 |
+
"dense",
|
| 112 |
+
"dense",
|
| 113 |
+
"dense",
|
| 114 |
+
"sparse",
|
| 115 |
+
"sparse",
|
| 116 |
+
"sparse",
|
| 117 |
+
"sparse",
|
| 118 |
+
"sparse",
|
| 119 |
+
"sparse",
|
| 120 |
+
"sparse",
|
| 121 |
+
"sparse",
|
| 122 |
+
"sparse",
|
| 123 |
+
"sparse",
|
| 124 |
+
"sparse",
|
| 125 |
+
"sparse",
|
| 126 |
+
"sparse",
|
| 127 |
+
"sparse",
|
| 128 |
+
"sparse",
|
| 129 |
+
"sparse",
|
| 130 |
+
"sparse",
|
| 131 |
+
"sparse",
|
| 132 |
+
"sparse",
|
| 133 |
+
"sparse",
|
| 134 |
+
"sparse",
|
| 135 |
+
"sparse",
|
| 136 |
+
"sparse",
|
| 137 |
+
"sparse",
|
| 138 |
+
"sparse",
|
| 139 |
+
"sparse",
|
| 140 |
+
"sparse",
|
| 141 |
+
"sparse",
|
| 142 |
+
"sparse",
|
| 143 |
+
"sparse",
|
| 144 |
+
"sparse",
|
| 145 |
+
"sparse",
|
| 146 |
+
"sparse",
|
| 147 |
+
"sparse",
|
| 148 |
+
"sparse",
|
| 149 |
+
"sparse",
|
| 150 |
+
"sparse",
|
| 151 |
+
"sparse",
|
| 152 |
+
"sparse",
|
| 153 |
+
"sparse",
|
| 154 |
+
"sparse",
|
| 155 |
+
"sparse",
|
| 156 |
+
"sparse",
|
| 157 |
+
"sparse",
|
| 158 |
+
"sparse",
|
| 159 |
+
"sparse",
|
| 160 |
+
"sparse",
|
| 161 |
+
"sparse",
|
| 162 |
+
"sparse",
|
| 163 |
+
"sparse",
|
| 164 |
+
"sparse",
|
| 165 |
+
"sparse",
|
| 166 |
+
"sparse",
|
| 167 |
+
"sparse",
|
| 168 |
+
"sparse",
|
| 169 |
+
"sparse",
|
| 170 |
+
"sparse",
|
| 171 |
+
"sparse",
|
| 172 |
+
"sparse",
|
| 173 |
+
"sparse",
|
| 174 |
+
"sparse",
|
| 175 |
+
"sparse",
|
| 176 |
+
"sparse",
|
| 177 |
+
"sparse",
|
| 178 |
+
"sparse",
|
| 179 |
+
"sparse",
|
| 180 |
+
"sparse",
|
| 181 |
+
"sparse",
|
| 182 |
+
"sparse",
|
| 183 |
+
"sparse",
|
| 184 |
+
"sparse",
|
| 185 |
+
"sparse",
|
| 186 |
+
"sparse",
|
| 187 |
+
"sparse",
|
| 188 |
+
"sparse"
|
| 189 |
+
],
|
| 190 |
+
"model_type": "glm_moe_dsa",
|
| 191 |
+
"moe_intermediate_size": 2048,
|
| 192 |
+
"moe_layer_freq": 1,
|
| 193 |
+
"moe_router_dtype": "float32",
|
| 194 |
+
"n_group": 1,
|
| 195 |
+
"n_routed_experts": 256,
|
| 196 |
+
"n_shared_experts": 1,
|
| 197 |
+
"norm_topk_prob": true,
|
| 198 |
+
"num_attention_heads": 64,
|
| 199 |
+
"num_experts_per_tok": 8,
|
| 200 |
+
"num_hidden_layers": 78,
|
| 201 |
+
"num_key_value_heads": 64,
|
| 202 |
+
"num_nextn_predict_layers": 1,
|
| 203 |
+
"pad_token_id": 154820,
|
| 204 |
+
"pretraining_tp": 1,
|
| 205 |
+
"q_lora_rank": 2048,
|
| 206 |
+
"qk_head_dim": 256,
|
| 207 |
+
"qk_nope_head_dim": 192,
|
| 208 |
+
"qk_rope_head_dim": 64,
|
| 209 |
+
"rms_norm_eps": 1e-05,
|
| 210 |
+
"rope_interleave": true,
|
| 211 |
+
"rope_parameters": {
|
| 212 |
+
"rope_theta": 8000000,
|
| 213 |
+
"rope_type": "default"
|
| 214 |
+
},
|
| 215 |
+
"routed_scaling_factor": 2.5,
|
| 216 |
+
"scoring_func": "sigmoid",
|
| 217 |
+
"tie_word_embeddings": false,
|
| 218 |
+
"topk_group": 1,
|
| 219 |
+
"topk_method": "noaux_tc",
|
| 220 |
+
"transformers_version": "5.15.0",
|
| 221 |
+
"use_cache": true,
|
| 222 |
+
"v_head_dim": 256,
|
| 223 |
+
"vocab_size": 154880,
|
| 224 |
+
"quantization_config": {
|
| 225 |
+
"quant_method": "exl3",
|
| 226 |
+
"version": "1.4.5",
|
| 227 |
+
"bits": 3,
|
| 228 |
+
"head_bits": 16,
|
| 229 |
+
"calibration": {
|
| 230 |
+
"rows": 250,
|
| 231 |
+
"cols": 2048
|
| 232 |
+
},
|
| 233 |
+
"out_scales": "always",
|
| 234 |
+
"codebook": "mul1",
|
| 235 |
+
"original_quantization_config": {
|
| 236 |
+
"activation_scheme": "dynamic",
|
| 237 |
+
"fmt": "e4m3",
|
| 238 |
+
"quant_method": "fp8",
|
| 239 |
+
"weight_block_size": [
|
| 240 |
+
128,
|
| 241 |
+
128
|
| 242 |
+
],
|
| 243 |
+
"modules_to_not_convert": [
|
| 244 |
+
"model.layers.59.post_attention_layernorm",
|
| 245 |
+
"model.layers.47.mlp.gate.e_score_correction_bias",
|
| 246 |
+
"model.layers.71.self_attn.kv_a_layernorm",
|
| 247 |
+
"model.layers.46.mlp.gate.e_score_correction_bias",
|
| 248 |
+
"model.layers.30.self_attn.kv_a_layernorm",
|
| 249 |
+
"model.layers.23.self_attn.kv_a_layernorm",
|
| 250 |
+
"model.layers.73.mlp.gate.e_score_correction_bias",
|
| 251 |
+
"model.layers.36.mlp.gate",
|
| 252 |
+
"model.layers.12.self_attn.q_a_layernorm",
|
| 253 |
+
"model.layers.51.mlp.gate",
|
| 254 |
+
"model.layers.47.self_attn.kv_a_layernorm",
|
| 255 |
+
"model.layers.45.self_attn.q_a_layernorm",
|
| 256 |
+
"model.layers.15.self_attn.q_a_layernorm",
|
| 257 |
+
"model.layers.39.input_layernorm",
|
| 258 |
+
"model.layers.50.self_attn.kv_a_layernorm",
|
| 259 |
+
"model.layers.19.post_attention_layernorm",
|
| 260 |
+
"model.layers.71.input_layernorm",
|
| 261 |
+
"model.layers.72.self_attn.kv_a_layernorm",
|
| 262 |
+
"model.layers.70.mlp.gate",
|
| 263 |
+
"model.layers.52.input_layernorm",
|
| 264 |
+
"model.layers.78.self_attn.kv_a_layernorm",
|
| 265 |
+
"model.layers.48.post_attention_layernorm",
|
| 266 |
+
"model.layers.4.self_attn.q_a_layernorm",
|
| 267 |
+
"model.layers.62.self_attn.q_a_layernorm",
|
| 268 |
+
"model.layers.4.post_attention_layernorm",
|
| 269 |
+
"model.layers.38.self_attn.indexers_proj",
|
| 270 |
+
"model.layers.61.mlp.gate.e_score_correction_bias",
|
| 271 |
+
"model.layers.14.mlp.gate.e_score_correction_bias",
|
| 272 |
+
"model.layers.36.self_attn.kv_a_layernorm",
|
| 273 |
+
"model.layers.65.mlp.gate",
|
| 274 |
+
"model.layers.13.self_attn.kv_a_layernorm",
|
| 275 |
+
"model.layers.57.mlp.gate",
|
| 276 |
+
"model.layers.65.self_attn.kv_a_layernorm",
|
| 277 |
+
"model.layers.68.input_layernorm",
|
| 278 |
+
"model.layers.70.self_attn.kv_a_layernorm",
|
| 279 |
+
"model.layers.24.mlp.gate.e_score_correction_bias",
|
| 280 |
+
"model.layers.4.input_layernorm",
|
| 281 |
+
"model.layers.20.input_layernorm",
|
| 282 |
+
"model.layers.65.self_attn.q_a_layernorm",
|
| 283 |
+
"model.layers.38.mlp.gate",
|
| 284 |
+
"model.layers.76.mlp.gate.e_score_correction_bias",
|
| 285 |
+
"model.layers.62.input_layernorm",
|
| 286 |
+
"model.layers.45.input_layernorm",
|
| 287 |
+
"model.layers.68.post_attention_layernorm",
|
| 288 |
+
"model.layers.72.input_layernorm",
|
| 289 |
+
"model.layers.32.mlp.gate.e_score_correction_bias",
|
| 290 |
+
"model.layers.27.input_layernorm",
|
| 291 |
+
"model.layers.16.post_attention_layernorm",
|
| 292 |
+
"model.layers.35.self_attn.q_a_layernorm",
|
| 293 |
+
"model.layers.58.self_attn.indexers_proj",
|
| 294 |
+
"model.layers.3.mlp.gate.e_score_correction_bias",
|
| 295 |
+
"model.layers.49.self_attn.q_a_layernorm",
|
| 296 |
+
"model.layers.66.mlp.gate.e_score_correction_bias",
|
| 297 |
+
"model.layers.10.self_attn.q_a_layernorm",
|
| 298 |
+
"model.layers.51.mlp.gate.e_score_correction_bias",
|
| 299 |
+
"model.layers.64.self_attn.q_a_layernorm",
|
| 300 |
+
"model.layers.70.input_layernorm",
|
| 301 |
+
"model.layers.71.post_attention_layernorm",
|
| 302 |
+
"model.layers.67.post_attention_layernorm",
|
| 303 |
+
"model.layers.6.self_attn.indexer.k_norm",
|
| 304 |
+
"model.layers.3.mlp.gate",
|
| 305 |
+
"model.layers.11.mlp.gate",
|
| 306 |
+
"model.layers.17.input_layernorm",
|
| 307 |
+
"model.layers.13.mlp.gate",
|
| 308 |
+
"model.layers.15.mlp.gate",
|
| 309 |
+
"model.layers.71.self_attn.q_a_layernorm",
|
| 310 |
+
"model.layers.33.mlp.gate.e_score_correction_bias",
|
| 311 |
+
"model.layers.72.mlp.gate.e_score_correction_bias",
|
| 312 |
+
"model.layers.65.post_attention_layernorm",
|
| 313 |
+
"model.layers.78.hnorm",
|
| 314 |
+
"model.layers.40.post_attention_layernorm",
|
| 315 |
+
"model.layers.12.mlp.gate.e_score_correction_bias",
|
| 316 |
+
"model.layers.8.input_layernorm",
|
| 317 |
+
"model.layers.73.mlp.gate",
|
| 318 |
+
"model.layers.11.post_attention_layernorm",
|
| 319 |
+
"model.layers.61.input_layernorm",
|
| 320 |
+
"model.layers.14.self_attn.kv_a_layernorm",
|
| 321 |
+
"model.layers.39.self_attn.kv_a_layernorm",
|
| 322 |
+
"model.layers.70.self_attn.indexer.k_norm",
|
| 323 |
+
"model.layers.45.mlp.gate",
|
| 324 |
+
"model.layers.51.self_attn.q_a_layernorm",
|
| 325 |
+
"model.layers.64.post_attention_layernorm",
|
| 326 |
+
"model.layers.20.post_attention_layernorm",
|
| 327 |
+
"model.layers.66.self_attn.kv_a_layernorm",
|
| 328 |
+
"model.layers.10.self_attn.indexer.k_norm.bias",
|
| 329 |
+
"model.layers.23.input_layernorm",
|
| 330 |
+
"model.layers.38.self_attn.indexer.k_norm",
|
| 331 |
+
"model.layers.19.self_attn.kv_a_layernorm",
|
| 332 |
+
"model.layers.40.self_attn.q_a_layernorm",
|
| 333 |
+
"model.layers.10.self_attn.indexer.k_norm",
|
| 334 |
+
"model.layers.44.post_attention_layernorm",
|
| 335 |
+
"model.layers.70.post_attention_layernorm",
|
| 336 |
+
"model.layers.41.mlp.gate.e_score_correction_bias",
|
| 337 |
+
"model.layers.36.post_attention_layernorm",
|
| 338 |
+
"model.layers.30.self_attn.indexer.k_norm.bias",
|
| 339 |
+
"model.layers.54.self_attn.q_a_layernorm",
|
| 340 |
+
"model.layers.59.mlp.gate",
|
| 341 |
+
"model.layers.69.post_attention_layernorm",
|
| 342 |
+
"lm_head",
|
| 343 |
+
"model.layers.77.self_attn.kv_a_layernorm",
|
| 344 |
+
"model.layers.75.self_attn.q_a_layernorm",
|
| 345 |
+
"model.layers.56.self_attn.q_a_layernorm",
|
| 346 |
+
"model.layers.50.self_attn.indexer.k_norm.bias",
|
| 347 |
+
"model.layers.54.mlp.gate",
|
| 348 |
+
"model.layers.6.self_attn.indexer.k_norm.bias",
|
| 349 |
+
"model.layers.15.input_layernorm",
|
| 350 |
+
"model.layers.29.self_attn.q_a_layernorm",
|
| 351 |
+
"model.layers.54.self_attn.indexers_proj",
|
| 352 |
+
"model.layers.73.self_attn.kv_a_layernorm",
|
| 353 |
+
"model.layers.0.self_attn.indexer.k_norm",
|
| 354 |
+
"model.layers.58.self_attn.indexer.k_norm",
|
| 355 |
+
"model.layers.36.input_layernorm",
|
| 356 |
+
"model.layers.47.self_attn.q_a_layernorm",
|
| 357 |
+
"model.layers.26.mlp.gate.e_score_correction_bias",
|
| 358 |
+
"model.layers.50.input_layernorm",
|
| 359 |
+
"model.layers.16.self_attn.q_a_layernorm",
|
| 360 |
+
"model.layers.19.mlp.gate.e_score_correction_bias",
|
| 361 |
+
"model.layers.78.input_layernorm",
|
| 362 |
+
"model.layers.27.mlp.gate",
|
| 363 |
+
"model.layers.19.self_attn.q_a_layernorm",
|
| 364 |
+
"model.layers.25.input_layernorm",
|
| 365 |
+
"model.layers.33.self_attn.kv_a_layernorm",
|
| 366 |
+
"model.layers.32.mlp.gate",
|
| 367 |
+
"model.layers.61.self_attn.kv_a_layernorm",
|
| 368 |
+
"model.layers.50.self_attn.indexers_proj",
|
| 369 |
+
"model.layers.34.self_attn.indexers_proj",
|
| 370 |
+
"model.layers.4.mlp.gate.e_score_correction_bias",
|
| 371 |
+
"model.layers.76.post_attention_layernorm",
|
| 372 |
+
"model.layers.57.post_attention_layernorm",
|
| 373 |
+
"model.layers.73.post_attention_layernorm",
|
| 374 |
+
"model.layers.68.mlp.gate.e_score_correction_bias",
|
| 375 |
+
"model.layers.34.self_attn.kv_a_layernorm",
|
| 376 |
+
"model.layers.78.enorm",
|
| 377 |
+
"model.layers.30.mlp.gate",
|
| 378 |
+
"model.layers.10.self_attn.indexers_proj",
|
| 379 |
+
"model.layers.34.input_layernorm",
|
| 380 |
+
"model.layers.34.post_attention_layernorm",
|
| 381 |
+
"model.layers.20.mlp.gate.e_score_correction_bias",
|
| 382 |
+
"model.layers.54.self_attn.indexer.k_norm.bias",
|
| 383 |
+
"model.layers.44.mlp.gate",
|
| 384 |
+
"model.layers.25.mlp.gate.e_score_correction_bias",
|
| 385 |
+
"model.layers.26.self_attn.indexers_proj",
|
| 386 |
+
"model.layers.66.self_attn.indexer.k_norm.bias",
|
| 387 |
+
"model.layers.41.post_attention_layernorm",
|
| 388 |
+
"model.layers.51.post_attention_layernorm",
|
| 389 |
+
"model.layers.22.self_attn.indexer.k_norm.bias",
|
| 390 |
+
"model.layers.21.mlp.gate.e_score_correction_bias",
|
| 391 |
+
"model.layers.15.self_attn.kv_a_layernorm",
|
| 392 |
+
"model.layers.25.self_attn.kv_a_layernorm",
|
| 393 |
+
"model.layers.55.mlp.gate",
|
| 394 |
+
"model.layers.11.self_attn.q_a_layernorm",
|
| 395 |
+
"model.layers.72.mlp.gate",
|
| 396 |
+
"model.layers.21.self_attn.kv_a_layernorm",
|
| 397 |
+
"model.layers.59.mlp.gate.e_score_correction_bias",
|
| 398 |
+
"model.layers.2.input_layernorm",
|
| 399 |
+
"model.layers.5.mlp.gate",
|
| 400 |
+
"model.layers.54.self_attn.kv_a_layernorm",
|
| 401 |
+
"model.layers.0.self_attn.indexers_proj",
|
| 402 |
+
"model.layers.71.mlp.gate.e_score_correction_bias",
|
| 403 |
+
"model.layers.6.self_attn.q_a_layernorm",
|
| 404 |
+
"model.layers.14.mlp.gate",
|
| 405 |
+
"model.layers.44.self_attn.kv_a_layernorm",
|
| 406 |
+
"model.layers.61.post_attention_layernorm",
|
| 407 |
+
"model.layers.64.self_attn.kv_a_layernorm",
|
| 408 |
+
"model.layers.1.post_attention_layernorm",
|
| 409 |
+
"model.layers.4.mlp.gate",
|
| 410 |
+
"model.layers.22.post_attention_layernorm",
|
| 411 |
+
"model.layers.21.self_attn.q_a_layernorm",
|
| 412 |
+
"model.layers.74.self_attn.q_a_layernorm",
|
| 413 |
+
"model.layers.18.self_attn.indexers_proj",
|
| 414 |
+
"model.layers.75.input_layernorm",
|
| 415 |
+
"model.layers.70.self_attn.q_a_layernorm",
|
| 416 |
+
"model.layers.22.self_attn.indexer.k_norm",
|
| 417 |
+
"model.layers.42.mlp.gate.e_score_correction_bias",
|
| 418 |
+
"model.layers.5.self_attn.kv_a_layernorm",
|
| 419 |
+
"model.layers.31.mlp.gate.e_score_correction_bias",
|
| 420 |
+
"model.layers.62.self_attn.indexer.k_norm.bias",
|
| 421 |
+
"model.layers.29.post_attention_layernorm",
|
| 422 |
+
"model.layers.6.self_attn.kv_a_layernorm",
|
| 423 |
+
"model.layers.48.input_layernorm",
|
| 424 |
+
"model.layers.74.self_attn.indexer.k_norm.bias",
|
| 425 |
+
"model.layers.7.input_layernorm",
|
| 426 |
+
"model.layers.39.self_attn.q_a_layernorm",
|
| 427 |
+
"model.layers.41.input_layernorm",
|
| 428 |
+
"model.layers.54.self_attn.indexer.k_norm",
|
| 429 |
+
"model.layers.32.self_attn.q_a_layernorm",
|
| 430 |
+
"model.layers.39.mlp.gate.e_score_correction_bias",
|
| 431 |
+
"model.layers.42.input_layernorm",
|
| 432 |
+
"model.layers.43.mlp.gate",
|
| 433 |
+
"model.layers.72.self_attn.q_a_layernorm",
|
| 434 |
+
"model.layers.37.mlp.gate.e_score_correction_bias",
|
| 435 |
+
"model.layers.5.post_attention_layernorm",
|
| 436 |
+
"model.layers.28.mlp.gate",
|
| 437 |
+
"model.layers.2.post_attention_layernorm",
|
| 438 |
+
"model.layers.49.post_attention_layernorm",
|
| 439 |
+
"model.layers.43.self_attn.kv_a_layernorm",
|
| 440 |
+
"model.layers.12.input_layernorm",
|
| 441 |
+
"model.layers.56.mlp.gate",
|
| 442 |
+
"model.layers.5.input_layernorm",
|
| 443 |
+
"model.layers.29.mlp.gate.e_score_correction_bias",
|
| 444 |
+
"model.layers.4.self_attn.kv_a_layernorm",
|
| 445 |
+
"model.layers.20.self_attn.q_a_layernorm",
|
| 446 |
+
"model.layers.59.self_attn.q_a_layernorm",
|
| 447 |
+
"model.layers.68.mlp.gate",
|
| 448 |
+
"model.layers.21.post_attention_layernorm",
|
| 449 |
+
"model.layers.26.self_attn.q_a_layernorm",
|
| 450 |
+
"model.layers.52.self_attn.q_a_layernorm",
|
| 451 |
+
"model.layers.29.self_attn.kv_a_layernorm",
|
| 452 |
+
"model.layers.24.input_layernorm",
|
| 453 |
+
"model.layers.34.self_attn.indexer.k_norm.bias",
|
| 454 |
+
"model.layers.43.input_layernorm",
|
| 455 |
+
"model.layers.27.self_attn.kv_a_layernorm",
|
| 456 |
+
"model.layers.31.input_layernorm",
|
| 457 |
+
"model.layers.69.mlp.gate",
|
| 458 |
+
"model.layers.26.post_attention_layernorm",
|
| 459 |
+
"model.layers.3.self_attn.q_a_layernorm",
|
| 460 |
+
"model.layers.37.post_attention_layernorm",
|
| 461 |
+
"model.layers.52.mlp.gate",
|
| 462 |
+
"model.layers.73.input_layernorm",
|
| 463 |
+
"model.layers.19.input_layernorm",
|
| 464 |
+
"model.layers.13.self_attn.q_a_layernorm",
|
| 465 |
+
"model.layers.76.input_layernorm",
|
| 466 |
+
"model.layers.8.mlp.gate",
|
| 467 |
+
"model.layers.63.post_attention_layernorm",
|
| 468 |
+
"model.layers.58.mlp.gate",
|
| 469 |
+
"model.layers.31.post_attention_layernorm",
|
| 470 |
+
"model.layers.23.mlp.gate.e_score_correction_bias",
|
| 471 |
+
"model.layers.34.self_attn.q_a_layernorm",
|
| 472 |
+
"model.layers.78.self_attn.indexer.k_norm.bias",
|
| 473 |
+
"model.layers.13.mlp.gate.e_score_correction_bias",
|
| 474 |
+
"model.layers.71.mlp.gate",
|
| 475 |
+
"model.layers.14.input_layernorm",
|
| 476 |
+
"model.layers.17.mlp.gate.e_score_correction_bias",
|
| 477 |
+
"model.layers.8.post_attention_layernorm",
|
| 478 |
+
"model.layers.45.self_attn.kv_a_layernorm",
|
| 479 |
+
"model.layers.46.self_attn.indexer.k_norm",
|
| 480 |
+
"model.layers.20.self_attn.kv_a_layernorm",
|
| 481 |
+
"model.layers.78.self_attn.indexer.k_norm",
|
| 482 |
+
"model.layers.37.mlp.gate",
|
| 483 |
+
"model.layers.7.mlp.gate",
|
| 484 |
+
"model.layers.30.mlp.gate.e_score_correction_bias",
|
| 485 |
+
"model.layers.67.mlp.gate",
|
| 486 |
+
"model.layers.73.self_attn.q_a_layernorm",
|
| 487 |
+
"model.layers.10.self_attn.kv_a_layernorm",
|
| 488 |
+
"model.layers.61.mlp.gate",
|
| 489 |
+
"model.layers.22.mlp.gate.e_score_correction_bias",
|
| 490 |
+
"model.layers.58.input_layernorm",
|
| 491 |
+
"model.layers.8.self_attn.kv_a_layernorm",
|
| 492 |
+
"model.layers.33.self_attn.q_a_layernorm",
|
| 493 |
+
"model.layers.55.self_attn.q_a_layernorm",
|
| 494 |
+
"model.layers.32.input_layernorm",
|
| 495 |
+
"model.layers.17.self_attn.kv_a_layernorm",
|
| 496 |
+
"model.layers.46.self_attn.indexer.k_norm.bias",
|
| 497 |
+
"model.layers.11.mlp.gate.e_score_correction_bias",
|
| 498 |
+
"model.layers.9.input_layernorm",
|
| 499 |
+
"model.layers.77.input_layernorm",
|
| 500 |
+
"model.layers.16.self_attn.kv_a_layernorm",
|
| 501 |
+
"model.layers.45.post_attention_layernorm",
|
| 502 |
+
"model.layers.74.mlp.gate",
|
| 503 |
+
"model.layers.68.self_attn.q_a_layernorm",
|
| 504 |
+
"model.layers.55.self_attn.kv_a_layernorm",
|
| 505 |
+
"model.layers.44.input_layernorm",
|
| 506 |
+
"model.layers.60.self_attn.kv_a_layernorm",
|
| 507 |
+
"model.layers.14.post_attention_layernorm",
|
| 508 |
+
"model.layers.62.self_attn.indexers_proj",
|
| 509 |
+
"model.layers.0.self_attn.q_a_layernorm",
|
| 510 |
+
"model.layers.72.post_attention_layernorm",
|
| 511 |
+
"model.layers.78.post_attention_layernorm",
|
| 512 |
+
"model.layers.60.mlp.gate.e_score_correction_bias",
|
| 513 |
+
"model.layers.26.self_attn.indexer.k_norm.bias",
|
| 514 |
+
"model.layers.38.post_attention_layernorm",
|
| 515 |
+
"model.layers.50.post_attention_layernorm",
|
| 516 |
+
"model.layers.28.input_layernorm",
|
| 517 |
+
"model.layers.30.self_attn.indexer.k_norm",
|
| 518 |
+
"model.layers.37.self_attn.q_a_layernorm",
|
| 519 |
+
"model.layers.56.input_layernorm",
|
| 520 |
+
"model.layers.27.post_attention_layernorm",
|
| 521 |
+
"model.layers.74.input_layernorm",
|
| 522 |
+
"model.layers.78.self_attn.q_a_layernorm",
|
| 523 |
+
"model.layers.42.self_attn.q_a_layernorm",
|
| 524 |
+
"model.layers.26.self_attn.indexer.k_norm",
|
| 525 |
+
"model.layers.42.self_attn.kv_a_layernorm",
|
| 526 |
+
"model.layers.1.self_attn.indexer.k_norm",
|
| 527 |
+
"model.layers.39.post_attention_layernorm",
|
| 528 |
+
"model.layers.48.mlp.gate.e_score_correction_bias",
|
| 529 |
+
"model.layers.74.post_attention_layernorm",
|
| 530 |
+
"model.layers.69.self_attn.q_a_layernorm",
|
| 531 |
+
"model.layers.13.input_layernorm",
|
| 532 |
+
"model.layers.74.self_attn.indexer.k_norm",
|
| 533 |
+
"model.layers.29.mlp.gate",
|
| 534 |
+
"model.layers.7.mlp.gate.e_score_correction_bias",
|
| 535 |
+
"model.layers.3.self_attn.kv_a_layernorm",
|
| 536 |
+
"model.layers.35.self_attn.kv_a_layernorm",
|
| 537 |
+
"model.layers.46.self_attn.indexers_proj",
|
| 538 |
+
"model.layers.0.self_attn.kv_a_layernorm",
|
| 539 |
+
"model.layers.43.self_attn.q_a_layernorm",
|
| 540 |
+
"model.norm",
|
| 541 |
+
"model.layers.1.self_attn.kv_a_layernorm",
|
| 542 |
+
"model.layers.38.self_attn.indexer.k_norm.bias",
|
| 543 |
+
"model.layers.30.self_attn.q_a_layernorm",
|
| 544 |
+
"model.layers.0.input_layernorm",
|
| 545 |
+
"model.layers.47.input_layernorm",
|
| 546 |
+
"model.layers.8.self_attn.q_a_layernorm",
|
| 547 |
+
"model.layers.61.self_attn.q_a_layernorm",
|
| 548 |
+
"model.layers.1.self_attn.indexer.k_norm.bias",
|
| 549 |
+
"model.layers.62.mlp.gate.e_score_correction_bias",
|
| 550 |
+
"model.layers.66.self_attn.q_a_layernorm",
|
| 551 |
+
"model.layers.46.self_attn.kv_a_layernorm",
|
| 552 |
+
"model.layers.66.mlp.gate",
|
| 553 |
+
"model.layers.69.input_layernorm",
|
| 554 |
+
"model.layers.69.self_attn.kv_a_layernorm",
|
| 555 |
+
"model.layers.22.input_layernorm",
|
| 556 |
+
"model.layers.30.post_attention_layernorm",
|
| 557 |
+
"model.layers.24.self_attn.q_a_layernorm",
|
| 558 |
+
"model.layers.48.mlp.gate",
|
| 559 |
+
"model.layers.7.self_attn.kv_a_layernorm",
|
| 560 |
+
"model.layers.67.input_layernorm",
|
| 561 |
+
"model.layers.27.self_attn.q_a_layernorm",
|
| 562 |
+
"model.layers.23.mlp.gate",
|
| 563 |
+
"model.layers.45.mlp.gate.e_score_correction_bias",
|
| 564 |
+
"model.layers.76.self_attn.kv_a_layernorm",
|
| 565 |
+
"model.layers.22.self_attn.q_a_layernorm",
|
| 566 |
+
"model.layers.78.mlp.gate",
|
| 567 |
+
"model.layers.9.mlp.gate",
|
| 568 |
+
"model.layers.57.self_attn.q_a_layernorm",
|
| 569 |
+
"model.layers.78.mlp.gate.e_score_correction_bias",
|
| 570 |
+
"model.layers.23.post_attention_layernorm",
|
| 571 |
+
"model.layers.38.self_attn.q_a_layernorm",
|
| 572 |
+
"model.layers.53.mlp.gate",
|
| 573 |
+
"model.layers.77.self_attn.q_a_layernorm",
|
| 574 |
+
"model.layers.62.self_attn.kv_a_layernorm",
|
| 575 |
+
"model.layers.12.mlp.gate",
|
| 576 |
+
"model.layers.34.self_attn.indexer.k_norm",
|
| 577 |
+
"model.layers.46.self_attn.q_a_layernorm",
|
| 578 |
+
"model.layers.36.self_attn.q_a_layernorm",
|
| 579 |
+
"model.layers.58.self_attn.indexer.k_norm.bias",
|
| 580 |
+
"model.layers.60.post_attention_layernorm",
|
| 581 |
+
"model.layers.28.post_attention_layernorm",
|
| 582 |
+
"model.layers.2.self_attn.kv_a_layernorm",
|
| 583 |
+
"model.layers.70.self_attn.indexers_proj",
|
| 584 |
+
"model.layers.16.mlp.gate.e_score_correction_bias",
|
| 585 |
+
"model.layers.2.self_attn.indexer.k_norm",
|
| 586 |
+
"model.layers.31.self_attn.kv_a_layernorm",
|
| 587 |
+
"model.layers.64.input_layernorm",
|
| 588 |
+
"model.layers.6.mlp.gate.e_score_correction_bias",
|
| 589 |
+
"model.layers.50.self_attn.indexer.k_norm",
|
| 590 |
+
"model.layers.58.self_attn.q_a_layernorm",
|
| 591 |
+
"model.layers.57.mlp.gate.e_score_correction_bias",
|
| 592 |
+
"model.layers.13.post_attention_layernorm",
|
| 593 |
+
"model.layers.28.self_attn.kv_a_layernorm",
|
| 594 |
+
"model.layers.31.self_attn.q_a_layernorm",
|
| 595 |
+
"model.layers.1.self_attn.q_a_layernorm",
|
| 596 |
+
"model.layers.17.mlp.gate",
|
| 597 |
+
"model.layers.15.post_attention_layernorm",
|
| 598 |
+
"model.layers.18.self_attn.indexer.k_norm.bias",
|
| 599 |
+
"model.layers.18.self_attn.indexer.k_norm",
|
| 600 |
+
"model.layers.43.mlp.gate.e_score_correction_bias",
|
| 601 |
+
"model.layers.11.input_layernorm",
|
| 602 |
+
"model.layers.9.post_attention_layernorm",
|
| 603 |
+
"model.layers.33.input_layernorm",
|
| 604 |
+
"model.layers.66.input_layernorm",
|
| 605 |
+
"model.layers.78.self_attn.indexers_proj",
|
| 606 |
+
"model.layers.54.post_attention_layernorm",
|
| 607 |
+
"model.layers.53.mlp.gate.e_score_correction_bias",
|
| 608 |
+
"model.layers.5.mlp.gate.e_score_correction_bias",
|
| 609 |
+
"model.layers.19.mlp.gate",
|
| 610 |
+
"model.layers.49.mlp.gate",
|
| 611 |
+
"model.layers.66.self_attn.indexers_proj",
|
| 612 |
+
"model.layers.14.self_attn.indexer.k_norm.bias",
|
| 613 |
+
"model.layers.41.mlp.gate",
|
| 614 |
+
"model.layers.42.self_attn.indexer.k_norm.bias",
|
| 615 |
+
"model.layers.65.input_layernorm",
|
| 616 |
+
"model.layers.75.mlp.gate",
|
| 617 |
+
"model.layers.12.self_attn.kv_a_layernorm",
|
| 618 |
+
"model.layers.53.input_layernorm",
|
| 619 |
+
"model.layers.58.self_attn.kv_a_layernorm",
|
| 620 |
+
"model.layers.60.self_attn.q_a_layernorm",
|
| 621 |
+
"model.layers.6.mlp.gate",
|
| 622 |
+
"model.layers.49.self_attn.kv_a_layernorm",
|
| 623 |
+
"model.layers.16.mlp.gate",
|
| 624 |
+
"model.layers.22.mlp.gate",
|
| 625 |
+
"model.layers.58.post_attention_layernorm",
|
| 626 |
+
"model.layers.15.mlp.gate.e_score_correction_bias",
|
| 627 |
+
"model.layers.26.input_layernorm",
|
| 628 |
+
"model.layers.3.post_attention_layernorm",
|
| 629 |
+
"model.layers.6.input_layernorm",
|
| 630 |
+
"model.layers.22.self_attn.kv_a_layernorm",
|
| 631 |
+
"model.layers.16.input_layernorm",
|
| 632 |
+
"model.layers.33.post_attention_layernorm",
|
| 633 |
+
"model.layers.17.post_attention_layernorm",
|
| 634 |
+
"model.layers.35.mlp.gate",
|
| 635 |
+
"model.layers.56.post_attention_layernorm",
|
| 636 |
+
"model.layers.7.self_attn.q_a_layernorm",
|
| 637 |
+
"model.layers.24.mlp.gate",
|
| 638 |
+
"model.layers.24.post_attention_layernorm",
|
| 639 |
+
"model.layers.36.mlp.gate.e_score_correction_bias",
|
| 640 |
+
"model.layers.56.mlp.gate.e_score_correction_bias",
|
| 641 |
+
"model.layers.32.post_attention_layernorm",
|
| 642 |
+
"model.layers.47.post_attention_layernorm",
|
| 643 |
+
"model.layers.40.mlp.gate",
|
| 644 |
+
"model.layers.41.self_attn.kv_a_layernorm",
|
| 645 |
+
"model.layers.52.self_attn.kv_a_layernorm",
|
| 646 |
+
"model.layers.9.self_attn.kv_a_layernorm",
|
| 647 |
+
"model.layers.77.mlp.gate.e_score_correction_bias",
|
| 648 |
+
"model.layers.78.shared_head.norm",
|
| 649 |
+
"model.layers.53.post_attention_layernorm",
|
| 650 |
+
"model.layers.63.self_attn.kv_a_layernorm",
|
| 651 |
+
"model.layers.48.self_attn.kv_a_layernorm",
|
| 652 |
+
"model.layers.28.mlp.gate.e_score_correction_bias",
|
| 653 |
+
"model.layers.8.mlp.gate.e_score_correction_bias",
|
| 654 |
+
"model.layers.52.mlp.gate.e_score_correction_bias",
|
| 655 |
+
"model.layers.52.post_attention_layernorm",
|
| 656 |
+
"model.layers.18.self_attn.q_a_layernorm",
|
| 657 |
+
"model.layers.50.mlp.gate.e_score_correction_bias",
|
| 658 |
+
"model.layers.64.mlp.gate.e_score_correction_bias",
|
| 659 |
+
"model.layers.38.mlp.gate.e_score_correction_bias",
|
| 660 |
+
"model.layers.59.input_layernorm",
|
| 661 |
+
"model.layers.21.input_layernorm",
|
| 662 |
+
"model.layers.39.mlp.gate",
|
| 663 |
+
"model.layers.77.post_attention_layernorm",
|
| 664 |
+
"model.layers.76.self_attn.q_a_layernorm",
|
| 665 |
+
"model.layers.12.post_attention_layernorm",
|
| 666 |
+
"model.layers.43.post_attention_layernorm",
|
| 667 |
+
"model.layers.35.input_layernorm",
|
| 668 |
+
"model.layers.62.mlp.gate",
|
| 669 |
+
"model.layers.38.self_attn.kv_a_layernorm",
|
| 670 |
+
"model.layers.63.mlp.gate",
|
| 671 |
+
"model.layers.27.mlp.gate.e_score_correction_bias",
|
| 672 |
+
"model.layers.18.mlp.gate",
|
| 673 |
+
"model.layers.25.post_attention_layernorm",
|
| 674 |
+
"model.layers.55.mlp.gate.e_score_correction_bias",
|
| 675 |
+
"model.layers.66.self_attn.indexer.k_norm",
|
| 676 |
+
"model.layers.48.self_attn.q_a_layernorm",
|
| 677 |
+
"model.layers.78.eh_proj",
|
| 678 |
+
"model.layers.77.mlp.gate",
|
| 679 |
+
"model.layers.6.self_attn.indexers_proj",
|
| 680 |
+
"model.layers.14.self_attn.q_a_layernorm",
|
| 681 |
+
"model.layers.2.self_attn.q_a_layernorm",
|
| 682 |
+
"model.layers.42.mlp.gate",
|
| 683 |
+
"model.layers.54.mlp.gate.e_score_correction_bias",
|
| 684 |
+
"model.layers.60.input_layernorm",
|
| 685 |
+
"model.layers.40.self_attn.kv_a_layernorm",
|
| 686 |
+
"model.layers.51.self_attn.kv_a_layernorm",
|
| 687 |
+
"model.layers.18.input_layernorm",
|
| 688 |
+
"model.layers.30.self_attn.indexers_proj",
|
| 689 |
+
"model.layers.53.self_attn.kv_a_layernorm",
|
| 690 |
+
"model.layers.6.post_attention_layernorm",
|
| 691 |
+
"model.layers.70.self_attn.indexer.k_norm.bias",
|
| 692 |
+
"model.layers.54.input_layernorm",
|
| 693 |
+
"model.layers.41.self_attn.q_a_layernorm",
|
| 694 |
+
"model.layers.18.mlp.gate.e_score_correction_bias",
|
| 695 |
+
"model.layers.34.mlp.gate.e_score_correction_bias",
|
| 696 |
+
"model.layers.47.mlp.gate",
|
| 697 |
+
"model.layers.46.mlp.gate",
|
| 698 |
+
"model.layers.25.self_attn.q_a_layernorm",
|
| 699 |
+
"model.layers.63.mlp.gate.e_score_correction_bias",
|
| 700 |
+
"model.layers.66.post_attention_layernorm",
|
| 701 |
+
"model.layers.76.mlp.gate",
|
| 702 |
+
"model.embed_tokens",
|
| 703 |
+
"model.layers.67.self_attn.kv_a_layernorm",
|
| 704 |
+
"model.layers.67.mlp.gate.e_score_correction_bias",
|
| 705 |
+
"model.layers.26.self_attn.kv_a_layernorm",
|
| 706 |
+
"model.layers.31.mlp.gate",
|
| 707 |
+
"model.layers.18.self_attn.kv_a_layernorm",
|
| 708 |
+
"model.layers.34.mlp.gate",
|
| 709 |
+
"model.layers.55.post_attention_layernorm",
|
| 710 |
+
"model.layers.30.input_layernorm",
|
| 711 |
+
"model.layers.50.mlp.gate",
|
| 712 |
+
"model.layers.63.input_layernorm",
|
| 713 |
+
"model.layers.11.self_attn.kv_a_layernorm",
|
| 714 |
+
"model.layers.24.self_attn.kv_a_layernorm",
|
| 715 |
+
"model.layers.75.mlp.gate.e_score_correction_bias",
|
| 716 |
+
"model.layers.10.post_attention_layernorm",
|
| 717 |
+
"model.layers.1.self_attn.indexers_proj",
|
| 718 |
+
"model.layers.42.self_attn.indexers_proj",
|
| 719 |
+
"model.layers.46.input_layernorm",
|
| 720 |
+
"model.layers.23.self_attn.q_a_layernorm",
|
| 721 |
+
"model.layers.63.self_attn.q_a_layernorm",
|
| 722 |
+
"model.layers.14.self_attn.indexer.k_norm",
|
| 723 |
+
"model.layers.14.self_attn.indexers_proj",
|
| 724 |
+
"model.layers.60.mlp.gate",
|
| 725 |
+
"model.layers.70.mlp.gate.e_score_correction_bias",
|
| 726 |
+
"model.layers.53.self_attn.q_a_layernorm",
|
| 727 |
+
"model.layers.0.post_attention_layernorm",
|
| 728 |
+
"model.layers.35.mlp.gate.e_score_correction_bias",
|
| 729 |
+
"model.layers.75.self_attn.kv_a_layernorm",
|
| 730 |
+
"model.layers.1.input_layernorm",
|
| 731 |
+
"model.layers.42.post_attention_layernorm",
|
| 732 |
+
"model.layers.37.input_layernorm",
|
| 733 |
+
"model.layers.9.mlp.gate.e_score_correction_bias",
|
| 734 |
+
"model.layers.46.post_attention_layernorm",
|
| 735 |
+
"model.layers.26.mlp.gate",
|
| 736 |
+
"model.layers.65.mlp.gate.e_score_correction_bias",
|
| 737 |
+
"model.layers.67.self_attn.q_a_layernorm",
|
| 738 |
+
"model.layers.44.mlp.gate.e_score_correction_bias",
|
| 739 |
+
"model.layers.33.mlp.gate",
|
| 740 |
+
"model.layers.29.input_layernorm",
|
| 741 |
+
"model.layers.18.post_attention_layernorm",
|
| 742 |
+
"model.layers.75.post_attention_layernorm",
|
| 743 |
+
"model.layers.44.self_attn.q_a_layernorm",
|
| 744 |
+
"model.layers.10.mlp.gate",
|
| 745 |
+
"model.layers.21.mlp.gate",
|
| 746 |
+
"model.layers.58.mlp.gate.e_score_correction_bias",
|
| 747 |
+
"model.layers.7.post_attention_layernorm",
|
| 748 |
+
"model.layers.50.self_attn.q_a_layernorm",
|
| 749 |
+
"model.layers.9.self_attn.q_a_layernorm",
|
| 750 |
+
"model.layers.2.self_attn.indexers_proj",
|
| 751 |
+
"model.layers.0.self_attn.indexer.k_norm.bias",
|
| 752 |
+
"model.layers.74.self_attn.kv_a_layernorm",
|
| 753 |
+
"model.layers.74.mlp.gate.e_score_correction_bias",
|
| 754 |
+
"model.layers.49.mlp.gate.e_score_correction_bias",
|
| 755 |
+
"model.layers.62.post_attention_layernorm",
|
| 756 |
+
"model.layers.57.self_attn.kv_a_layernorm",
|
| 757 |
+
"model.layers.40.input_layernorm",
|
| 758 |
+
"model.layers.69.mlp.gate.e_score_correction_bias",
|
| 759 |
+
"model.layers.35.post_attention_layernorm",
|
| 760 |
+
"model.layers.74.self_attn.indexers_proj",
|
| 761 |
+
"model.layers.55.input_layernorm",
|
| 762 |
+
"model.layers.49.input_layernorm",
|
| 763 |
+
"model.layers.40.mlp.gate.e_score_correction_bias",
|
| 764 |
+
"model.layers.25.mlp.gate",
|
| 765 |
+
"model.layers.2.self_attn.indexer.k_norm.bias",
|
| 766 |
+
"model.layers.68.self_attn.kv_a_layernorm",
|
| 767 |
+
"model.layers.51.input_layernorm",
|
| 768 |
+
"model.layers.22.self_attn.indexers_proj",
|
| 769 |
+
"model.layers.10.input_layernorm",
|
| 770 |
+
"model.layers.42.self_attn.indexer.k_norm",
|
| 771 |
+
"model.layers.56.self_attn.kv_a_layernorm",
|
| 772 |
+
"model.layers.17.self_attn.q_a_layernorm",
|
| 773 |
+
"model.layers.59.self_attn.kv_a_layernorm",
|
| 774 |
+
"model.layers.3.input_layernorm",
|
| 775 |
+
"model.layers.57.input_layernorm",
|
| 776 |
+
"model.layers.38.input_layernorm",
|
| 777 |
+
"model.layers.62.self_attn.indexer.k_norm",
|
| 778 |
+
"model.layers.20.mlp.gate",
|
| 779 |
+
"model.layers.64.mlp.gate",
|
| 780 |
+
"model.layers.28.self_attn.q_a_layernorm",
|
| 781 |
+
"model.layers.32.self_attn.kv_a_layernorm",
|
| 782 |
+
"model.layers.10.mlp.gate.e_score_correction_bias",
|
| 783 |
+
"model.layers.5.self_attn.q_a_layernorm",
|
| 784 |
+
"model.layers.37.self_attn.kv_a_layernorm"
|
| 785 |
+
]
|
| 786 |
+
},
|
| 787 |
+
"mtp_bits": 16,
|
| 788 |
+
"scope": "glm53_routed_experts_only",
|
| 789 |
+
"keys_profile": "routed-experts-3bpw-mul1; attn/dense/shared/head bf16",
|
| 790 |
+
"keys_source": "zai-org/GLM-5.3 (FP8) via exllamav3 1.4.5 + keys encode farm"
|
| 791 |
+
}
|
| 792 |
+
}
|
generation_config.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_from_model_config": true,
|
| 3 |
+
"eos_token_id": [
|
| 4 |
+
154820,
|
| 5 |
+
154827,
|
| 6 |
+
154829
|
| 7 |
+
],
|
| 8 |
+
"pad_token_id": 154820,
|
| 9 |
+
"temperature": 1.0,
|
| 10 |
+
"top_p": 0.95,
|
| 11 |
+
"transformers_version": "5.12.0"
|
| 12 |
+
}
|
model-00001-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fec34deec06cd68b9ced94494ab653836c7e645bf98dadee46cdf1e98432ca2b
|
| 3 |
+
size 8357250403
|
model-00005-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:966889d2a98cede970ea40b1d006ebcd40d24e372b81ed1cac6b807340b645b2
|
| 3 |
+
size 8111403478
|
model-00009-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cdf80c8975f78f82ef7200be56f0ef342cc534b01b5d4c0ba94b9483a8ad46af
|
| 3 |
+
size 8111403478
|
model-00010-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2b383a7f2c463d59745dbcaf9883d656328550611f84475ce83e035b848c37db
|
| 3 |
+
size 8092659062
|
model-00011-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6488dcf79d89daee948e4ea43c54e8eb4ec23857f12a1a874c04870f7a5700a2
|
| 3 |
+
size 8111403478
|
model-00013-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ed1ba921b27c719cf09cefa0f80a92deee6633964bd6990612a142e2378cb6f2
|
| 3 |
+
size 8111403478
|
model-00014-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:92ecb399799bb0d1d11c8b4469f80c4f84cf63d086ce5d38dc9e7bf65a97819c
|
| 3 |
+
size 8092659062
|
model-00019-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7c12183bb06e161c2df208eb208a80729d3034ab1c02607e5ebd44384709096c
|
| 3 |
+
size 8111403478
|
model-00020-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fe76908fb0495d600f2cca59d1d6802d3529d74959e9685712a9eba025817c40
|
| 3 |
+
size 8092659062
|
model-00021-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a8f4340829f726f92fdb5b10823ffe0f4a4578db1d73b8eeefdeeff45b3e3261
|
| 3 |
+
size 8111403478
|
model-00023-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5ba92d7e5210f9565ac587f776cbaed146febbf6b9ce1f2de9b835c14a6b6cf3
|
| 3 |
+
size 8111403478
|
model-00024-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dca97644a9978c7f42b498bb84df2bc81af17f9b55000781a26d992bf2c2052c
|
| 3 |
+
size 8092659062
|
model-00030-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5f0c68eab43e468fa8ae8607a069ecb02bca8b670645322cbe56dacd3eb38916
|
| 3 |
+
size 8092659062
|
model-00031-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4ccc7fb59c8f4a3c81b3d4d64c6d0880aee12e1748c750f351aa8c80410dc581
|
| 3 |
+
size 8111403478
|
model-00032-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:98525b8f4757949a7e60ec075c5dec20b2d2208f220a673b3db8c89d31e1823d
|
| 3 |
+
size 8092659062
|
model-00034-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:72777c7367f3973d9d10ca88e8fe95896d65342bc8a27511f057079f67599911
|
| 3 |
+
size 8092659062
|
model-00035-of-00041.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e8f21f16c2a599412edecfaee695d48cb0c2ac7ffa6b18fc37941f30137aa7b3
|
| 3 |
+
size 8111403478
|
quant/build.sh
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# exllamav3 1.4.5 native sm_121a build inside the Mia image (run via docker exec)
|
| 3 |
+
set -x
|
| 4 |
+
CU=/usr/local/lib/python3.12/dist-packages/nvidia/cu13/include
|
| 5 |
+
for h in $CU/*.h; do b=$(basename $h); [ -e /usr/local/cuda/include/$b ] || ln -s $h /usr/local/cuda/include/$b; done
|
| 6 |
+
cd /work/exllamav3
|
| 7 |
+
unset EXLLAMA_NOCOMPILE
|
| 8 |
+
export TORCH_CUDA_ARCH_LIST=12.1a MAX_JOBS=20
|
| 9 |
+
pip install --no-deps --no-build-isolation -v . 2>&1 | tail -40
|
| 10 |
+
echo BUILD_EXIT=${PIPESTATUS[0]}
|
| 11 |
+
ls -la /usr/local/lib/python3.12/dist-packages/exllamav3_ext*.so
|
| 12 |
+
python3 -c "import exllamav3, exllamav3.ext; print(\"IMPORT_OK\", exllamav3.__version__)"
|
| 13 |
+
python3 -c "from exllamav3.architecture.glm_moe_dsa import GlmMoeDsaConfig; print(\"ARCH_OK\")"
|
| 14 |
+
echo BUILD_DONE
|
quant/exl3_farm.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""EXL3 encode farm: run exllamav3's trellis quantization of individual linears on remote GPUs.
|
| 3 |
+
|
| 4 |
+
The converter's layer chain is sequential (each layer is calibrated on the quantized output of the
|
| 5 |
+
previous ones), but inside a layer every expert matrix is encoded independently given its Hessian.
|
| 6 |
+
This module lets the master (running convert.py) ship (weights, finalized H_data, quant_args) for a
|
| 7 |
+
group of linears to a worker GPU on another node and get back the packed EXL3 tensors.
|
| 8 |
+
|
| 9 |
+
Worker: python3 exl3_farm.py serve --port 5599
|
| 10 |
+
Client: quantize_remote(host, port, weights, H_datas, quant_args_list) -> (results, quant_args_list)
|
| 11 |
+
|
| 12 |
+
Wire format: 8-byte little-endian length + torch.save payload (pickled dict of CPU tensors).
|
| 13 |
+
"""
|
| 14 |
+
import argparse, io, socket, struct, sys, time, threading, traceback
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
|
| 18 |
+
MAGIC_OK = b"OK"
|
| 19 |
+
MAGIC_ERR = b"ER"
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _send(sock: socket.socket, obj) -> None:
|
| 23 |
+
buf = io.BytesIO()
|
| 24 |
+
torch.save(obj, buf)
|
| 25 |
+
data = buf.getvalue()
|
| 26 |
+
sock.sendall(struct.pack("<Q", len(data)))
|
| 27 |
+
sock.sendall(data)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _recv_exact(sock: socket.socket, n: int) -> bytes:
|
| 31 |
+
chunks = []
|
| 32 |
+
got = 0
|
| 33 |
+
while got < n:
|
| 34 |
+
c = sock.recv(min(n - got, 64 << 20))
|
| 35 |
+
if not c:
|
| 36 |
+
raise ConnectionError("socket closed")
|
| 37 |
+
chunks.append(c)
|
| 38 |
+
got += len(c)
|
| 39 |
+
return b"".join(chunks)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _recv(sock: socket.socket, map_location="cpu"):
|
| 43 |
+
n = struct.unpack("<Q", _recv_exact(sock, 8))[0]
|
| 44 |
+
data = _recv_exact(sock, n)
|
| 45 |
+
return torch.load(io.BytesIO(data), map_location=map_location, weights_only=False)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
# ------------------------------------------------------------------ client (master side)
|
| 49 |
+
_H_KEYS = ("H", "first_key", "count", "finalized", "num_total", "inf_nan", "su", "L", "diag", "q_fallback")
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _export_H(hd: dict) -> dict:
|
| 53 |
+
out = {}
|
| 54 |
+
for k in _H_KEYS:
|
| 55 |
+
v = hd.get(k)
|
| 56 |
+
if isinstance(v, torch.Tensor):
|
| 57 |
+
v = v.detach().to("cpu")
|
| 58 |
+
out[k] = v
|
| 59 |
+
return out
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def quantize_remote(host: str, port: int, weights, H_datas, quant_args_list, timeout=3600):
|
| 63 |
+
"""Quantize a group of linears on a remote worker. weights: list of (in,out) fp16/fp32 tensors
|
| 64 |
+
(CPU ok); H_datas: list of FINALIZED H_data dicts (may repeat the same object); returns
|
| 65 |
+
([(proxy_err, out_tensors_cpu), ...], updated quant_args_list)."""
|
| 66 |
+
pool, idx = [], []
|
| 67 |
+
for hd in H_datas:
|
| 68 |
+
for i, p in enumerate(pool):
|
| 69 |
+
if p is hd:
|
| 70 |
+
idx.append(i)
|
| 71 |
+
break
|
| 72 |
+
else:
|
| 73 |
+
pool.append(hd)
|
| 74 |
+
idx.append(len(pool) - 1)
|
| 75 |
+
req = {
|
| 76 |
+
"weights": [w.detach().to("cpu") for w in weights],
|
| 77 |
+
"H_pool": [_export_H(hd) for hd in pool],
|
| 78 |
+
"H_idx": idx,
|
| 79 |
+
"quant_args_list": [{k: v for k, v in qa.items() if k != "devices"} for qa in quant_args_list],
|
| 80 |
+
}
|
| 81 |
+
sock = socket.create_connection((host, port), timeout=timeout)
|
| 82 |
+
try:
|
| 83 |
+
sock.settimeout(timeout)
|
| 84 |
+
_send(sock, req)
|
| 85 |
+
rep = _recv(sock, map_location="cpu")
|
| 86 |
+
finally:
|
| 87 |
+
sock.close()
|
| 88 |
+
if rep.get("status") != "ok":
|
| 89 |
+
raise RuntimeError(f"remote {host}:{port} failed: {rep.get('error')}")
|
| 90 |
+
for qa, nqa in zip(quant_args_list, rep["quant_args_list"]):
|
| 91 |
+
for k, v in nqa.items():
|
| 92 |
+
if k != "devices":
|
| 93 |
+
qa[k] = v
|
| 94 |
+
return rep["results"], quant_args_list
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
# ------------------------------------------------------------------ server (worker side)
|
| 98 |
+
def _handle(conn: socket.socket, device: torch.device, log):
|
| 99 |
+
from exllamav3.modules.quant.exl3_lib.quantize import quantize_exl3, quantize_exl3_batch
|
| 100 |
+
|
| 101 |
+
t0 = time.time()
|
| 102 |
+
req = _recv(conn, map_location="cpu")
|
| 103 |
+
t1 = time.time()
|
| 104 |
+
try:
|
| 105 |
+
with torch.inference_mode():
|
| 106 |
+
pool = req["H_pool"]
|
| 107 |
+
for hd in pool:
|
| 108 |
+
for k, v in list(hd.items()):
|
| 109 |
+
if isinstance(v, torch.Tensor):
|
| 110 |
+
hd[k] = v.to(device)
|
| 111 |
+
hd["device"] = device
|
| 112 |
+
H_datas = [pool[i] for i in req["H_idx"]]
|
| 113 |
+
weights = req["weights"]
|
| 114 |
+
qal = req["quant_args_list"]
|
| 115 |
+
for qa in qal:
|
| 116 |
+
qa["devices"] = [device.index or 0]
|
| 117 |
+
qa["device_ratios"] = None
|
| 118 |
+
qa["debug_dir"] = "/tmp/exl3-farm-debug"
|
| 119 |
+
if len(weights) > 1:
|
| 120 |
+
results = quantize_exl3_batch(weights, H_datas, qal)
|
| 121 |
+
else:
|
| 122 |
+
_, pe, out = quantize_exl3(weights[0].to(device).float(), H_datas[0], qal[0], False)
|
| 123 |
+
results = [(pe, out)]
|
| 124 |
+
cpu_results = []
|
| 125 |
+
for pe, out in results:
|
| 126 |
+
cpu_results.append((float(pe), {k: v.detach().to("cpu") for k, v in out.items() if v is not None}))
|
| 127 |
+
rep = {
|
| 128 |
+
"status": "ok",
|
| 129 |
+
"results": cpu_results,
|
| 130 |
+
"quant_args_list": [{k: (v.detach().to("cpu") if isinstance(v, torch.Tensor) else v)
|
| 131 |
+
for k, v in qa.items() if k not in ("devices",)} for qa in qal],
|
| 132 |
+
}
|
| 133 |
+
torch.cuda.synchronize(device)
|
| 134 |
+
except Exception as e: # noqa: BLE001
|
| 135 |
+
rep = {"status": "error", "error": f"{e!r}\n{traceback.format_exc()}"}
|
| 136 |
+
log(f"ERROR: {e!r}")
|
| 137 |
+
t2 = time.time()
|
| 138 |
+
_send(conn, rep)
|
| 139 |
+
n = len(req["weights"])
|
| 140 |
+
log(f"group of {n}: recv {t1 - t0:.2f}s quant {t2 - t1:.2f}s send {time.time() - t2:.2f}s")
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def serve(port: int, device_index: int, concurrency: int):
|
| 144 |
+
device = torch.device(f"cuda:{device_index}")
|
| 145 |
+
torch.cuda.set_device(device)
|
| 146 |
+
lock = threading.Semaphore(concurrency)
|
| 147 |
+
|
| 148 |
+
def log(msg):
|
| 149 |
+
print(f"[farm {time.strftime('%H:%M:%S')}] {msg}", flush=True)
|
| 150 |
+
|
| 151 |
+
# warm the kernels once so the first real request does not pay JIT/allocation
|
| 152 |
+
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
| 153 |
+
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 154 |
+
srv.bind(("0.0.0.0", port))
|
| 155 |
+
srv.listen(8)
|
| 156 |
+
log(f"listening on :{port} device={device} concurrency={concurrency}")
|
| 157 |
+
|
| 158 |
+
def worker(conn):
|
| 159 |
+
with lock:
|
| 160 |
+
try:
|
| 161 |
+
_handle(conn, device, log)
|
| 162 |
+
except Exception as e: # noqa: BLE001
|
| 163 |
+
log(f"connection failed: {e!r}")
|
| 164 |
+
finally:
|
| 165 |
+
conn.close()
|
| 166 |
+
|
| 167 |
+
while True:
|
| 168 |
+
conn, addr = srv.accept()
|
| 169 |
+
conn.settimeout(3600)
|
| 170 |
+
threading.Thread(target=worker, args=(conn,), daemon=True).start()
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
if __name__ == "__main__":
|
| 174 |
+
ap = argparse.ArgumentParser()
|
| 175 |
+
sub = ap.add_subparsers(dest="cmd")
|
| 176 |
+
s = sub.add_parser("serve")
|
| 177 |
+
s.add_argument("--port", type=int, default=5599)
|
| 178 |
+
s.add_argument("--device", type=int, default=0)
|
| 179 |
+
s.add_argument("--concurrency", type=int, default=1)
|
| 180 |
+
a = ap.parse_args()
|
| 181 |
+
if a.cmd == "serve":
|
| 182 |
+
serve(a.port, a.device, a.concurrency)
|
| 183 |
+
else:
|
| 184 |
+
ap.print_help()
|
| 185 |
+
sys.exit(1)
|
quant/make_recipe.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Generate the exllamav3 per-tensor recipe for a Mia-native GLM-5.3 EXL3 profile.
|
| 3 |
+
|
| 4 |
+
Profile (matches MiaAI-Lab GLM-5.3-Flash EXL3 scope=glm53_routed_experts_only):
|
| 5 |
+
* routed experts gate/up/down (layers first_k_dense..77) -> EXPERT_BITS (uniform, mcg codebook)
|
| 6 |
+
* everything else that exllamav3 budgets (MLA attention projections, indexer wq_b,
|
| 7 |
+
dense MLP layers 0-2, shared experts) -> 16 (stored unquantized)
|
| 8 |
+
* lm_head -> --head_bits 16, MTP layer 78 -> --mtp_bits 16 (not in the recipe)
|
| 9 |
+
Usage: make_recipe.py <model.safetensors.index.json> <config.json> <out.yaml> [expert_bits]
|
| 10 |
+
"""
|
| 11 |
+
import json, re, sys
|
| 12 |
+
|
| 13 |
+
index, config, out = sys.argv[1:4]
|
| 14 |
+
EXPERT_BITS = int(sys.argv[4]) if len(sys.argv) > 4 else 3
|
| 15 |
+
|
| 16 |
+
cfg = json.load(open(config))
|
| 17 |
+
n_layers = cfg["num_hidden_layers"]
|
| 18 |
+
first_dense = cfg.get("first_k_dense_replace", 3)
|
| 19 |
+
wm = json.load(open(index))["weight_map"]
|
| 20 |
+
|
| 21 |
+
NONEXPERT = {
|
| 22 |
+
"self_attn.q_a_proj", "self_attn.q_b_proj", "self_attn.kv_a_proj_with_mqa",
|
| 23 |
+
"self_attn.o_proj", "self_attn.indexer.wq_b",
|
| 24 |
+
"mlp.gate_proj", "mlp.up_proj", "mlp.down_proj",
|
| 25 |
+
"mlp.shared_experts.gate_proj", "mlp.shared_experts.up_proj", "mlp.shared_experts.down_proj",
|
| 26 |
+
}
|
| 27 |
+
EXPERT_RE = re.compile(r"mlp\.experts\.(\d+)\.(gate|up|down)_proj$")
|
| 28 |
+
|
| 29 |
+
tensors = {}
|
| 30 |
+
for k in wm:
|
| 31 |
+
if not k.endswith(".weight"):
|
| 32 |
+
continue
|
| 33 |
+
m = re.match(r"model\.layers\.(\d+)\.(.*)\.weight$", k)
|
| 34 |
+
if not m:
|
| 35 |
+
continue
|
| 36 |
+
layer, rest = int(m.group(1)), m.group(2)
|
| 37 |
+
if layer >= n_layers: # MTP layer(s): governed by --mtp_bits
|
| 38 |
+
continue
|
| 39 |
+
key = f"model.layers.{layer}.{rest}"
|
| 40 |
+
if EXPERT_RE.match(rest):
|
| 41 |
+
assert layer >= first_dense, key
|
| 42 |
+
tensors[key] = EXPERT_BITS
|
| 43 |
+
elif rest in NONEXPERT:
|
| 44 |
+
tensors[key] = 16
|
| 45 |
+
|
| 46 |
+
n_exp = sum(1 for v in tensors.values() if v == EXPERT_BITS)
|
| 47 |
+
n_16 = sum(1 for v in tensors.values() if v == 16)
|
| 48 |
+
with open(out, "w") as f:
|
| 49 |
+
f.write(f"# GLM-5.3 (753B) Mia-native EXL3 recipe: routed experts {EXPERT_BITS}b mcg, rest bf16\n")
|
| 50 |
+
f.write(f"target_bpw: {EXPERT_BITS}\nhead_bits: 16\ntensors:\n")
|
| 51 |
+
for k in sorted(tensors, key=lambda s: (int(s.split('.')[2]), s)):
|
| 52 |
+
f.write(f' "{k}": {tensors[k]}\n')
|
| 53 |
+
print(f"recipe: {len(tensors)} tensors experts@{EXPERT_BITS}b={n_exp} unquantized(16)={n_16} -> {out}")
|
quant/orchestrate.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Rolling-shard driver for exllamav3 convert.py on a disk-limited node (stdlib only, runs on host).
|
| 3 |
+
|
| 4 |
+
Why: the FP8 source (zai-org/GLM-5.3, 141 shards, 756 GB) does not fit next to the ~300 GB work
|
| 5 |
+
dir and ~300 GB output. exllamav3 walks modules in order and checkpoints per module, so we:
|
| 6 |
+
1. keep a rolling window of source shards (download ahead in a thread),
|
| 7 |
+
2. run convert.py in a fresh container per step with --max_module so it stops after layer L,
|
| 8 |
+
3. between steps, rewrite finished shards dropping only `model.layers.{L}.mlp.experts.*`
|
| 9 |
+
(norms/attn/shared/router stay, compile needs them), then advance.
|
| 10 |
+
Every convert process rescans the source dir at start, so rewritten shards are always consistent.
|
| 11 |
+
|
| 12 |
+
Layout (ROOT): src/ (rolling source), work/ (exllamav3 work dir), out/ (final), runs/ (logs),
|
| 13 |
+
state.json (progress), shards.json (HF sizes + sha256).
|
| 14 |
+
"""
|
| 15 |
+
import json, os, re, sys, time, struct, subprocess, threading, hashlib, shutil, urllib.request
|
| 16 |
+
|
| 17 |
+
ROOT = os.environ.get("ROOT", "/home/keyspark/glm53-full-exl3")
|
| 18 |
+
SRC, WORK, OUT, RUNS = (os.path.join(ROOT, d) for d in ("src", "work", "out", "runs"))
|
| 19 |
+
STUB = os.path.join(ROOT, "src-stub")
|
| 20 |
+
STATE = os.path.join(ROOT, "state.json")
|
| 21 |
+
LOG = os.path.join(ROOT, "orchestrate.log")
|
| 22 |
+
REPO = "zai-org/GLM-5.3"
|
| 23 |
+
HF = f"https://huggingface.co/{REPO}/resolve/main/"
|
| 24 |
+
IMAGE = os.environ.get("IMAGE", "glm53-exl3-quant:1.4.5")
|
| 25 |
+
CONTAINER = "glm53-exl3-convert"
|
| 26 |
+
RECIPE = os.path.join(ROOT, "recipe.yaml")
|
| 27 |
+
EXPERT_BITS = int(os.environ.get("EXPERT_BITS", "3"))
|
| 28 |
+
LAYERS_PER_RUN = int(os.environ.get("LAYERS_PER_RUN", "2"))
|
| 29 |
+
WINDOW = int(os.environ.get("WINDOW", "5")) # layers downloaded ahead of the current one
|
| 30 |
+
MIN_FREE_GB = int(os.environ.get("MIN_FREE_GB", "80"))
|
| 31 |
+
MEM_CAP = os.environ.get("MEM_CAP", "112g")
|
| 32 |
+
NUM_LAYERS = 78
|
| 33 |
+
MTP_LAYER = 78
|
| 34 |
+
|
| 35 |
+
def log(msg):
|
| 36 |
+
line = f"[{time.strftime('%m-%d %H:%M:%S')}] {msg}"
|
| 37 |
+
print(line, flush=True)
|
| 38 |
+
with open(LOG, "a") as f:
|
| 39 |
+
f.write(line + "\n")
|
| 40 |
+
|
| 41 |
+
def free_gb(path=ROOT):
|
| 42 |
+
st = os.statvfs(path)
|
| 43 |
+
return st.f_bavail * st.f_frsize / 1e9
|
| 44 |
+
|
| 45 |
+
# ---------------------------------------------------------------- index / shard maps
|
| 46 |
+
index = json.load(open(os.path.join(STUB, "model.safetensors.index.json")))["weight_map"]
|
| 47 |
+
layer_shards = {} # layer -> set(shard)
|
| 48 |
+
shard_layers = {} # shard -> set(layer)
|
| 49 |
+
special = set() # shards holding embed/lm_head/norm
|
| 50 |
+
for k, s in index.items():
|
| 51 |
+
m = re.match(r"model\.layers\.(\d+)\.", k)
|
| 52 |
+
if m:
|
| 53 |
+
L = int(m.group(1))
|
| 54 |
+
layer_shards.setdefault(L, set()).add(s)
|
| 55 |
+
shard_layers.setdefault(s, set()).add(L)
|
| 56 |
+
else:
|
| 57 |
+
special.add(s)
|
| 58 |
+
shard_layers.setdefault(s, set())
|
| 59 |
+
ALL_SHARDS = sorted(shard_layers)
|
| 60 |
+
PERMANENT = set(special) | layer_shards[MTP_LAYER] # eh_proj must exist at every resume
|
| 61 |
+
|
| 62 |
+
def load_state():
|
| 63 |
+
if os.path.exists(STATE):
|
| 64 |
+
return json.load(open(STATE))
|
| 65 |
+
return {"done_layers": [], "dropped": {}, "started": False, "finished": False}
|
| 66 |
+
|
| 67 |
+
def save_state(st):
|
| 68 |
+
tmp = STATE + ".tmp"
|
| 69 |
+
json.dump(st, open(tmp, "w"), indent=1)
|
| 70 |
+
os.replace(tmp, STATE)
|
| 71 |
+
|
| 72 |
+
# ---------------------------------------------------------------- HF shard metadata
|
| 73 |
+
def shard_meta():
|
| 74 |
+
p = os.path.join(ROOT, "shards.json")
|
| 75 |
+
if os.path.exists(p):
|
| 76 |
+
return json.load(open(p))
|
| 77 |
+
with urllib.request.urlopen(f"https://huggingface.co/api/models/{REPO}?blobs=true", timeout=60) as r:
|
| 78 |
+
d = json.load(r)
|
| 79 |
+
meta = {}
|
| 80 |
+
for s in d["siblings"]:
|
| 81 |
+
fn = s["rfilename"]
|
| 82 |
+
if fn.endswith(".safetensors"):
|
| 83 |
+
lfs = s.get("lfs") or {}
|
| 84 |
+
meta[fn] = {"size": s.get("size") or lfs.get("size"), "sha256": lfs.get("oid")}
|
| 85 |
+
json.dump(meta, open(p, "w"), indent=1)
|
| 86 |
+
return meta
|
| 87 |
+
|
| 88 |
+
META = shard_meta()
|
| 89 |
+
|
| 90 |
+
# ---------------------------------------------------------------- download (thread)
|
| 91 |
+
dl_lock = threading.Lock()
|
| 92 |
+
dl_inflight = set()
|
| 93 |
+
|
| 94 |
+
def sha256_file(path):
|
| 95 |
+
h = hashlib.sha256()
|
| 96 |
+
with open(path, "rb") as f:
|
| 97 |
+
for chunk in iter(lambda: f.read(64 << 20), b""):
|
| 98 |
+
h.update(chunk)
|
| 99 |
+
return h.hexdigest()
|
| 100 |
+
|
| 101 |
+
def download(shard):
|
| 102 |
+
dst = os.path.join(SRC, shard)
|
| 103 |
+
part = dst + ".part"
|
| 104 |
+
if os.path.exists(dst):
|
| 105 |
+
return True
|
| 106 |
+
# one curl per shard: the prefetch thread and the main thread may want the same file
|
| 107 |
+
with dl_lock:
|
| 108 |
+
if shard in dl_inflight:
|
| 109 |
+
busy = True
|
| 110 |
+
else:
|
| 111 |
+
dl_inflight.add(shard)
|
| 112 |
+
busy = False
|
| 113 |
+
if busy:
|
| 114 |
+
while shard in dl_inflight:
|
| 115 |
+
time.sleep(5)
|
| 116 |
+
return os.path.exists(dst)
|
| 117 |
+
try:
|
| 118 |
+
return _download(shard, dst, part)
|
| 119 |
+
finally:
|
| 120 |
+
with dl_lock:
|
| 121 |
+
dl_inflight.discard(shard)
|
| 122 |
+
|
| 123 |
+
def _download(shard, dst, part):
|
| 124 |
+
for attempt in range(5):
|
| 125 |
+
rc = subprocess.call(["curl", "-sSL", "-C", "-", "--retry", "8", "--retry-delay", "5",
|
| 126 |
+
"-o", part, HF + shard])
|
| 127 |
+
if rc == 0 and os.path.exists(part):
|
| 128 |
+
size = os.path.getsize(part)
|
| 129 |
+
want = META.get(shard, {}).get("size")
|
| 130 |
+
if want and size != want:
|
| 131 |
+
log(f" size mismatch {shard}: {size} != {want}, retrying")
|
| 132 |
+
os.remove(part)
|
| 133 |
+
continue
|
| 134 |
+
want_sha = META.get(shard, {}).get("sha256")
|
| 135 |
+
if want_sha:
|
| 136 |
+
got = sha256_file(part)
|
| 137 |
+
if got != want_sha:
|
| 138 |
+
log(f" sha256 mismatch {shard}, retrying")
|
| 139 |
+
os.remove(part)
|
| 140 |
+
continue
|
| 141 |
+
os.replace(part, dst)
|
| 142 |
+
log(f" downloaded {shard} ({size/1e9:.1f} GB) free={free_gb():.0f} GB")
|
| 143 |
+
return True
|
| 144 |
+
time.sleep(10)
|
| 145 |
+
log(f"!! download failed for {shard}")
|
| 146 |
+
return False
|
| 147 |
+
|
| 148 |
+
def needed_shards(upto_layer):
|
| 149 |
+
"""Shards required for layers <= upto_layer that are not done, plus permanent ones."""
|
| 150 |
+
st = load_state()
|
| 151 |
+
done = set(st["done_layers"])
|
| 152 |
+
need = set(PERMANENT)
|
| 153 |
+
for L in range(0, min(upto_layer, NUM_LAYERS - 1) + 1):
|
| 154 |
+
if L not in done:
|
| 155 |
+
need |= layer_shards[L]
|
| 156 |
+
return need
|
| 157 |
+
|
| 158 |
+
def prefetch_loop(get_current):
|
| 159 |
+
while True:
|
| 160 |
+
try:
|
| 161 |
+
st = load_state()
|
| 162 |
+
if st.get("finished"):
|
| 163 |
+
return
|
| 164 |
+
cur = get_current()
|
| 165 |
+
for shard in sorted(needed_shards(cur + WINDOW)):
|
| 166 |
+
dst = os.path.join(SRC, shard)
|
| 167 |
+
if os.path.exists(dst):
|
| 168 |
+
continue
|
| 169 |
+
if free_gb() < MIN_FREE_GB:
|
| 170 |
+
break
|
| 171 |
+
download(shard)
|
| 172 |
+
except Exception as e:
|
| 173 |
+
log(f"prefetch error: {e!r}")
|
| 174 |
+
time.sleep(15)
|
| 175 |
+
|
| 176 |
+
# ---------------------------------------------------------------- shard rewrite (pure python)
|
| 177 |
+
def rewrite_shard(path, drop_prefixes):
|
| 178 |
+
"""Rewrite a safetensors file keeping only tensors not starting with drop_prefixes."""
|
| 179 |
+
with open(path, "rb") as f:
|
| 180 |
+
n = struct.unpack("<Q", f.read(8))[0]
|
| 181 |
+
hdr = json.loads(f.read(n))
|
| 182 |
+
base = 8 + n
|
| 183 |
+
meta = hdr.pop("__metadata__", None)
|
| 184 |
+
keep = {k: v for k, v in hdr.items() if not any(k.startswith(p) for p in drop_prefixes)}
|
| 185 |
+
if len(keep) == len(hdr):
|
| 186 |
+
return False
|
| 187 |
+
items = sorted(keep.items(), key=lambda kv: kv[1]["data_offsets"][0])
|
| 188 |
+
new = {}
|
| 189 |
+
off = 0
|
| 190 |
+
for k, v in items:
|
| 191 |
+
s, e = v["data_offsets"]
|
| 192 |
+
new[k] = {"dtype": v["dtype"], "shape": v["shape"], "data_offsets": [off, off + (e - s)]}
|
| 193 |
+
off += e - s
|
| 194 |
+
if meta is not None:
|
| 195 |
+
new["__metadata__"] = meta
|
| 196 |
+
hb = json.dumps(new, separators=(",", ":")).encode()
|
| 197 |
+
hb += b" " * ((8 - len(hb) % 8) % 8)
|
| 198 |
+
tmp = path + ".rewrite"
|
| 199 |
+
with open(tmp, "wb") as o:
|
| 200 |
+
o.write(struct.pack("<Q", len(hb)))
|
| 201 |
+
o.write(hb)
|
| 202 |
+
for k, v in items:
|
| 203 |
+
s, e = v["data_offsets"]
|
| 204 |
+
f.seek(base + s)
|
| 205 |
+
remaining = e - s
|
| 206 |
+
while remaining:
|
| 207 |
+
chunk = f.read(min(64 << 20, remaining))
|
| 208 |
+
o.write(chunk)
|
| 209 |
+
remaining -= len(chunk)
|
| 210 |
+
before = os.path.getsize(path)
|
| 211 |
+
os.replace(tmp, path)
|
| 212 |
+
log(f" rewrote {os.path.basename(path)}: {before/1e9:.1f} -> {os.path.getsize(path)/1e9:.1f} GB")
|
| 213 |
+
return True
|
| 214 |
+
|
| 215 |
+
def drop_done_experts(st):
|
| 216 |
+
done = set(st["done_layers"])
|
| 217 |
+
for shard in ALL_SHARDS:
|
| 218 |
+
path = os.path.join(SRC, shard)
|
| 219 |
+
if not os.path.exists(path):
|
| 220 |
+
continue
|
| 221 |
+
already = set(st["dropped"].get(shard, []))
|
| 222 |
+
todo = sorted((shard_layers[shard] & done) - already)
|
| 223 |
+
todo = [L for L in todo if L != MTP_LAYER or st.get("mtp_done")]
|
| 224 |
+
if not todo:
|
| 225 |
+
continue
|
| 226 |
+
prefixes = [f"model.layers.{L}.mlp.experts." for L in todo]
|
| 227 |
+
rewrite_shard(path, prefixes)
|
| 228 |
+
st["dropped"][shard] = sorted(already | set(todo))
|
| 229 |
+
save_state(st)
|
| 230 |
+
|
| 231 |
+
# ---------------------------------------------------------------- convert runs
|
| 232 |
+
def image_ready():
|
| 233 |
+
return subprocess.call(["docker", "image", "inspect", IMAGE], stdout=subprocess.DEVNULL,
|
| 234 |
+
stderr=subprocess.DEVNULL) == 0
|
| 235 |
+
|
| 236 |
+
def run_convert(max_module, first, tag):
|
| 237 |
+
os.makedirs(RUNS, exist_ok=True)
|
| 238 |
+
logf = os.path.join(RUNS, f"run-{tag}.log")
|
| 239 |
+
cmd = ["docker", "run", "--rm", "--name", CONTAINER, "--gpus", "all", "--ipc=host", "--network", "host",
|
| 240 |
+
"--memory", MEM_CAP, "--memory-swap", MEM_CAP,
|
| 241 |
+
"-v", f"{ROOT}:/work", "-e", "TORCH_CUDA_ARCH_LIST=12.1a", "-e", "PYTHONUNBUFFERED=1",
|
| 242 |
+
"-e", "PYTHONPATH=/work/exllamav3:/work", "-e", "PYTHONDONTWRITEBYTECODE=1"]
|
| 243 |
+
# encode farm: remote worker GPUs, re-read from farm.env before every run (edit without restart)
|
| 244 |
+
fenv = os.path.join(ROOT, "farm.env")
|
| 245 |
+
if os.path.exists(fenv):
|
| 246 |
+
for line in open(fenv):
|
| 247 |
+
line = line.strip()
|
| 248 |
+
if line and not line.startswith("#") and "=" in line:
|
| 249 |
+
cmd += ["-e", line]
|
| 250 |
+
log(f" farm env: {line}")
|
| 251 |
+
cmd += ["--entrypoint", "python3", IMAGE, "/work/exllamav3/convert.py", "-w", "/work/work"]
|
| 252 |
+
if first:
|
| 253 |
+
cmd += ["-i", "/work/src", "-o", "/work/out", "-rcp", "/work/recipe.yaml",
|
| 254 |
+
"-b", str(EXPERT_BITS), "-hb", "16", "-mb", "16", "-cb", "mcg", "-cpi", "0", "-d", "0"]
|
| 255 |
+
else:
|
| 256 |
+
cmd += ["-r"]
|
| 257 |
+
if max_module is not None:
|
| 258 |
+
cmd += ["--max_module", str(max_module)]
|
| 259 |
+
log(f"RUN {tag}: {' '.join(cmd[-8:])} -> {logf}")
|
| 260 |
+
with open(logf, "w") as lf:
|
| 261 |
+
lf.write(" ".join(cmd) + "\n")
|
| 262 |
+
lf.flush()
|
| 263 |
+
rc = subprocess.call(cmd, stdout=lf, stderr=subprocess.STDOUT)
|
| 264 |
+
log(f"RUN {tag} exit={rc}")
|
| 265 |
+
return rc
|
| 266 |
+
|
| 267 |
+
def ensure_present(shards):
|
| 268 |
+
while True:
|
| 269 |
+
missing = [s for s in sorted(shards) if not os.path.exists(os.path.join(SRC, s))]
|
| 270 |
+
if not missing:
|
| 271 |
+
return
|
| 272 |
+
log(f" waiting for {len(missing)} shard(s): {missing[:3]}... free={free_gb():.0f} GB")
|
| 273 |
+
for s in missing:
|
| 274 |
+
if free_gb() < MIN_FREE_GB - 20:
|
| 275 |
+
break
|
| 276 |
+
download(s)
|
| 277 |
+
time.sleep(20)
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def container_running():
|
| 281 |
+
out = subprocess.run(["docker", "ps", "-q", "-f", f"name=^{CONTAINER}$"], capture_output=True, text=True).stdout
|
| 282 |
+
return bool(out.strip())
|
| 283 |
+
|
| 284 |
+
def adopt_running(st):
|
| 285 |
+
"""If a previous orchestrator left a convert run going, wait for it and credit its layers from
|
| 286 |
+
the converter's own checkpoint (work/ckpt/job.json: next_module_idx = layer + 2)."""
|
| 287 |
+
if not container_running():
|
| 288 |
+
return
|
| 289 |
+
log("adopting a running convert container; waiting for it to exit ...")
|
| 290 |
+
while container_running():
|
| 291 |
+
time.sleep(30)
|
| 292 |
+
try:
|
| 293 |
+
job = json.load(open(os.path.join(WORK, "ckpt", "job.json")))
|
| 294 |
+
nmi = int(job["next_module_idx"])
|
| 295 |
+
except Exception as e:
|
| 296 |
+
log(f"adopt: cannot read job.json ({e!r}); layers will be re-derived by the next run")
|
| 297 |
+
return
|
| 298 |
+
st = load_state()
|
| 299 |
+
done_before = len(st["done_layers"])
|
| 300 |
+
for L in range(0, min(nmi - 1, NUM_LAYERS)):
|
| 301 |
+
if L not in st["done_layers"]:
|
| 302 |
+
st["done_layers"].append(L)
|
| 303 |
+
st["started"] = True
|
| 304 |
+
save_state(st)
|
| 305 |
+
log(f"adopted: next_module_idx={nmi} -> done layers {done_before} -> {len(st['done_layers'])}")
|
| 306 |
+
drop_done_experts(st)
|
| 307 |
+
|
| 308 |
+
# ---------------------------------------------------------------- main
|
| 309 |
+
def main():
|
| 310 |
+
os.makedirs(SRC, exist_ok=True)
|
| 311 |
+
for fn in os.listdir(STUB):
|
| 312 |
+
dst = os.path.join(SRC, fn)
|
| 313 |
+
if not os.path.exists(dst):
|
| 314 |
+
shutil.copy(os.path.join(STUB, fn), dst)
|
| 315 |
+
st = load_state()
|
| 316 |
+
current = {"layer": max(st["done_layers"], default=-1) + 1}
|
| 317 |
+
threading.Thread(target=prefetch_loop, args=(lambda: current["layer"],), daemon=True).start()
|
| 318 |
+
log(f"orchestrator start: done={len(st['done_layers'])} layers, free={free_gb():.0f} GB, "
|
| 319 |
+
f"permanent shards={len(PERMANENT)}, window={WINDOW}, layers/run={LAYERS_PER_RUN}")
|
| 320 |
+
|
| 321 |
+
while not image_ready():
|
| 322 |
+
log(f"waiting for docker image {IMAGE} ...")
|
| 323 |
+
time.sleep(60)
|
| 324 |
+
|
| 325 |
+
adopt_running(st)
|
| 326 |
+
|
| 327 |
+
while True:
|
| 328 |
+
st = load_state()
|
| 329 |
+
done = set(st["done_layers"])
|
| 330 |
+
if st.get("finished"):
|
| 331 |
+
log("already finished")
|
| 332 |
+
return
|
| 333 |
+
next_layer = 0
|
| 334 |
+
while next_layer in done:
|
| 335 |
+
next_layer += 1
|
| 336 |
+
if next_layer >= NUM_LAYERS:
|
| 337 |
+
# final run: norm, head, MTP, compile
|
| 338 |
+
ensure_present(PERMANENT)
|
| 339 |
+
rc = run_convert(None, not st["started"], "final")
|
| 340 |
+
if rc != 0:
|
| 341 |
+
log("!! final run failed; stopping (fix and restart)")
|
| 342 |
+
return
|
| 343 |
+
st["finished"] = True
|
| 344 |
+
st["mtp_done"] = True
|
| 345 |
+
save_state(st)
|
| 346 |
+
log(f"FINISHED. output at {OUT}; free={free_gb():.0f} GB")
|
| 347 |
+
return
|
| 348 |
+
# first run: embed + dense layers 0..2 + first MoE layer in one go
|
| 349 |
+
if not st["started"]:
|
| 350 |
+
last = 3
|
| 351 |
+
else:
|
| 352 |
+
last = min(next_layer + LAYERS_PER_RUN - 1, NUM_LAYERS - 1)
|
| 353 |
+
current["layer"] = next_layer
|
| 354 |
+
need = set(PERMANENT)
|
| 355 |
+
for L in range(next_layer, last + 1):
|
| 356 |
+
need |= layer_shards[L]
|
| 357 |
+
ensure_present(need)
|
| 358 |
+
tag = f"L{next_layer:02d}-{last:02d}"
|
| 359 |
+
rc = run_convert(last + 1, not st["started"], tag) # module idx = layer + 1 (embedding is 0)
|
| 360 |
+
if rc != 0:
|
| 361 |
+
log(f"!! run {tag} failed (exit {rc}); retrying once after 60 s")
|
| 362 |
+
time.sleep(60)
|
| 363 |
+
rc = run_convert(last + 1, False, tag + "-retry")
|
| 364 |
+
if rc != 0:
|
| 365 |
+
log("!! run failed twice; stopping. Inspect runs/ logs, then restart orchestrator.")
|
| 366 |
+
return
|
| 367 |
+
st = load_state()
|
| 368 |
+
st["started"] = True
|
| 369 |
+
for L in range(next_layer, last + 1):
|
| 370 |
+
if L not in st["done_layers"]:
|
| 371 |
+
st["done_layers"].append(L)
|
| 372 |
+
save_state(st)
|
| 373 |
+
current["layer"] = last + 1
|
| 374 |
+
drop_done_experts(st)
|
| 375 |
+
log(f"progress: {len(st['done_layers'])}/{NUM_LAYERS} layers, free={free_gb():.0f} GB")
|
| 376 |
+
|
| 377 |
+
if __name__ == "__main__":
|
| 378 |
+
main()
|
quant/patch_aarch64.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Make exllamav3 1.4.5's C++/CUDA extension build on aarch64 (GB10 / DGX Spark).
|
| 3 |
+
|
| 4 |
+
x86-only pieces (AVX all-reduce, AVX CPU MoE offload for the mul1 codebook, x86 pause
|
| 5 |
+
intrinsics) are wrapped in architecture guards with throwing stubs. None of these paths are used
|
| 6 |
+
for GPU quantization or GPU inference. Idempotent.
|
| 7 |
+
"""
|
| 8 |
+
import re, sys, os
|
| 9 |
+
|
| 10 |
+
root = sys.argv[1] if len(sys.argv) > 1 else "exllamav3/exllamav3_ext"
|
| 11 |
+
X86 = "defined(__x86_64__) || defined(__i386__)"
|
| 12 |
+
PAUSE_MACRO = """
|
| 13 |
+
#if defined(__x86_64__) || defined(__i386__)
|
| 14 |
+
#define EXL3_CPU_PAUSE() __builtin_ia32_pause()
|
| 15 |
+
#else
|
| 16 |
+
#define EXL3_CPU_PAUSE() __asm__ __volatile__("yield" ::: "memory")
|
| 17 |
+
#endif
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def wrap(path, stub_body):
|
| 21 |
+
s = open(path).read()
|
| 22 |
+
if "EXL3_AARCH64_STUB" in s:
|
| 23 |
+
print("already:", path); return
|
| 24 |
+
s = f"#if {X86}\n" + s + f"\n#else /* EXL3_AARCH64_STUB */\n{stub_body}\n#endif\n"
|
| 25 |
+
open(path, "w").write(s); print("wrapped:", path)
|
| 26 |
+
|
| 27 |
+
def decls_from_header(path):
|
| 28 |
+
"""Extract non-inline, non-template free-function declarations `ret name(args);`."""
|
| 29 |
+
s = open(path).read()
|
| 30 |
+
s = re.sub(r"//.*", "", s)
|
| 31 |
+
s = re.sub(r"/\*.*?\*/", "", s, flags=re.S)
|
| 32 |
+
out = []
|
| 33 |
+
for m in re.finditer(r"^\s*((?:[A-Za-z_][\w:<>\*&\s]*?)\s+\**\s*([A-Za-z_]\w*)\s*\(([^;{}]*?)\)\s*;)", s, flags=re.M):
|
| 34 |
+
full, name, args = m.group(1), m.group(2), m.group(3)
|
| 35 |
+
head = full[: full.index(name)].strip()
|
| 36 |
+
if head.startswith(("inline", "template", "static", "typedef", "return", "struct", "class")):
|
| 37 |
+
continue
|
| 38 |
+
if not head:
|
| 39 |
+
continue
|
| 40 |
+
out.append((head, name, " ".join(args.split())))
|
| 41 |
+
return out
|
| 42 |
+
|
| 43 |
+
def stubs(header, exclude=()):
|
| 44 |
+
lines = [f'#include "{os.path.basename(header)}"', "#include <stdexcept>"]
|
| 45 |
+
for ret, name, args in decls_from_header(header):
|
| 46 |
+
if name in exclude:
|
| 47 |
+
continue
|
| 48 |
+
body = 'throw std::runtime_error("' + name + ' unavailable on aarch64");'
|
| 49 |
+
lines.append(f"{ret} {name}({args}) {{ {body} }}")
|
| 50 |
+
return "\n".join(lines)
|
| 51 |
+
|
| 52 |
+
# 1. AVX all-reduce implementations
|
| 53 |
+
h512 = os.path.join(root, "parallel/all_reduce_cpu_avx512.h")
|
| 54 |
+
h2 = os.path.join(root, "parallel/all_reduce_cpu_avx2.h")
|
| 55 |
+
wrap(os.path.join(root, "parallel/all_reduce_cpu_avx512.cpp"),
|
| 56 |
+
stubs(h512, exclude=("is_avx512_supported",)))
|
| 57 |
+
wrap(os.path.join(root, "parallel/all_reduce_cpu_avx2.cpp"),
|
| 58 |
+
'#include "all_reduce_cpu_avx512.h"\n' + stubs(h2))
|
| 59 |
+
|
| 60 |
+
# 2. CPU MoE (mul1) offload kernels
|
| 61 |
+
hm = os.path.join(root, "cpu/moe_mul1.h")
|
| 62 |
+
wrap(os.path.join(root, "cpu/moe_mul1.cpp"), stubs(hm))
|
| 63 |
+
|
| 64 |
+
# 3. x86 pause intrinsic in host code of .cu files
|
| 65 |
+
for f in ("parallel/all_reduce_cpu.cu", "cpu/moe_handoff.cu"):
|
| 66 |
+
p = os.path.join(root, f)
|
| 67 |
+
s = open(p).read()
|
| 68 |
+
if "EXL3_CPU_PAUSE" in s:
|
| 69 |
+
print("already:", p); continue
|
| 70 |
+
s = s.replace("__builtin_ia32_pause()", "EXL3_CPU_PAUSE()")
|
| 71 |
+
# insert macro after the last #include at file top
|
| 72 |
+
idx = 0
|
| 73 |
+
for m in re.finditer(r"^#include.*$", s, flags=re.M):
|
| 74 |
+
idx = m.end()
|
| 75 |
+
s = s[:idx] + "\n" + PAUSE_MACRO + s[idx:]
|
| 76 |
+
open(p, "w").write(s); print("pause-patched:", p)
|
| 77 |
+
print("stub decls:", [n for _, n, _ in decls_from_header(hm)])
|
quant/patch_farm.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Patch exllamav3/conversion/convert_model.py so expert encoding can be farmed to remote GPUs.
|
| 3 |
+
|
| 4 |
+
EXL3_REMOTE_WORKERS="host:port,host:port" adds one work thread per remote worker to the
|
| 5 |
+
per-layer parallel quantization; each thread ships its groups (weights + finalized Hessians)
|
| 6 |
+
via exl3_farm.quantize_remote and rebuilds LinearEXL3 from the returned tensors. Falls back to
|
| 7 |
+
local encoding for a group if the remote call fails. Idempotent.
|
| 8 |
+
"""
|
| 9 |
+
import sys, re
|
| 10 |
+
|
| 11 |
+
p = sys.argv[1]
|
| 12 |
+
s = open(p).read()
|
| 13 |
+
if "EXL3_REMOTE_WORKERS" in s:
|
| 14 |
+
print("already patched")
|
| 15 |
+
sys.exit(0)
|
| 16 |
+
|
| 17 |
+
# 1. remote work path inside quantize_linears_parallel
|
| 18 |
+
old_thread = " def work_thread_(device_idx, dev_groups):\n global curr_progress\n\n with torch.inference_mode():\n"
|
| 19 |
+
assert old_thread in s
|
| 20 |
+
new_thread = ''' def work_thread_(device_idx, dev_groups):
|
| 21 |
+
global curr_progress
|
| 22 |
+
|
| 23 |
+
if isinstance(device_idx, str) and device_idx.startswith("remote:"):
|
| 24 |
+
_remote_work_thread(device_idx, dev_groups)
|
| 25 |
+
return
|
| 26 |
+
|
| 27 |
+
with torch.inference_mode():
|
| 28 |
+
'''
|
| 29 |
+
s = s.replace(old_thread, new_thread, 1)
|
| 30 |
+
|
| 31 |
+
remote_fn = '''
|
| 32 |
+
def _remote_work_thread(device_idx, dev_groups):
|
| 33 |
+
global curr_progress
|
| 34 |
+
from exl3_farm import quantize_remote
|
| 35 |
+
from exllamav3.modules.quant.exl3_lib.quantize import finalize_capture_H
|
| 36 |
+
host, port = device_idx[len("remote:"):].rsplit(":", 1)
|
| 37 |
+
port = int(port)
|
| 38 |
+
local_dev = torch.device([d for d in devices if not isinstance(d, str)][0])
|
| 39 |
+
t0 = time.time()
|
| 40 |
+
with torch.inference_mode():
|
| 41 |
+
for group in dev_groups:
|
| 42 |
+
quant_args_list = [make_quant_args(args, idx, strategy[l.key], [0]) for l in group]
|
| 43 |
+
H_datas = [capture_H[l.qmap] if state else l.init_H_data(False) for l in group]
|
| 44 |
+
for hd, qa in zip(H_datas, quant_args_list):
|
| 45 |
+
torch.manual_seed(qa["seed"])
|
| 46 |
+
finalize_capture_H(hd, qa, False)
|
| 47 |
+
weights = [l.inner.get_weight_tensor() for l in group]
|
| 48 |
+
biases = [l.inner.get_bias_tensor() for l in group]
|
| 49 |
+
try:
|
| 50 |
+
tq = time.time()
|
| 51 |
+
results, quant_args_list = quantize_remote(host, port, weights, H_datas, quant_args_list)
|
| 52 |
+
tstr = f" [remote {host} {(time.time() - tq) / len(group):4.2f} s]"
|
| 53 |
+
except Exception as e:
|
| 54 |
+
print(f" !! remote {host}:{port} failed ({e!r}); encoding group locally", flush = True)
|
| 55 |
+
for l in group:
|
| 56 |
+
l.inner.swap_cpu()
|
| 57 |
+
if len(group) > 1:
|
| 58 |
+
quant_args_list = [make_quant_args(args, idx, strategy[l.key], [local_dev.index or 0]) for l in group]
|
| 59 |
+
proxy_errs = convert_exl3_group(group, H_datas, quant_args_list)
|
| 60 |
+
for l, qa, pe in zip(group, quant_args_list, proxy_errs):
|
| 61 |
+
l.inner.swap_cpu()
|
| 62 |
+
print_quantized_linear(config, l, qa, pe, " [local fallback]")
|
| 63 |
+
with progress_lock:
|
| 64 |
+
curr_progress += 1
|
| 65 |
+
else:
|
| 66 |
+
l = group[0]
|
| 67 |
+
qa = make_quant_args(args, idx, strategy[l.key], [local_dev.index or 0])
|
| 68 |
+
pe = l.convert_exl3(H_datas[0], quant_args = qa, verbose = False, save_reg = False,
|
| 69 |
+
override_swap_device = local_dev)
|
| 70 |
+
l.inner.swap_cpu()
|
| 71 |
+
print_quantized_linear(config, l, qa, pe, " [local fallback]")
|
| 72 |
+
with progress_lock:
|
| 73 |
+
curr_progress += 1
|
| 74 |
+
continue
|
| 75 |
+
for l, bias, qa, (pe, out) in zip(group, biases, quant_args_list, results):
|
| 76 |
+
out = {k: v.to(local_dev) for k, v in out.items()}
|
| 77 |
+
l.inner = LinearEXL3(
|
| 78 |
+
l.config, l.in_features, l.out_features,
|
| 79 |
+
out.get("scale"), out.get("su"), out.get("sv"), out.get("suh"), out.get("svh"),
|
| 80 |
+
out.get("trellis"), out.get("mcg"), out.get("mul1"),
|
| 81 |
+
bias, l.out_dtype, key = l.key
|
| 82 |
+
)
|
| 83 |
+
l.inner.swap_cpu()
|
| 84 |
+
print_quantized_linear(config, l, qa, pe, tstr)
|
| 85 |
+
with progress_lock:
|
| 86 |
+
curr_progress += 1
|
| 87 |
+
print(f" -- remote {host}: {sum(len(g) for g in dev_groups)} tensors in {time.time() - t0:.0f} s", flush = True)
|
| 88 |
+
|
| 89 |
+
'''
|
| 90 |
+
anchor = " # Launch\n threads = []\n"
|
| 91 |
+
assert anchor in s
|
| 92 |
+
s = s.replace(anchor, remote_fn + anchor, 1)
|
| 93 |
+
|
| 94 |
+
# 2. dispatch: farm the <=8-bit linears, keep 16-bit ones on the single path
|
| 95 |
+
old_dispatch = ''' if (
|
| 96 |
+
len(linears) >= len(devices) and
|
| 97 |
+
all(b <= 8 for _, b in strategy.items())
|
| 98 |
+
):
|
| 99 |
+
quantize_linears_parallel(args, linears, config, strategy, idx, devices, eff_ratios("quant_thread"), capture_H, state)
|
| 100 |
+
else:
|
| 101 |
+
quantize_linears_single(args, linears, config, strategy, idx, devices, eff_ratios("quant_tiles"), capture_H, state)
|
| 102 |
+
'''
|
| 103 |
+
assert old_dispatch in s
|
| 104 |
+
new_dispatch = ''' _remotes = [f"remote:{w.strip()}" for w in os.environ.get("EXL3_REMOTE_WORKERS", "").split(",") if w.strip()]
|
| 105 |
+
_q_linears = [l for l in linears if strategy[l.key] <= 8]
|
| 106 |
+
_u_linears = [l for l in linears if strategy[l.key] > 8]
|
| 107 |
+
if _remotes and len(_q_linears) >= 2 * (len(devices) + len(_remotes)):
|
| 108 |
+
if _u_linears:
|
| 109 |
+
quantize_linears_single(args, _u_linears, config, strategy, idx, devices, eff_ratios("quant_tiles"), capture_H, state)
|
| 110 |
+
print(f" -- Farming {len(_q_linears)} tensors over {devices + _remotes}", flush = True)
|
| 111 |
+
quantize_linears_parallel(args, _q_linears, config, strategy, idx, devices + _remotes, None, capture_H, state)
|
| 112 |
+
elif (
|
| 113 |
+
len(linears) >= len(devices) and
|
| 114 |
+
all(b <= 8 for _, b in strategy.items())
|
| 115 |
+
):
|
| 116 |
+
quantize_linears_parallel(args, linears, config, strategy, idx, devices, eff_ratios("quant_thread"), capture_H, state)
|
| 117 |
+
else:
|
| 118 |
+
quantize_linears_single(args, linears, config, strategy, idx, devices, eff_ratios("quant_tiles"), capture_H, state)
|
| 119 |
+
'''
|
| 120 |
+
s = s.replace(old_dispatch, new_dispatch, 1)
|
| 121 |
+
open(p, "w").write(s)
|
| 122 |
+
print("patched", p)
|
quant/recipe.yaml
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
quant/start-orch.sh
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
cd ~/glm53-full-exl3 && nohup python3 orchestrate.py >> orchestrate.out 2>&1 &
|
serving/Dockerfile.dcp
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# keys GLM-5.3 FULL EXL3 (3-bit mcg routed experts) serving image = Mia base + vertical integration:
|
| 2 |
+
# * keys-DCP port of the SM120 sparse-MLA backend (KV sharded across TP ranks -> 1M ctx on 4 GB10)
|
| 3 |
+
# * MLA layer DCP combine fix for sparse all-MQA backends (no MHA prefill path)
|
| 4 |
+
# * ABLIT load-time o_proj transplant hook for the DeepseekV2 model classes (derisk donors from
|
| 5 |
+
# make_derisk_donor.py mounted at /opt/glm53/ablit/transplant; ABLIT=1 to enable)
|
| 6 |
+
# Build on any GB10 (context = this directory): docker build -f Dockerfile.dcp -t glm53-exl3-tp4:dcp .
|
| 7 |
+
ARG BASE=ghcr.io/miaai-lab/glm-5.3-flash-2x-dgx-sparks:exl3
|
| 8 |
+
FROM ${BASE}
|
| 9 |
+
ARG VLLM=/usr/local/lib/python3.12/dist-packages/vllm
|
| 10 |
+
COPY sm120_dcp.py ${VLLM}/v1/attention/backends/mla/flashinfer_mla_sparse_sm120.py
|
| 11 |
+
COPY mla_attn_dcp.py ${VLLM}/model_executor/layers/attention/mla_attention.py
|
| 12 |
+
COPY patch_ablit_dsv2.py /opt/glm53/patch_ablit_dsv2.py
|
| 13 |
+
COPY ablit_runtime.py /opt/glm53/ablit_runtime.py
|
| 14 |
+
RUN python3 /opt/glm53/patch_ablit_dsv2.py /opt/glm53/ablit_runtime.py && python3 - <<'EOF'
|
| 15 |
+
from vllm.v1.attention.backends.mla.flashinfer_mla_sparse_sm120 import FlashInferMLASparseSM120Impl as I
|
| 16 |
+
assert I.can_return_lse_for_decode is True
|
| 17 |
+
import vllm.model_executor.layers.attention.mla_attention as m
|
| 18 |
+
assert "keys-DCP" in open(m.__file__).read()
|
| 19 |
+
print("keys image OK: DCP backend + layer fix + ablit hook; lse_base_on_e =", I.lse_base_on_e)
|
| 20 |
+
EOF
|
| 21 |
+
LABEL keys.glm53.full.exl3="DCP sm120 + mla combine fix + ablit dsv2 hook, 2026-09-01"
|
serving/Dockerfile.keys
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM glm53-exl3-tp4:dcp-mul1-v145
|
| 2 |
+
RUN f=/usr/local/lib/python3.12/dist-packages/vllm/model_executor/layers/sparse_attn_indexer.py && sed -i "s/use_persistent_topk = current_platform.is_cuda() and topk_tokens in (/use_persistent_topk = False and current_platform.is_cuda() and topk_tokens in ( # keys: GB10 48-SM oversubscribe/" $f && grep -c "keys: GB10 48-SM" $f && rm -rf /usr/local/lib/python3.12/dist-packages/vllm/model_executor/layers/__pycache__/sparse_attn_indexer*
|
serving/Dockerfile.keys3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM glm53-exl3-tp4:keys
|
| 2 |
+
COPY exl3.py /usr/local/lib/python3.12/dist-packages/vllm/model_executor/layers/quantization/exl3.py
|
| 3 |
+
RUN rm -rf /usr/local/lib/python3.12/dist-packages/vllm/model_executor/layers/quantization/__pycache__
|
serving/Dockerfile.mul1
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM glm53-exl3-tp4:dcp
|
| 2 |
+
COPY exl3.py /usr/local/lib/python3.12/dist-packages/vllm/model_executor/layers/quantization/exl3.py
|
serving/Dockerfile.v145
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM glm53-exl3-tp4:dcp-mul1
|
| 2 |
+
ARG DP=/usr/local/lib/python3.12/dist-packages
|
| 3 |
+
RUN rm -rf $DP/exllamav3 $DP/exllamav3-0.0.43.dist-info $DP/exllamav3_ext*.so
|
| 4 |
+
COPY --from=glm53-exl3-quant:1.4.5 $DP/exllamav3 $DP/exllamav3
|
| 5 |
+
COPY --from=glm53-exl3-quant:1.4.5 $DP/exllamav3-1.4.5.dist-info $DP/exllamav3-1.4.5.dist-info
|
| 6 |
+
COPY --from=glm53-exl3-quant:1.4.5 $DP/exllamav3_ext.cpython-312-aarch64-linux-gnu.so $DP/exllamav3_ext.cpython-312-aarch64-linux-gnu.so
|
serving/ablit_runtime.py
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: MIT
|
| 2 |
+
"""GLM-5.3-Flash o_proj abliteration (ABLIT) — load-time refusal-direction edit.
|
| 3 |
+
|
| 4 |
+
Applies the classic weight orthogonalization to every ``self_attn.o_proj`` in
|
| 5 |
+
the configured layer range:
|
| 6 |
+
|
| 7 |
+
W' = (I - alpha * r r^T / ||r||^2) W
|
| 8 |
+
|
| 9 |
+
``r`` is the refusal direction in the residual/output space of o_proj
|
| 10 |
+
(hidden_size=4096). Components of the attention output orthogonal to ``r``
|
| 11 |
+
are preserved exactly; the component along ``r`` is scaled by ``1 - alpha``
|
| 12 |
+
(alpha > 1 over-projects, which is what the published recipe uses).
|
| 13 |
+
|
| 14 |
+
METHOD (ABLIT_METHOD=auto | transplant | proj):
|
| 15 |
+
transplant — replace the stock o_proj weights L15-45 (incl. the checkpoint
|
| 16 |
+
MTP block's, layer 45) with the donor tensors fetched by
|
| 17 |
+
ablit/fetch_transplant.py into ablit/transplant/ (raw bf16 + MANIFEST.json).
|
| 18 |
+
This is the published "dealign-oproj-transplant" edit — a byte-copy from
|
| 19 |
+
dealignai/GLM-5.3-Flash-UNCENSORED-NVFP4 into the stock body. Direction
|
| 20 |
+
orthogonalization does NOT work on this model (the published directions
|
| 21 |
+
are statistically random vs stock o_proj — measured), so transplant is
|
| 22 |
+
the only effective mode; "auto" prefers it when the tensors are present.
|
| 23 |
+
proj — the classic orthogonalization below. Kept for custom directions.
|
| 24 |
+
|
| 25 |
+
TP notes: o_proj is a RowParallelLinear — vLLM shards the *input* dim, the
|
| 26 |
+
4096 output rows are replicated on every rank. The edit only touches rows,
|
| 27 |
+
so each rank applies the identical formula to its own shard and the
|
| 28 |
+
post-allreduce result stays consistent. No collective is needed.
|
| 29 |
+
|
| 30 |
+
The edit runs at the end of ``Glm5NextModel.load_weights`` / ``Glm5NextMTP.
|
| 31 |
+
load_weights`` (installed by overlay/patch_ablit.py). o_proj stays native
|
| 32 |
+
BF16 in this serve (attn is unquantized), so the parameter is final at that
|
| 33 |
+
point — quantized-expert post-processing never touches it, and CUDA graph
|
| 34 |
+
capture happens later.
|
| 35 |
+
|
| 36 |
+
Artifacts (shipped in ``ablit/`` and mounted at /opt/glm53/ablit):
|
| 37 |
+
LAYER_MAP.json layer/shard map + published recipe
|
| 38 |
+
refusal_direction_glm53_dealign_late.pt published dealign direction
|
| 39 |
+
refusal_direction_glm53_bf_oproj.pt blackfrost direction (alpha_ref 3.0)
|
| 40 |
+
|
| 41 |
+
Source: drowzeys/keys-GLM-5.3-Flash-NVFP4-ablit-l15-45-anchorstock
|
| 42 |
+
(published method "dealign-oproj-transplant": layers 15-45 edited, 0-14 kept
|
| 43 |
+
as stock safety anchors, MTP block included).
|
| 44 |
+
|
| 45 |
+
Env knobs (see .env.example):
|
| 46 |
+
ABLIT=1 enable (default off — hook is a no-op)
|
| 47 |
+
ABLIT_DIR artifact dir (default /opt/glm53/ablit)
|
| 48 |
+
ABLIT_METHOD auto | transplant | proj (default auto: transplant
|
| 49 |
+
when ablit/transplant/ is populated, else proj)
|
| 50 |
+
ABLIT_DIRECTION dealign | bf_oproj | /path/to/dir.pt (default dealign)
|
| 51 |
+
ABLIT_LAYERS inclusive ranges, e.g. "15-45" or "15,17-19"
|
| 52 |
+
ABLIT_ALPHA projection scale (default 3.0)
|
| 53 |
+
ABLIT_INCLUDE_MTP also edit the checkpoint MTP block when it exists
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
from __future__ import annotations
|
| 57 |
+
|
| 58 |
+
import json
|
| 59 |
+
import math
|
| 60 |
+
import os
|
| 61 |
+
import re
|
| 62 |
+
from pathlib import Path
|
| 63 |
+
from typing import Any
|
| 64 |
+
|
| 65 |
+
import torch
|
| 66 |
+
|
| 67 |
+
def _tp_world() -> int:
|
| 68 |
+
try:
|
| 69 |
+
from vllm.distributed import get_tensor_model_parallel_world_size
|
| 70 |
+
return get_tensor_model_parallel_world_size()
|
| 71 |
+
except Exception: # standalone / tests
|
| 72 |
+
return 1
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def _tp_rank() -> int:
|
| 76 |
+
try:
|
| 77 |
+
from vllm.distributed import get_tensor_model_parallel_rank
|
| 78 |
+
return get_tensor_model_parallel_rank()
|
| 79 |
+
except Exception: # standalone / tests
|
| 80 |
+
return 0
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
try: # inside the vLLM image
|
| 84 |
+
from vllm.logger import init_logger
|
| 85 |
+
|
| 86 |
+
logger = init_logger(__name__)
|
| 87 |
+
except Exception: # standalone (tests)
|
| 88 |
+
import logging
|
| 89 |
+
|
| 90 |
+
logger = logging.getLogger("glm53_ablit")
|
| 91 |
+
|
| 92 |
+
DEFAULT_ABLIT_DIR = "/opt/glm53/ablit"
|
| 93 |
+
TRANSPLANT_SUBDIR = "transplant"
|
| 94 |
+
|
| 95 |
+
DIRECTION_FILES = {
|
| 96 |
+
"dealign": "refusal_direction_glm53_dealign_late.pt",
|
| 97 |
+
"bf_oproj": "refusal_direction_glm53_bf_oproj.pt",
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
# paths inside the hooked model that carry an o_proj to edit
|
| 101 |
+
_LAYER_O_PROJ_RE = re.compile(r"(?:^|\.)layers\.(\d+)\.self_attn\.o_proj$")
|
| 102 |
+
_MTP_O_PROJ_RE = re.compile(r"(?:^|\.)layers\.(\d+)\.mtp_block\.self_attn\.o_proj$")
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
class AblitError(RuntimeError):
|
| 106 |
+
"""ABLIT was explicitly enabled but the edit cannot be applied."""
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def env_flag(name: str, default: bool = False) -> bool:
|
| 110 |
+
val = os.environ.get(name)
|
| 111 |
+
if val is None or val == "":
|
| 112 |
+
return default
|
| 113 |
+
return val.strip().lower() in ("1", "true", "yes", "on")
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def parse_layers(spec: str) -> list[int]:
|
| 117 |
+
"""Parse "15-45" / "15,17-19" into a sorted list of layer indices."""
|
| 118 |
+
layers: set[int] = set()
|
| 119 |
+
for part in spec.split(","):
|
| 120 |
+
part = part.strip()
|
| 121 |
+
if not part:
|
| 122 |
+
continue
|
| 123 |
+
if "-" in part:
|
| 124 |
+
lo_s, hi_s = part.split("-", 1)
|
| 125 |
+
try:
|
| 126 |
+
lo, hi = int(lo_s), int(hi_s)
|
| 127 |
+
except ValueError as exc:
|
| 128 |
+
raise AblitError(f"ABLIT_LAYERS: bad range {part!r}") from exc
|
| 129 |
+
if hi < lo:
|
| 130 |
+
raise AblitError(f"ABLIT_LAYERS: inverted range {part!r}")
|
| 131 |
+
layers.update(range(lo, hi + 1))
|
| 132 |
+
else:
|
| 133 |
+
try:
|
| 134 |
+
layers.add(int(part))
|
| 135 |
+
except ValueError as exc:
|
| 136 |
+
raise AblitError(f"ABLIT_LAYERS: bad index {part!r}") from exc
|
| 137 |
+
if not layers:
|
| 138 |
+
raise AblitError("ABLIT_LAYERS resolved to an empty set")
|
| 139 |
+
return sorted(layers)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def resolve_direction_path(ablit_dir: str, direction: str) -> Path:
|
| 143 |
+
if "/" in direction: # explicit path to a .pt
|
| 144 |
+
return Path(direction)
|
| 145 |
+
fname = DIRECTION_FILES.get(direction)
|
| 146 |
+
if fname is None:
|
| 147 |
+
raise AblitError(
|
| 148 |
+
f"ABLIT_DIRECTION={direction!r} not one of "
|
| 149 |
+
f"{sorted(DIRECTION_FILES)} (or a .pt path)"
|
| 150 |
+
)
|
| 151 |
+
return Path(ablit_dir) / fname
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def load_direction(path: Path) -> torch.Tensor:
|
| 155 |
+
try:
|
| 156 |
+
obj = torch.load(str(path), map_location="cpu", weights_only=True)
|
| 157 |
+
except Exception as exc:
|
| 158 |
+
raise AblitError(f"cannot load ablit direction {path}: {exc}") from exc
|
| 159 |
+
if not isinstance(obj, dict) or "directions" not in obj:
|
| 160 |
+
raise AblitError(f"{path}: expected a dict with 'directions'")
|
| 161 |
+
r = obj["directions"]
|
| 162 |
+
if not torch.is_tensor(r):
|
| 163 |
+
raise AblitError(f"{path}: 'directions' must be a tensor, got {type(r)}")
|
| 164 |
+
if r.dim() == 2 and r.shape[0] == 1: # some exports store a [1, N] row
|
| 165 |
+
r = r.squeeze(0)
|
| 166 |
+
if r.dim() != 1:
|
| 167 |
+
raise AblitError(f"{path}: 'directions' must be 1-D (or [1, N]), got {tuple(r.shape)}")
|
| 168 |
+
r = r.to(torch.float32)
|
| 169 |
+
norm = r.norm()
|
| 170 |
+
if not torch.isfinite(norm) or float(norm) <= 0:
|
| 171 |
+
raise AblitError(f"{path}: direction has non-positive/invalid norm {norm}")
|
| 172 |
+
return r
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def apply_to_o_proj(mod: Any, r: torch.Tensor, alpha: float) -> dict[str, Any]:
|
| 176 |
+
"""Orthogonalize one o_proj module in place. Returns a report dict."""
|
| 177 |
+
weight = getattr(mod, "weight", None)
|
| 178 |
+
if weight is None or not torch.is_tensor(weight) or weight.dim() != 2:
|
| 179 |
+
return {"edited": False, "reason": "no 2-D .weight"}
|
| 180 |
+
if weight.shape[0] != r.numel():
|
| 181 |
+
return {
|
| 182 |
+
"edited": False,
|
| 183 |
+
"reason": f"out_features={weight.shape[0]} != direction dim {r.numel()}",
|
| 184 |
+
}
|
| 185 |
+
r_dev = r.to(device=weight.device, dtype=torch.float32)
|
| 186 |
+
with torch.no_grad():
|
| 187 |
+
w32 = weight.data.to(torch.float32)
|
| 188 |
+
# W' = W - alpha * outer(r, r @ W) / ||r||^2
|
| 189 |
+
rw = r_dev @ w32 # [in_local]
|
| 190 |
+
delta = alpha * torch.outer(r_dev, rw) / float(r_dev @ r_dev)
|
| 191 |
+
w32.sub_(delta)
|
| 192 |
+
weight.data.copy_(w32.to(weight.dtype))
|
| 193 |
+
residual = float((r_dev @ weight.data.to(torch.float32)).abs().max())
|
| 194 |
+
return {
|
| 195 |
+
"edited": True,
|
| 196 |
+
"shape": tuple(weight.shape),
|
| 197 |
+
"residual_max": residual,
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def walk_o_proj(model: Any) -> list[tuple[str, int | None, Any]]:
|
| 202 |
+
"""Collect (name, layer_idx or None-if-pure-mtp, module) o_proj candidates."""
|
| 203 |
+
found: list[tuple[str, int | None, Any]] = []
|
| 204 |
+
seen: set[int] = set()
|
| 205 |
+
for name, mod in model.named_modules():
|
| 206 |
+
if id(mod) in seen:
|
| 207 |
+
continue
|
| 208 |
+
m = _LAYER_O_PROJ_RE.search(name)
|
| 209 |
+
if m is not None:
|
| 210 |
+
seen.add(id(mod))
|
| 211 |
+
found.append((name, int(m.group(1)), mod))
|
| 212 |
+
continue
|
| 213 |
+
m = _MTP_O_PROJ_RE.search(name)
|
| 214 |
+
if m is not None:
|
| 215 |
+
seen.add(id(mod))
|
| 216 |
+
found.append((name, int(m.group(1)), mod))
|
| 217 |
+
return found
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def unwrap_text_model(model: Any) -> Any:
|
| 221 |
+
"""Accept the multimodal wrapper and hand back the text model."""
|
| 222 |
+
lm = getattr(model, "language_model", None)
|
| 223 |
+
if lm is not None:
|
| 224 |
+
inner = getattr(lm, "model", None)
|
| 225 |
+
if inner is not None:
|
| 226 |
+
return inner
|
| 227 |
+
return lm
|
| 228 |
+
return model
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def apply_ablit(
|
| 232 |
+
model: Any,
|
| 233 |
+
r: torch.Tensor,
|
| 234 |
+
layers: list[int],
|
| 235 |
+
alpha: float,
|
| 236 |
+
include_mtp: bool,
|
| 237 |
+
) -> dict[str, Any]:
|
| 238 |
+
"""Edit every configured o_proj under ``model``. Fails hard on surprises."""
|
| 239 |
+
text_model = unwrap_text_model(model)
|
| 240 |
+
candidates = walk_o_proj(text_model)
|
| 241 |
+
want = set(layers)
|
| 242 |
+
report: dict[str, Any] = {
|
| 243 |
+
"edited_layers": [],
|
| 244 |
+
"skipped": [],
|
| 245 |
+
"mtp_edited": False,
|
| 246 |
+
}
|
| 247 |
+
seen_ids: set[int] = set()
|
| 248 |
+
for name, idx, mod in candidates:
|
| 249 |
+
if id(mod) in seen_ids:
|
| 250 |
+
continue
|
| 251 |
+
seen_ids.add(id(mod))
|
| 252 |
+
is_mtp = _MTP_O_PROJ_RE.search(name) is not None
|
| 253 |
+
if is_mtp:
|
| 254 |
+
if not include_mtp:
|
| 255 |
+
report["skipped"].append({"name": name, "reason": "include_mtp=0"})
|
| 256 |
+
continue
|
| 257 |
+
if idx not in want:
|
| 258 |
+
report["skipped"].append(
|
| 259 |
+
{"name": name, "reason": f"layer {idx} not in ABLIT_LAYERS"}
|
| 260 |
+
)
|
| 261 |
+
continue
|
| 262 |
+
else:
|
| 263 |
+
if idx not in want:
|
| 264 |
+
continue
|
| 265 |
+
rep = apply_to_o_proj(mod, r, alpha)
|
| 266 |
+
if rep.get("edited"):
|
| 267 |
+
if is_mtp:
|
| 268 |
+
report["mtp_edited"] = True
|
| 269 |
+
else:
|
| 270 |
+
report["edited_layers"].append(idx)
|
| 271 |
+
logger.info(
|
| 272 |
+
"ablit: orthogonalized %s shape=%s residual_max=%.3e",
|
| 273 |
+
name,
|
| 274 |
+
rep.get("shape"),
|
| 275 |
+
rep.get("residual_max", float("nan")),
|
| 276 |
+
)
|
| 277 |
+
else:
|
| 278 |
+
report["skipped"].append({"name": name, "reason": rep.get("reason")})
|
| 279 |
+
logger.warning("ablit: skipped %s: %s", name, rep.get("reason"))
|
| 280 |
+
report["edited_layers"].sort()
|
| 281 |
+
return report
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def load_transplant_tensors(
|
| 285 |
+
ablit_dir: str, layers: list[int]
|
| 286 |
+
) -> dict[int, torch.Tensor]:
|
| 287 |
+
"""Load donor o_proj tensors (full, unsharded) from ablit/transplant/."""
|
| 288 |
+
tdir = Path(ablit_dir) / TRANSPLANT_SUBDIR
|
| 289 |
+
manifest_path = tdir / "MANIFEST.json"
|
| 290 |
+
if not manifest_path.is_file():
|
| 291 |
+
raise AblitError(f"ABLIT_METHOD=transplant but {manifest_path} is missing "
|
| 292 |
+
"— run ablit/fetch_transplant.py first")
|
| 293 |
+
manifest = json.loads(manifest_path.read_text())
|
| 294 |
+
meta = {int(k): v for k, v in (manifest.get("layers") or manifest.get("tensors") or {}).items()}
|
| 295 |
+
out: dict[int, torch.Tensor] = {}
|
| 296 |
+
for L in layers:
|
| 297 |
+
if L not in meta:
|
| 298 |
+
continue
|
| 299 |
+
info = meta[L]
|
| 300 |
+
path = tdir / f"L{L}.bin"
|
| 301 |
+
if not path.is_file():
|
| 302 |
+
raise AblitError(f"transplant manifest lists L{L} but {path} is missing")
|
| 303 |
+
raw = path.read_bytes()
|
| 304 |
+
if len(raw) != int(info["nbytes"]):
|
| 305 |
+
raise AblitError(
|
| 306 |
+
f"transplant L{L}: expected {info['nbytes']} bytes, got {len(raw)} "
|
| 307 |
+
"— re-run ablit/fetch_transplant.py")
|
| 308 |
+
if info["dtype"] != "BF16":
|
| 309 |
+
raise AblitError(f"transplant L{L}: unsupported dtype {info['dtype']}")
|
| 310 |
+
t = torch.frombuffer(bytearray(raw), dtype=torch.bfloat16).reshape(
|
| 311 |
+
tuple(info["shape"]))
|
| 312 |
+
out[L] = t
|
| 313 |
+
if not out:
|
| 314 |
+
raise AblitError(
|
| 315 |
+
f"no transplant tensors under {tdir} cover ABLIT_LAYERS — "
|
| 316 |
+
"run ablit/fetch_transplant.py first")
|
| 317 |
+
return out
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
def apply_transplant(
|
| 321 |
+
model: Any,
|
| 322 |
+
donors: dict[int, torch.Tensor],
|
| 323 |
+
layers: list[int],
|
| 324 |
+
include_mtp: bool,
|
| 325 |
+
) -> dict[str, Any]:
|
| 326 |
+
"""Replace stock o_proj weights with the donor tensors (per-TP shard)."""
|
| 327 |
+
text_model = unwrap_text_model(model)
|
| 328 |
+
candidates = walk_o_proj(text_model)
|
| 329 |
+
want = set(layers)
|
| 330 |
+
world, rank = _tp_world(), _tp_rank()
|
| 331 |
+
report: dict[str, Any] = {"edited_layers": [], "skipped": [], "mtp_edited": False,
|
| 332 |
+
"deltas": {}}
|
| 333 |
+
seen_ids: set[int] = set()
|
| 334 |
+
for name, idx, mod in candidates:
|
| 335 |
+
if id(mod) in seen_ids:
|
| 336 |
+
continue
|
| 337 |
+
seen_ids.add(id(mod))
|
| 338 |
+
is_mtp = _MTP_O_PROJ_RE.search(name) is not None
|
| 339 |
+
if is_mtp and not include_mtp:
|
| 340 |
+
report["skipped"].append({"name": name, "reason": "include_mtp=0"})
|
| 341 |
+
continue
|
| 342 |
+
if idx not in want:
|
| 343 |
+
continue
|
| 344 |
+
if idx not in donors:
|
| 345 |
+
raise AblitError(
|
| 346 |
+
f"ABLIT_METHOD=transplant has no donor tensor for layer {idx} "
|
| 347 |
+
"— fetch it with ablit/fetch_transplant.py")
|
| 348 |
+
donor = donors[idx]
|
| 349 |
+
weight = getattr(mod, "weight", None)
|
| 350 |
+
if weight is None or not torch.is_tensor(weight) or weight.dim() != 2:
|
| 351 |
+
raise AblitError(f"ablit transplant: {name} has no 2-D .weight")
|
| 352 |
+
full_in = donor.shape[1]
|
| 353 |
+
local_in = weight.shape[1]
|
| 354 |
+
if donor.shape[0] != weight.shape[0] or full_in != local_in * world:
|
| 355 |
+
raise AblitError(
|
| 356 |
+
f"ablit transplant: {name} shape {tuple(weight.shape)} does not "
|
| 357 |
+
f"match donor {tuple(donor.shape)} at TP={world}")
|
| 358 |
+
shard = donor if world == 1 else donor[:, rank * local_in:(rank + 1) * local_in]
|
| 359 |
+
# Donors come from torch.frombuffer on CPU; o_proj is already on the
|
| 360 |
+
# worker GPU. Compare and copy on weight's device (proj path already
|
| 361 |
+
# does r.to(weight.device); transplant forgot).
|
| 362 |
+
shard = shard.to(device=weight.device, dtype=weight.dtype)
|
| 363 |
+
with torch.no_grad():
|
| 364 |
+
w32 = weight.data.float()
|
| 365 |
+
d32 = shard.float()
|
| 366 |
+
rel_l2 = float((d32 - w32).norm() / w32.norm().clamp_min(1e-9))
|
| 367 |
+
mean_rel = float((d32 - w32).abs().mean() / w32.abs().mean().clamp_min(1e-9))
|
| 368 |
+
weight.data.copy_(shard)
|
| 369 |
+
report["edited_layers"].append(idx)
|
| 370 |
+
report["deltas"][idx] = {"rel_l2": rel_l2, "mean_rel": mean_rel}
|
| 371 |
+
if is_mtp:
|
| 372 |
+
report["mtp_edited"] = True
|
| 373 |
+
logger.info(
|
| 374 |
+
"ablit: transplanted %s (donor L%d) shape=%s rel_l2=%.4f mean_rel=%.4f",
|
| 375 |
+
name, idx, tuple(weight.shape), rel_l2, mean_rel)
|
| 376 |
+
report["edited_layers"].sort()
|
| 377 |
+
return report
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
def maybe_apply(model: Any) -> dict[str, Any] | None:
|
| 381 |
+
"""Hook entrypoint. No-op unless ABLIT=1; raises AblitError if enabled
|
| 382 |
+
but the recipe cannot be honored (fail loud beats silent stock weights)."""
|
| 383 |
+
if not env_flag("ABLIT", False):
|
| 384 |
+
return None
|
| 385 |
+
|
| 386 |
+
ablit_dir = os.environ.get("ABLIT_DIR") or DEFAULT_ABLIT_DIR
|
| 387 |
+
layers_spec = os.environ.get("ABLIT_LAYERS") or "15-45"
|
| 388 |
+
include_mtp = env_flag("ABLIT_INCLUDE_MTP", True)
|
| 389 |
+
method = (os.environ.get("ABLIT_METHOD") or "auto").strip().lower()
|
| 390 |
+
layers = parse_layers(layers_spec)
|
| 391 |
+
|
| 392 |
+
if method in ("auto", "transplant"):
|
| 393 |
+
tdir = Path(ablit_dir) / TRANSPLANT_SUBDIR
|
| 394 |
+
have_transplant = (tdir / "MANIFEST.json").is_file() and (
|
| 395 |
+
method == "transplant"
|
| 396 |
+
or any((tdir / f"L{L}.bin").is_file() for L in layers)
|
| 397 |
+
)
|
| 398 |
+
if have_transplant:
|
| 399 |
+
if method == "auto":
|
| 400 |
+
logger.info("ablit: ABLIT_METHOD=auto -> transplant "
|
| 401 |
+
"(ablit/transplant/ present; direction proj does "
|
| 402 |
+
"not align with stock o_proj on this model)")
|
| 403 |
+
donors = load_transplant_tensors(ablit_dir, layers)
|
| 404 |
+
report = apply_transplant(model, donors, layers, include_mtp)
|
| 405 |
+
if not report["edited_layers"] and not report["mtp_edited"]:
|
| 406 |
+
raise AblitError(
|
| 407 |
+
"ABLIT=1 transplant matched no o_proj — ABLIT_LAYERS="
|
| 408 |
+
f"{layers_spec} matched nothing under this model")
|
| 409 |
+
deltas = list(report["deltas"].values())
|
| 410 |
+
logger.info(
|
| 411 |
+
"ablit: ON method=transplant layers=%s edited=%s mtp=%s "
|
| 412 |
+
"mean rel_l2=%.4f (skipped=%d) — early safety-anchor layers "
|
| 413 |
+
"stay stock",
|
| 414 |
+
layers_spec,
|
| 415 |
+
report["edited_layers"],
|
| 416 |
+
report["mtp_edited"],
|
| 417 |
+
sum(d["rel_l2"] for d in deltas) / max(len(deltas), 1),
|
| 418 |
+
len(report["skipped"]),
|
| 419 |
+
)
|
| 420 |
+
return report
|
| 421 |
+
if method == "transplant":
|
| 422 |
+
raise AblitError(
|
| 423 |
+
"ABLIT_METHOD=transplant but ablit/transplant/ is missing or "
|
| 424 |
+
"empty — run: python3 ablit/fetch_transplant.py")
|
| 425 |
+
logger.info("ablit: ABLIT_METHOD=auto -> proj (no ablit/transplant/ "
|
| 426 |
+
"tensors found — falling back to direction orthogonalization)")
|
| 427 |
+
|
| 428 |
+
# ABLIT_METHOD=proj (or auto fallback): direction orthogonalization
|
| 429 |
+
direction = os.environ.get("ABLIT_DIRECTION") or "dealign"
|
| 430 |
+
try:
|
| 431 |
+
alpha = float(os.environ.get("ABLIT_ALPHA") or "3.0")
|
| 432 |
+
except ValueError as exc:
|
| 433 |
+
raise AblitError(f"ABLIT_ALPHA is not a number: {exc}") from exc
|
| 434 |
+
if not math.isfinite(alpha) or alpha <= 0:
|
| 435 |
+
raise AblitError(f"ABLIT_ALPHA must be a positive finite number, got {alpha}")
|
| 436 |
+
|
| 437 |
+
path = resolve_direction_path(ablit_dir, direction)
|
| 438 |
+
if not path.is_file():
|
| 439 |
+
raise AblitError(f"ABLIT=1 but direction file missing: {path}")
|
| 440 |
+
r = load_direction(path)
|
| 441 |
+
|
| 442 |
+
report = apply_ablit(model, r, layers, alpha, include_mtp)
|
| 443 |
+
|
| 444 |
+
if not report["edited_layers"] and not report["mtp_edited"]:
|
| 445 |
+
raise AblitError(
|
| 446 |
+
"ABLIT=1 but no o_proj was edited — ABLIT_LAYERS="
|
| 447 |
+
f"{layers_spec} matched nothing under this model"
|
| 448 |
+
)
|
| 449 |
+
logger.info(
|
| 450 |
+
"ablit: ON method=proj direction=%s (%s) alpha=%s layers=%s edited=%s "
|
| 451 |
+
"mtp=%s (skipped=%d) — early safety-anchor layers stay stock",
|
| 452 |
+
direction,
|
| 453 |
+
path.name,
|
| 454 |
+
alpha,
|
| 455 |
+
layers_spec,
|
| 456 |
+
report["edited_layers"],
|
| 457 |
+
report["mtp_edited"],
|
| 458 |
+
len(report["skipped"]),
|
| 459 |
+
)
|
| 460 |
+
return report
|
serving/donor_diff.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json, torch, os, sys
|
| 2 |
+
from safetensors import safe_open
|
| 3 |
+
M="/models/GLM-5.3-EXL3-3bpw"; D="/models/glm53-ablit-donor-derisked"
|
| 4 |
+
idx=json.load(open(f"{M}/model.safetensors.index.json"))["weight_map"]
|
| 5 |
+
man=json.load(open(f"{D}/MANIFEST.json"))["layers"]
|
| 6 |
+
print("layer rel_l2 cos donor_bits")
|
| 7 |
+
for L in range(79):
|
| 8 |
+
key=f"model.layers.{L}.self_attn.o_proj.weight"
|
| 9 |
+
if key not in idx: print(L,"no-stock"); continue
|
| 10 |
+
with safe_open(f"{M}/{idx[key]}","pt") as f: w=f.get_tensor(key).float()
|
| 11 |
+
info=man[str(L)]; raw=open(f"{D}/L{L}.bin","rb").read()
|
| 12 |
+
d=torch.frombuffer(bytearray(raw),dtype=torch.bfloat16).view(info["shape"]).float()
|
| 13 |
+
rel=((d-w).norm()/w.norm()).item(); cos=torch.nn.functional.cosine_similarity(d.flatten(),w.flatten(),dim=0).item()
|
| 14 |
+
print(f"{L:2d} {rel:.4f} {cos:.5f} {info.get('bits')}", flush=True)
|
serving/env.tp4.example
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GLM-5.3 FULL EXL3 3bpw mcg — 4x DGX Spark TP=4 (+DCP=4 for 1M) — DRAFT for ~/glm53-mia/.env.tp4 on .1
|
| 2 |
+
# ranks: .1 head, .2 / .3 / .5 workers (all on the 10.100.10.x CX7 fabric, same NIC names)
|
| 3 |
+
HEAD_IP=10.100.10.1
|
| 4 |
+
WORKER_IP=10.100.10.2
|
| 5 |
+
WORKER2_IP=10.100.10.3
|
| 6 |
+
WORKER3_IP=10.100.10.5
|
| 7 |
+
WORKER_USER=keyspark
|
| 8 |
+
NCCL_CROSS_NIC=0
|
| 9 |
+
HEAD_CX7_IF=enp1s0f1np1
|
| 10 |
+
HEAD_CX7_IB=rocep1s0f1
|
| 11 |
+
WORKER_CX7_IF=enP2p1s0f1np1
|
| 12 |
+
WORKER_CX7_IB=roceP2p1s0f1
|
| 13 |
+
WORKER2_CX7_IF=enp1s0f1np1
|
| 14 |
+
WORKER2_CX7_IB=rocep1s0f1
|
| 15 |
+
WORKER3_CX7_IF=enp1s0f1np1
|
| 16 |
+
WORKER3_CX7_IB=rocep1s0f1
|
| 17 |
+
NCCL_IB_GID_INDEX=3
|
| 18 |
+
NCCL_DEBUG=WARN
|
| 19 |
+
|
| 20 |
+
TP=4
|
| 21 |
+
NNODES=4
|
| 22 |
+
PORT=8888
|
| 23 |
+
MASTER_PORT=29521
|
| 24 |
+
CONTAINER_HEAD=glm53-exl3-tp4-head
|
| 25 |
+
CONTAINER_WORKER=glm53-exl3-tp4-w1
|
| 26 |
+
CONTAINER_WORKER2=glm53-exl3-tp4-w2
|
| 27 |
+
CONTAINER_WORKER3=glm53-exl3-tp4-w3
|
| 28 |
+
|
| 29 |
+
# model: quantized on .3 -> exported over NFS as /mnt/models-node2/<name> on every node
|
| 30 |
+
LOCAL_MODEL_DIR=/mnt/models-node2/GLM-5.3-EXL3-3bpw
|
| 31 |
+
MODEL=keys/GLM-5.3-EXL3-3bpw
|
| 32 |
+
SERVED_MODEL_NAME=GLM-5.3-EXL3
|
| 33 |
+
IMAGE=glm53-exl3-tp4:keys3
|
| 34 |
+
QUANTIZATION=exl3
|
| 35 |
+
KV_CACHE_DTYPE=fp8
|
| 36 |
+
MAX_MODEL_LEN=1000000
|
| 37 |
+
GPU_MEM_UTIL=0.82
|
| 38 |
+
MAX_NUM_SEQS=8
|
| 39 |
+
MAX_NUM_BATCHED_TOKENS=4096
|
| 40 |
+
SPEC_METHOD=mtp
|
| 41 |
+
LANGUAGE_MODEL_ONLY=1
|
| 42 |
+
ENFORCE_EAGER=0
|
| 43 |
+
EXTRA_ARGS="--decode-context-parallel-size 4 --kv-cache-memory-bytes 14092861440"
|
| 44 |
+
LOCAL_DFLASH_DIR=/mnt/models-node2/GLM-5.3-DFlash2
|
| 45 |
+
DFLASH_TOKENS=7
|
| 46 |
+
GLM53_MIXED_PREFILL_CHUNK=2048
|
| 47 |
+
MTP_TOKENS=3
|
serving/exl3.py
ADDED
|
@@ -0,0 +1,862 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""EXL3/MCG trellis quantization for GLM-5.3-Flash routed experts.
|
| 3 |
+
|
| 4 |
+
Checkpoint ABI (brandonmusic/GLM-5.3-Flash-tr3-4bpw):
|
| 5 |
+
quant_method=exl3, codebook=mcg, scope=glm53_routed_experts_only
|
| 6 |
+
per expert matrix: trellis (int16) + suh/svh (fp16) + mcg (int32 marker)
|
| 7 |
+
|
| 8 |
+
Non-routed tensors stay native (UnquantizedLinearMethod). Experts never
|
| 9 |
+
expand to a persistent BF16 weight; LinearEXL3 / exllamav3_ext runs the
|
| 10 |
+
trellis GEMM. TP=2 shards gate/up column-wise and down row-wise; the MoE
|
| 11 |
+
runner all-reduces the combined output.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import importlib
|
| 17 |
+
import importlib.util
|
| 18 |
+
import os
|
| 19 |
+
import sys
|
| 20 |
+
import types
|
| 21 |
+
from pathlib import Path
|
| 22 |
+
from typing import TYPE_CHECKING, Any
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
import torch.nn.functional as F
|
| 26 |
+
from torch.nn.parameter import Parameter
|
| 27 |
+
|
| 28 |
+
from vllm.logger import init_logger
|
| 29 |
+
from vllm.model_executor.layers.fused_moe.config import FusedMoEQuantConfig
|
| 30 |
+
from vllm.model_executor.layers.fused_moe.fused_moe_method_base import (
|
| 31 |
+
FusedMoEMethodBase,
|
| 32 |
+
)
|
| 33 |
+
from vllm.model_executor.layers.linear import LinearBase, UnquantizedLinearMethod
|
| 34 |
+
from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
|
| 35 |
+
from vllm.model_executor.layers.quantization import register_quantization_config
|
| 36 |
+
from vllm.model_executor.utils import set_weight_attrs
|
| 37 |
+
|
| 38 |
+
if TYPE_CHECKING:
|
| 39 |
+
from vllm.model_executor.layers.fused_moe.routed_experts import RoutedExperts
|
| 40 |
+
from vllm.model_executor.layers.fused_moe.runner.shared_experts import (
|
| 41 |
+
SharedExperts,
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
logger = init_logger(__name__)
|
| 45 |
+
|
| 46 |
+
EXLLAMAV3_COMMIT = "c5d9c657966ffeeaa9353f0cc899f18629da4a13"
|
| 47 |
+
EXLLAMAV3_VERSION = "0.0.43"
|
| 48 |
+
MCG_MULTIPLIER = 0xCBAC1FED
|
| 49 |
+
MCG_MARKER_SIGNED_INT32 = -877912083
|
| 50 |
+
# KEYS PATCH: exllamav3 "mul1" codebook (converter default; 0x83DCD12D marker). LinearEXL3 and
|
| 51 |
+
# exllamav3_ext.exl3_moe in this image already take (mcg, mul1) flag pairs per matrix.
|
| 52 |
+
MUL1_MULTIPLIER = 0x83DCD12D
|
| 53 |
+
MUL1_MARKER_SIGNED_INT32 = -2082680531
|
| 54 |
+
EXL3_SUFFIXES = ("trellis", "suh", "svh", "mcg", "mul1")
|
| 55 |
+
SWIGLU_LIMIT_DEFAULT = 10.0
|
| 56 |
+
TEMP_ROWS_FUSED = 128
|
| 57 |
+
MOE_ACT_SILU = 0
|
| 58 |
+
# Shared fused scratch: decode is sequential across layers.
|
| 59 |
+
_FUSED_TEMP_CACHE: dict[tuple, tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]] = {}
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _narrow_tp(tensor: torch.Tensor, dim: int, tp_rank: int, tp_size: int) -> torch.Tensor:
|
| 63 |
+
if tp_size <= 1:
|
| 64 |
+
return tensor
|
| 65 |
+
size = int(tensor.shape[dim])
|
| 66 |
+
if size % tp_size:
|
| 67 |
+
raise ValueError(
|
| 68 |
+
f"EXL3 TP shard: dim {dim} size {size} is not divisible by tp={tp_size}"
|
| 69 |
+
)
|
| 70 |
+
chunk = size // tp_size
|
| 71 |
+
return tensor.narrow(dim, chunk * tp_rank, chunk).contiguous()
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def shard_exl3_col(loaded: torch.Tensor, suffix: str, tp_rank: int, tp_size: int) -> torch.Tensor:
|
| 75 |
+
"""Gate/up: trellis dim 1 and svh dim 0 are column-parallel."""
|
| 76 |
+
if suffix == "trellis":
|
| 77 |
+
return _narrow_tp(loaded, 1, tp_rank, tp_size)
|
| 78 |
+
if suffix == "svh":
|
| 79 |
+
return _narrow_tp(loaded, 0, tp_rank, tp_size)
|
| 80 |
+
return loaded.contiguous()
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def shard_exl3_row(loaded: torch.Tensor, suffix: str, tp_rank: int, tp_size: int) -> torch.Tensor:
|
| 84 |
+
"""Down: trellis dim 0 and suh dim 0 are row-parallel."""
|
| 85 |
+
if suffix == "trellis":
|
| 86 |
+
return _narrow_tp(loaded, 0, tp_rank, tp_size)
|
| 87 |
+
if suffix == "suh":
|
| 88 |
+
return _narrow_tp(loaded, 0, tp_rank, tp_size)
|
| 89 |
+
return loaded.contiguous()
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def _install_exllamav3_namespace() -> None:
|
| 93 |
+
"""Load LinearEXL3 without running exllamav3/__init__.py (FlashAttention)."""
|
| 94 |
+
if "exllamav3.modules.quant.exl3" in sys.modules:
|
| 95 |
+
return
|
| 96 |
+
import exllamav3_ext # noqa: F401 — compiled extension must exist
|
| 97 |
+
|
| 98 |
+
spec = importlib.util.find_spec("exllamav3")
|
| 99 |
+
if spec is None or not spec.submodule_search_locations:
|
| 100 |
+
raise RuntimeError("exllamav3 package is not installed in this image")
|
| 101 |
+
package_root = Path(list(spec.submodule_search_locations)[0])
|
| 102 |
+
|
| 103 |
+
# Stub only packages whose __init__.py pulls FlashAttention / serving extras.
|
| 104 |
+
# Leave .ext, .util, and .modules.quant as real modules so LinearEXL3 loads.
|
| 105 |
+
for name, path in (
|
| 106 |
+
("exllamav3", package_root),
|
| 107 |
+
("exllamav3.modules", package_root / "modules"),
|
| 108 |
+
("exllamav3.model", package_root / "model"),
|
| 109 |
+
):
|
| 110 |
+
if name in sys.modules:
|
| 111 |
+
continue
|
| 112 |
+
module = types.ModuleType(name)
|
| 113 |
+
module.__file__ = str(path / "__init__.py")
|
| 114 |
+
module.__package__ = name
|
| 115 |
+
module.__path__ = [str(path)]
|
| 116 |
+
sys.modules[name] = module
|
| 117 |
+
|
| 118 |
+
if "exllamav3.model.config" not in sys.modules:
|
| 119 |
+
config = types.ModuleType("exllamav3.model.config")
|
| 120 |
+
config.__file__ = str(package_root / "model/config.py")
|
| 121 |
+
config.__package__ = "exllamav3.model"
|
| 122 |
+
config.Config = type("Config", (), {})
|
| 123 |
+
# KEYS PATCH: exllamav3 >= 1.x LinearEXL3(config=None) does
|
| 124 |
+
# `from ...model.config import NullConfig` and reads
|
| 125 |
+
# config.infer_params.no_reconstruct in forward().
|
| 126 |
+
class _InferParams:
|
| 127 |
+
no_reconstruct = False
|
| 128 |
+
mgemm_K_threshold = 0
|
| 129 |
+
mgemm_n_threshold = 0
|
| 130 |
+
|
| 131 |
+
class _NullConfig:
|
| 132 |
+
def __init__(self):
|
| 133 |
+
self.infer_params = _InferParams()
|
| 134 |
+
|
| 135 |
+
config.InferParams = _InferParams
|
| 136 |
+
config.NullConfig = _NullConfig
|
| 137 |
+
sys.modules[config.__name__] = config
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def load_linear_exl3_cls():
|
| 141 |
+
_install_exllamav3_namespace()
|
| 142 |
+
return importlib.import_module("exllamav3.modules.quant.exl3").LinearEXL3
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def make_linear_exl3(
|
| 146 |
+
trellis: torch.Tensor,
|
| 147 |
+
suh: torch.Tensor,
|
| 148 |
+
svh: torch.Tensor,
|
| 149 |
+
mcg: torch.Tensor,
|
| 150 |
+
*,
|
| 151 |
+
mul1: bool = False,
|
| 152 |
+
out_dtype: torch.dtype = torch.float16,
|
| 153 |
+
):
|
| 154 |
+
"""Build a LinearEXL3 over already-sharded packed tensors. No BF16 expand.
|
| 155 |
+
KEYS PATCH: `mcg` is the codebook marker tensor; with mul1=True it is passed as the
|
| 156 |
+
mul1 marker instead (exllamav3 LinearEXL3 accepts either)."""
|
| 157 |
+
cls = load_linear_exl3_cls()
|
| 158 |
+
marker = mcg.contiguous()
|
| 159 |
+
return cls(
|
| 160 |
+
config=None,
|
| 161 |
+
in_features=int(suh.numel()),
|
| 162 |
+
out_features=int(svh.numel()),
|
| 163 |
+
trellis=trellis.contiguous(),
|
| 164 |
+
suh=suh.contiguous(),
|
| 165 |
+
svh=svh.contiguous(),
|
| 166 |
+
mcg=None if mul1 else marker,
|
| 167 |
+
mul1=marker if mul1 else None,
|
| 168 |
+
out_dtype=out_dtype,
|
| 169 |
+
transformers_fix=True,
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def execute_exl3_linear(
|
| 174 |
+
x: torch.Tensor,
|
| 175 |
+
trellis: torch.Tensor,
|
| 176 |
+
suh: torch.Tensor,
|
| 177 |
+
svh: torch.Tensor,
|
| 178 |
+
mcg: torch.Tensor,
|
| 179 |
+
*,
|
| 180 |
+
out_dtype: torch.dtype = torch.float32,
|
| 181 |
+
) -> torch.Tensor:
|
| 182 |
+
"""Real EXL3 expert GEMM entry (LinearEXL3 / exllamav3_ext)."""
|
| 183 |
+
inner = make_linear_exl3(trellis, suh, svh, mcg, out_dtype=torch.float16)
|
| 184 |
+
return inner.forward(x.contiguous().half(), {}, out_dtype=out_dtype)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def fused_moe_enabled() -> bool:
|
| 188 |
+
return os.environ.get("EXL3_FUSED_MOE", "1") != "0"
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def load_exllamav3_ext():
|
| 192 |
+
import exllamav3_ext
|
| 193 |
+
|
| 194 |
+
return exllamav3_ext
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def _exl3_moe_accepts_num_active(fn) -> bool:
|
| 198 |
+
try:
|
| 199 |
+
import inspect
|
| 200 |
+
|
| 201 |
+
if "num_active" in inspect.signature(fn).parameters:
|
| 202 |
+
return True
|
| 203 |
+
except (TypeError, ValueError):
|
| 204 |
+
pass
|
| 205 |
+
doc = getattr(fn, "__doc__", None) or ""
|
| 206 |
+
return "num_active" in doc or "arg29" in doc or doc.count("arg") >= 30
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def pin_exl3_expert_map(
|
| 210 |
+
layer: torch.nn.Module, device: torch.device
|
| 211 |
+
) -> torch.Tensor | None:
|
| 212 |
+
"""Move expert_map onto `device` once. CUDA graph capture forbids a CPU→GPU copy."""
|
| 213 |
+
emap = getattr(layer, "expert_map", None)
|
| 214 |
+
if emap is None:
|
| 215 |
+
return None
|
| 216 |
+
if emap.device != device or emap.dtype != torch.long:
|
| 217 |
+
layer.expert_map = emap.to(device=device, dtype=torch.long)
|
| 218 |
+
return layer.expert_map
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def map_topk_to_local(
|
| 222 |
+
ids: torch.Tensor,
|
| 223 |
+
n_local: int,
|
| 224 |
+
expert_map: torch.Tensor | None,
|
| 225 |
+
) -> torch.Tensor:
|
| 226 |
+
"""ids (T, K) global expert ids → local ids, invalid/non-local → n_local sentinel.
|
| 227 |
+
|
| 228 |
+
`expert_map` must already live on `ids.device` (see pin_exl3_expert_map).
|
| 229 |
+
"""
|
| 230 |
+
flat = ids.reshape(-1)
|
| 231 |
+
if expert_map is None:
|
| 232 |
+
invalid = (flat < 0) | (flat >= n_local)
|
| 233 |
+
return torch.where(invalid, flat.new_full(flat.shape, n_local), flat)
|
| 234 |
+
if expert_map.device != flat.device or expert_map.dtype != torch.long:
|
| 235 |
+
raise RuntimeError(
|
| 236 |
+
"EXL3 expert_map is not pinned to the hidden-state device; "
|
| 237 |
+
"call pin_exl3_expert_map before fused apply (CUDA graphs forbid the copy)"
|
| 238 |
+
)
|
| 239 |
+
n_global = int(expert_map.numel())
|
| 240 |
+
safe = flat.clamp(min=0, max=max(n_global - 1, 0))
|
| 241 |
+
mapped = expert_map[safe] if n_global else flat.new_full(flat.shape, n_local)
|
| 242 |
+
invalid = (flat < 0) | (flat >= n_global) | (mapped < 0) | (mapped >= n_local)
|
| 243 |
+
return torch.where(invalid, flat.new_full(flat.shape, n_local), mapped)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def apply_exl3_python_loop(
|
| 247 |
+
x2d: torch.Tensor,
|
| 248 |
+
ids: torch.Tensor,
|
| 249 |
+
weights: torch.Tensor,
|
| 250 |
+
inners: list[dict[str, Any]],
|
| 251 |
+
expert_map: torch.Tensor | None,
|
| 252 |
+
limit: float,
|
| 253 |
+
*,
|
| 254 |
+
only_experts: set[int] | None = None,
|
| 255 |
+
out: torch.Tensor | None = None,
|
| 256 |
+
) -> torch.Tensor:
|
| 257 |
+
"""Unique-expert LinearEXL3 loop. `only_experts` is local ids (fat-expert fallback)."""
|
| 258 |
+
tokens, hidden = x2d.shape
|
| 259 |
+
if out is None:
|
| 260 |
+
out = torch.zeros(tokens, hidden, dtype=torch.float32, device=x2d.device)
|
| 261 |
+
unique = torch.unique(ids)
|
| 262 |
+
for raw in unique.tolist():
|
| 263 |
+
e_raw = int(raw)
|
| 264 |
+
if e_raw < 0:
|
| 265 |
+
continue
|
| 266 |
+
e = e_raw
|
| 267 |
+
if expert_map is not None:
|
| 268 |
+
mapped = int(expert_map[e].item()) if expert_map.numel() > e else e
|
| 269 |
+
if mapped < 0:
|
| 270 |
+
continue
|
| 271 |
+
e = mapped
|
| 272 |
+
if e >= len(inners):
|
| 273 |
+
continue
|
| 274 |
+
if only_experts is not None and e not in only_experts:
|
| 275 |
+
continue
|
| 276 |
+
token_idx, k_pos = (ids == int(raw)).nonzero(as_tuple=True)
|
| 277 |
+
h = x2d.index_select(0, token_idx)
|
| 278 |
+
pack = inners[e]
|
| 279 |
+
gate = pack["gate"].forward(h.contiguous().half(), {}, out_dtype=torch.float32)
|
| 280 |
+
up = pack["up"].forward(h.contiguous().half(), {}, out_dtype=torch.float32)
|
| 281 |
+
act = F.silu(gate.clamp(max=limit)) * up.clamp(min=-limit, max=limit)
|
| 282 |
+
down = pack["down"].forward(act.contiguous().half(), {}, out_dtype=torch.float32)
|
| 283 |
+
scale = weights[token_idx, k_pos].unsqueeze(-1).to(dtype=torch.float32)
|
| 284 |
+
out.index_add_(0, token_idx, down * scale)
|
| 285 |
+
return out
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
def build_exl3_fused_state(layer: torch.nn.Module, inners: list[dict[str, Any]]) -> None:
|
| 289 |
+
"""Pointer tables + fused temps, once after load. No per-token alloc."""
|
| 290 |
+
import exllamav3_ext
|
| 291 |
+
|
| 292 |
+
device = layer.w13_trellis.device
|
| 293 |
+
n_exp = len(inners)
|
| 294 |
+
hidden = int(layer._exl3_hidden_size)
|
| 295 |
+
intermediate = int(layer._exl3_intermediate_local)
|
| 296 |
+
|
| 297 |
+
def _ptrs(which: str, attr: str) -> torch.Tensor:
|
| 298 |
+
return torch.tensor(
|
| 299 |
+
[int(getattr(pack[which], attr).data_ptr()) for pack in inners],
|
| 300 |
+
dtype=torch.int64,
|
| 301 |
+
device=device,
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
layer._exl3_ptrs = {
|
| 305 |
+
"gate_trellis": _ptrs("gate", "trellis"),
|
| 306 |
+
"gate_suh": _ptrs("gate", "suh"),
|
| 307 |
+
"gate_svh": _ptrs("gate", "svh"),
|
| 308 |
+
"up_trellis": _ptrs("up", "trellis"),
|
| 309 |
+
"up_suh": _ptrs("up", "suh"),
|
| 310 |
+
"up_svh": _ptrs("up", "svh"),
|
| 311 |
+
"down_trellis": _ptrs("down", "trellis"),
|
| 312 |
+
"down_suh": _ptrs("down", "suh"),
|
| 313 |
+
"down_svh": _ptrs("down", "svh"),
|
| 314 |
+
}
|
| 315 |
+
idx = int(device.index) if device.index is not None else 0
|
| 316 |
+
concurrency = int(exllamav3_ext.exl3_moe_max_concurrency(idx))
|
| 317 |
+
if concurrency < 1:
|
| 318 |
+
concurrency = 1
|
| 319 |
+
key = (str(device), hidden, intermediate, concurrency)
|
| 320 |
+
temps = _FUSED_TEMP_CACHE.get(key)
|
| 321 |
+
if temps is None:
|
| 322 |
+
temps = (
|
| 323 |
+
torch.empty((concurrency, TEMP_ROWS_FUSED, hidden), dtype=torch.float16, device=device),
|
| 324 |
+
torch.empty((concurrency, TEMP_ROWS_FUSED, hidden), dtype=torch.float16, device=device),
|
| 325 |
+
torch.empty((concurrency, TEMP_ROWS_FUSED, intermediate), dtype=torch.float16, device=device),
|
| 326 |
+
torch.empty((concurrency, TEMP_ROWS_FUSED, intermediate), dtype=torch.float16, device=device),
|
| 327 |
+
)
|
| 328 |
+
_FUSED_TEMP_CACHE[key] = temps
|
| 329 |
+
layer._exl3_fused_temps = temps
|
| 330 |
+
layer._exl3_fused_concurrency = concurrency
|
| 331 |
+
layer._exl3_k = int(layer._exl3_bits)
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
def apply_exl3_fused_moe(
|
| 335 |
+
x2d: torch.Tensor,
|
| 336 |
+
ids: torch.Tensor,
|
| 337 |
+
weights: torch.Tensor,
|
| 338 |
+
layer: torch.nn.Module,
|
| 339 |
+
inners: list[dict[str, Any]],
|
| 340 |
+
expert_map: torch.Tensor | None,
|
| 341 |
+
limit: float,
|
| 342 |
+
) -> torch.Tensor:
|
| 343 |
+
"""One exl3_moe launch per layer. Experts with count > 128 fall back to LinearEXL3."""
|
| 344 |
+
import exllamav3_ext
|
| 345 |
+
|
| 346 |
+
tokens, hidden = x2d.shape
|
| 347 |
+
n_exp = len(inners)
|
| 348 |
+
ptrs = getattr(layer, "_exl3_ptrs", None)
|
| 349 |
+
temps = getattr(layer, "_exl3_fused_temps", None)
|
| 350 |
+
if not ptrs or temps is None:
|
| 351 |
+
raise RuntimeError("EXL3 fused pointer tables were not built after weight load")
|
| 352 |
+
|
| 353 |
+
local = map_topk_to_local(ids, n_exp, expert_map)
|
| 354 |
+
topk = int(ids.shape[-1])
|
| 355 |
+
flat_token = torch.arange(tokens, device=x2d.device, dtype=torch.long).repeat_interleave(topk)
|
| 356 |
+
flat_weight = weights.reshape(-1).to(dtype=torch.float16)
|
| 357 |
+
order = local.argsort()
|
| 358 |
+
token_sorted = flat_token[order]
|
| 359 |
+
weight_sorted = flat_weight[order]
|
| 360 |
+
# scatter_add stays on GPU. torch.bincount can host-stage and break CUDA graphs.
|
| 361 |
+
expert_count = torch.zeros(n_exp + 1, dtype=torch.long, device=local.device)
|
| 362 |
+
expert_count.scatter_add_(
|
| 363 |
+
0, local.long(), torch.ones(local.shape, dtype=torch.long, device=local.device)
|
| 364 |
+
)
|
| 365 |
+
out = torch.zeros(tokens, hidden, dtype=torch.float32, device=x2d.device)
|
| 366 |
+
xh = x2d.contiguous().half()
|
| 367 |
+
|
| 368 |
+
counts = expert_count[:n_exp]
|
| 369 |
+
fn = exllamav3_ext.exl3_moe
|
| 370 |
+
# -1 = unknown active count: max-concurrency grid, no .item() host sync.
|
| 371 |
+
n_active_host = -1 if _exl3_moe_accepts_num_active(fn) else None
|
| 372 |
+
|
| 373 |
+
k = int(getattr(layer, "_exl3_k", 4))
|
| 374 |
+
_m1 = bool(getattr(layer, "_exl3_mul1", False))
|
| 375 |
+
args = (
|
| 376 |
+
xh,
|
| 377 |
+
out,
|
| 378 |
+
expert_count,
|
| 379 |
+
token_sorted,
|
| 380 |
+
weight_sorted,
|
| 381 |
+
temps[0],
|
| 382 |
+
temps[1],
|
| 383 |
+
temps[2],
|
| 384 |
+
temps[3],
|
| 385 |
+
MOE_ACT_SILU,
|
| 386 |
+
k,
|
| 387 |
+
k,
|
| 388 |
+
k,
|
| 389 |
+
ptrs["gate_trellis"],
|
| 390 |
+
ptrs["gate_suh"],
|
| 391 |
+
ptrs["gate_svh"],
|
| 392 |
+
ptrs["up_trellis"],
|
| 393 |
+
ptrs["up_suh"],
|
| 394 |
+
ptrs["up_svh"],
|
| 395 |
+
ptrs["down_trellis"],
|
| 396 |
+
ptrs["down_suh"],
|
| 397 |
+
ptrs["down_svh"],
|
| 398 |
+
# KEYS PATCH: (mcg, mul1) flag pairs for gate / up / down, per-layer codebook.
|
| 399 |
+
not _m1,
|
| 400 |
+
_m1,
|
| 401 |
+
not _m1,
|
| 402 |
+
_m1,
|
| 403 |
+
not _m1,
|
| 404 |
+
_m1,
|
| 405 |
+
float(limit),
|
| 406 |
+
)
|
| 407 |
+
if n_active_host is not None:
|
| 408 |
+
fn(*args, n_active_host)
|
| 409 |
+
else:
|
| 410 |
+
fn(*args)
|
| 411 |
+
|
| 412 |
+
if tokens > TEMP_ROWS_FUSED:
|
| 413 |
+
fat = (counts > TEMP_ROWS_FUSED).nonzero(as_tuple=False).view(-1)
|
| 414 |
+
if fat.numel():
|
| 415 |
+
apply_exl3_python_loop(
|
| 416 |
+
x2d,
|
| 417 |
+
ids,
|
| 418 |
+
weights,
|
| 419 |
+
inners,
|
| 420 |
+
expert_map,
|
| 421 |
+
limit,
|
| 422 |
+
only_experts=set(int(i) for i in fat.tolist()),
|
| 423 |
+
out=out,
|
| 424 |
+
)
|
| 425 |
+
return out
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
def apply_exl3_experts(
|
| 429 |
+
x: torch.Tensor,
|
| 430 |
+
topk_ids: torch.Tensor,
|
| 431 |
+
topk_weights: torch.Tensor,
|
| 432 |
+
layer: torch.nn.Module,
|
| 433 |
+
*,
|
| 434 |
+
limit: float = SWIGLU_LIMIT_DEFAULT,
|
| 435 |
+
fused: bool | None = None,
|
| 436 |
+
) -> torch.Tensor:
|
| 437 |
+
"""Shipped routed-expert apply. `fused=None` honors EXL3_FUSED_MOE."""
|
| 438 |
+
inners = getattr(layer, "_exl3_inners", None)
|
| 439 |
+
if not inners:
|
| 440 |
+
raise RuntimeError("EXL3 experts were not built after weight load")
|
| 441 |
+
tokens, hidden = x.shape[-2], x.shape[-1]
|
| 442 |
+
x2d = x.reshape(tokens, hidden)
|
| 443 |
+
ids = topk_ids.reshape(tokens, -1).to(torch.long)
|
| 444 |
+
weights = topk_weights.reshape(tokens, -1)
|
| 445 |
+
expert_map = pin_exl3_expert_map(layer, x2d.device)
|
| 446 |
+
have_ptrs = bool(getattr(layer, "_exl3_ptrs", None))
|
| 447 |
+
if fused is True and not have_ptrs:
|
| 448 |
+
raise RuntimeError("EXL3 fused apply requested but pointer tables are missing")
|
| 449 |
+
use_fused = (fused_moe_enabled() if fused is None else bool(fused)) and have_ptrs
|
| 450 |
+
if use_fused:
|
| 451 |
+
try:
|
| 452 |
+
import exllamav3_ext
|
| 453 |
+
|
| 454 |
+
use_fused = hasattr(exllamav3_ext, "exl3_moe")
|
| 455 |
+
except Exception:
|
| 456 |
+
use_fused = False
|
| 457 |
+
if use_fused:
|
| 458 |
+
out = apply_exl3_fused_moe(x2d, ids, weights, layer, inners, expert_map, limit)
|
| 459 |
+
layer._exl3_last_apply = "fused"
|
| 460 |
+
else:
|
| 461 |
+
out = apply_exl3_python_loop(x2d, ids, weights, inners, expert_map, limit)
|
| 462 |
+
layer._exl3_last_apply = "loop"
|
| 463 |
+
return out.to(dtype=x.dtype)
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
def _suffix_from_mapped_name(weight_name: str) -> str:
|
| 467 |
+
tail = weight_name.rsplit(".", 1)[-1]
|
| 468 |
+
for suffix in EXL3_SUFFIXES:
|
| 469 |
+
if tail == suffix or tail.endswith("_" + suffix):
|
| 470 |
+
return suffix
|
| 471 |
+
raise ValueError(f"not an EXL3 packed name: {weight_name}")
|
| 472 |
+
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def _is_mtp_prefix(prefix: str) -> bool:
|
| 476 |
+
"""True for layers at index >= num_hidden_layers (DeepSeek/GLM MTP blocks)."""
|
| 477 |
+
import re as _re
|
| 478 |
+
m = _re.search(r"\.layers\.(\d+)\.", prefix + ".")
|
| 479 |
+
if not m:
|
| 480 |
+
return "mtp" in prefix.lower()
|
| 481 |
+
idx = int(m.group(1))
|
| 482 |
+
n = None
|
| 483 |
+
try:
|
| 484 |
+
from vllm.config import get_current_vllm_config
|
| 485 |
+
n = int(get_current_vllm_config().model_config.hf_config.num_hidden_layers)
|
| 486 |
+
except Exception: # noqa: BLE001
|
| 487 |
+
n = None
|
| 488 |
+
if n is None:
|
| 489 |
+
n = int(os.environ.get("EXL3_MTP_LAYER_START", "78"))
|
| 490 |
+
return idx >= n
|
| 491 |
+
|
| 492 |
+
@register_quantization_config("exl3")
|
| 493 |
+
class Exl3Config(QuantizationConfig):
|
| 494 |
+
"""Routed-experts-only EXL3/MCG. Dense / shared / attention stay native."""
|
| 495 |
+
|
| 496 |
+
def __init__(
|
| 497 |
+
self,
|
| 498 |
+
bits: int = 4,
|
| 499 |
+
codebook: str = "mcg",
|
| 500 |
+
scope: str = "glm53_routed_experts_only",
|
| 501 |
+
**kwargs: Any,
|
| 502 |
+
) -> None:
|
| 503 |
+
super().__init__()
|
| 504 |
+
self.bits = int(bits)
|
| 505 |
+
self.codebook = str(codebook)
|
| 506 |
+
self.scope = str(scope)
|
| 507 |
+
self.raw_config = dict(kwargs)
|
| 508 |
+
if self.codebook not in ("mcg", "mul1", "mixed", "auto"):
|
| 509 |
+
raise ValueError(
|
| 510 |
+
f"this overlay implements codebook mcg/mul1 (per layer); got {self.codebook!r}"
|
| 511 |
+
)
|
| 512 |
+
if self.bits not in (3, 4, 5, 6):
|
| 513 |
+
raise ValueError(f"unsupported EXL3 bits={self.bits}")
|
| 514 |
+
|
| 515 |
+
def get_name(self) -> str:
|
| 516 |
+
return "exl3"
|
| 517 |
+
|
| 518 |
+
def get_supported_act_dtypes(self) -> list[torch.dtype]:
|
| 519 |
+
return [torch.bfloat16, torch.float16, torch.float32]
|
| 520 |
+
|
| 521 |
+
@classmethod
|
| 522 |
+
def get_min_capability(cls) -> int:
|
| 523 |
+
# LinearEXL3 uses CUDA >= Ampere; GB10 is SM121.
|
| 524 |
+
return 80
|
| 525 |
+
|
| 526 |
+
@staticmethod
|
| 527 |
+
def get_config_filenames() -> list[str]:
|
| 528 |
+
return ["quantization_config.json"]
|
| 529 |
+
|
| 530 |
+
@classmethod
|
| 531 |
+
def from_config(cls, config: dict[str, Any]) -> "Exl3Config":
|
| 532 |
+
skip = {
|
| 533 |
+
"bits",
|
| 534 |
+
"codebook",
|
| 535 |
+
"scope",
|
| 536 |
+
"quant_method",
|
| 537 |
+
# tr3 ships a 37 MiB per-tensor ledger; keep it off the config object.
|
| 538 |
+
"tensor_storage",
|
| 539 |
+
}
|
| 540 |
+
return cls(
|
| 541 |
+
bits=int(config.get("bits", 4)),
|
| 542 |
+
codebook=str(config.get("codebook", "mcg")),
|
| 543 |
+
scope=str(config.get("scope", "glm53_routed_experts_only")),
|
| 544 |
+
**{k: v for k, v in config.items() if k not in skip},
|
| 545 |
+
)
|
| 546 |
+
|
| 547 |
+
@classmethod
|
| 548 |
+
def override_quantization_method(
|
| 549 |
+
cls,
|
| 550 |
+
hf_quant_cfg: dict[str, Any],
|
| 551 |
+
user_quant: str | None,
|
| 552 |
+
hf_config: Any = None,
|
| 553 |
+
) -> str | None:
|
| 554 |
+
method = str((hf_quant_cfg or {}).get("quant_method", "")).lower()
|
| 555 |
+
if method == "exl3":
|
| 556 |
+
return "exl3"
|
| 557 |
+
return None
|
| 558 |
+
|
| 559 |
+
def get_quant_method(self, layer: torch.nn.Module, prefix: str):
|
| 560 |
+
from vllm.model_executor.layers.fused_moe.routed_experts import RoutedExperts
|
| 561 |
+
|
| 562 |
+
# KEYS PATCH: the MTP block (model.layers.<num_hidden_layers>+) is stored bf16
|
| 563 |
+
# (-mb 16) -> leave it to the stock unquantized MoE path so SPEC_METHOD=mtp works.
|
| 564 |
+
if isinstance(layer, RoutedExperts) and _is_mtp_prefix(prefix):
|
| 565 |
+
logger.info_once("EXL3: MTP block %s left unquantized (bf16 experts)", prefix)
|
| 566 |
+
return None
|
| 567 |
+
if isinstance(layer, RoutedExperts):
|
| 568 |
+
return Exl3MoEMethod(layer.moe_config, self)
|
| 569 |
+
if isinstance(layer, LinearBase):
|
| 570 |
+
return UnquantizedLinearMethod()
|
| 571 |
+
return None
|
| 572 |
+
|
| 573 |
+
|
| 574 |
+
class Exl3MoEMethod(FusedMoEMethodBase):
|
| 575 |
+
"""Packed MCG trellis experts: create/load packed tensors, LinearEXL3 apply."""
|
| 576 |
+
|
| 577 |
+
def __init__(self, moe, quant_config: Exl3Config) -> None:
|
| 578 |
+
super().__init__(moe)
|
| 579 |
+
self.quant_config = quant_config
|
| 580 |
+
self.bits = quant_config.bits
|
| 581 |
+
self._logged = False
|
| 582 |
+
|
| 583 |
+
def get_fused_moe_quant_config(self, layer: "RoutedExperts") -> FusedMoEQuantConfig | None:
|
| 584 |
+
return None
|
| 585 |
+
|
| 586 |
+
def create_weights(
|
| 587 |
+
self,
|
| 588 |
+
layer: "RoutedExperts",
|
| 589 |
+
num_experts: int,
|
| 590 |
+
hidden_size: int,
|
| 591 |
+
intermediate_size_per_partition: int,
|
| 592 |
+
params_dtype: torch.dtype,
|
| 593 |
+
**extra_weight_attrs,
|
| 594 |
+
) -> None:
|
| 595 |
+
del params_dtype
|
| 596 |
+
if hidden_size % 16 or intermediate_size_per_partition % 16:
|
| 597 |
+
raise ValueError(
|
| 598 |
+
"EXL3 trellis tiles are 16-wide; "
|
| 599 |
+
f"hidden={hidden_size} intermediate_local={intermediate_size_per_partition}"
|
| 600 |
+
)
|
| 601 |
+
k_words = self.bits * 16
|
| 602 |
+
in_tiles = hidden_size // 16
|
| 603 |
+
out_tiles = intermediate_size_per_partition // 16
|
| 604 |
+
|
| 605 |
+
extra = {k: v for k, v in extra_weight_attrs.items() if k != "weight_loader"}
|
| 606 |
+
|
| 607 |
+
# w13_* : stacked [expert, {gate=0, up=1}, ...] so the stock
|
| 608 |
+
# expert_params_mapping (experts.w13_ + suffix) hits these names.
|
| 609 |
+
w13_trellis = Parameter(
|
| 610 |
+
torch.empty(
|
| 611 |
+
num_experts, 2, in_tiles, out_tiles, k_words, dtype=torch.int16
|
| 612 |
+
),
|
| 613 |
+
requires_grad=False,
|
| 614 |
+
)
|
| 615 |
+
w13_suh = Parameter(
|
| 616 |
+
torch.empty(num_experts, 2, hidden_size, dtype=torch.float16),
|
| 617 |
+
requires_grad=False,
|
| 618 |
+
)
|
| 619 |
+
w13_svh = Parameter(
|
| 620 |
+
torch.empty(
|
| 621 |
+
num_experts, 2, intermediate_size_per_partition, dtype=torch.float16
|
| 622 |
+
),
|
| 623 |
+
requires_grad=False,
|
| 624 |
+
)
|
| 625 |
+
w13_mcg = Parameter(
|
| 626 |
+
torch.zeros(num_experts, 2, 1, dtype=torch.int32),
|
| 627 |
+
requires_grad=False,
|
| 628 |
+
)
|
| 629 |
+
w13_mul1 = Parameter(
|
| 630 |
+
torch.zeros(num_experts, 2, 1, dtype=torch.int32),
|
| 631 |
+
requires_grad=False,
|
| 632 |
+
)
|
| 633 |
+
w2_trellis = Parameter(
|
| 634 |
+
torch.empty(
|
| 635 |
+
num_experts, out_tiles, in_tiles, k_words, dtype=torch.int16
|
| 636 |
+
),
|
| 637 |
+
requires_grad=False,
|
| 638 |
+
)
|
| 639 |
+
w2_suh = Parameter(
|
| 640 |
+
torch.empty(
|
| 641 |
+
num_experts, intermediate_size_per_partition, dtype=torch.float16
|
| 642 |
+
),
|
| 643 |
+
requires_grad=False,
|
| 644 |
+
)
|
| 645 |
+
w2_svh = Parameter(
|
| 646 |
+
torch.empty(num_experts, hidden_size, dtype=torch.float16),
|
| 647 |
+
requires_grad=False,
|
| 648 |
+
)
|
| 649 |
+
w2_mcg = Parameter(
|
| 650 |
+
torch.zeros(num_experts, 1, dtype=torch.int32),
|
| 651 |
+
requires_grad=False,
|
| 652 |
+
)
|
| 653 |
+
w2_mul1 = Parameter(
|
| 654 |
+
torch.zeros(num_experts, 1, dtype=torch.int32),
|
| 655 |
+
requires_grad=False,
|
| 656 |
+
)
|
| 657 |
+
|
| 658 |
+
packed = {
|
| 659 |
+
"w13_trellis": w13_trellis,
|
| 660 |
+
"w13_suh": w13_suh,
|
| 661 |
+
"w13_svh": w13_svh,
|
| 662 |
+
"w13_mcg": w13_mcg,
|
| 663 |
+
"w13_mul1": w13_mul1,
|
| 664 |
+
"w2_trellis": w2_trellis,
|
| 665 |
+
"w2_suh": w2_suh,
|
| 666 |
+
"w2_svh": w2_svh,
|
| 667 |
+
"w2_mcg": w2_mcg,
|
| 668 |
+
"w2_mul1": w2_mul1,
|
| 669 |
+
}
|
| 670 |
+
for name, param in packed.items():
|
| 671 |
+
layer.register_parameter(name, param)
|
| 672 |
+
set_weight_attrs(param, extra)
|
| 673 |
+
param.weight_loader = self._load_exl3
|
| 674 |
+
param._exl3_owner = layer
|
| 675 |
+
if hasattr(layer, "w13_weight") or hasattr(layer, "w2_weight"):
|
| 676 |
+
raise RuntimeError("EXL3 create_weights must not allocate dense expert weights")
|
| 677 |
+
|
| 678 |
+
layer._exl3_hidden_size = hidden_size
|
| 679 |
+
layer._exl3_intermediate_local = intermediate_size_per_partition
|
| 680 |
+
layer._exl3_k_words = k_words
|
| 681 |
+
layer._exl3_bits = self.bits
|
| 682 |
+
|
| 683 |
+
def _load_exl3(
|
| 684 |
+
self,
|
| 685 |
+
param: Parameter,
|
| 686 |
+
loaded_weight: torch.Tensor,
|
| 687 |
+
weight_name: str,
|
| 688 |
+
shard_id: str = "w1",
|
| 689 |
+
expert_id: int = 0,
|
| 690 |
+
return_success: bool = False,
|
| 691 |
+
) -> bool | None:
|
| 692 |
+
from vllm.distributed import (
|
| 693 |
+
get_tensor_model_parallel_rank,
|
| 694 |
+
get_tensor_model_parallel_world_size,
|
| 695 |
+
)
|
| 696 |
+
|
| 697 |
+
layer = param
|
| 698 |
+
# param is the Parameter; expert_id is already physical. Map to local
|
| 699 |
+
# via the owning module if present on the weight_loader closure... we
|
| 700 |
+
# look up from param's __dict__ after register. RoutedExperts.weight_loader
|
| 701 |
+
# maps global→local; glm5next calls *our* loader, so map here.
|
| 702 |
+
owner = getattr(param, "_exl3_owner", None)
|
| 703 |
+
if owner is not None:
|
| 704 |
+
local_id = owner._map_global_expert_id_to_local_expert_id(expert_id)
|
| 705 |
+
if local_id == -1:
|
| 706 |
+
return False if return_success else None
|
| 707 |
+
expert_id = local_id
|
| 708 |
+
|
| 709 |
+
tp_rank = get_tensor_model_parallel_rank()
|
| 710 |
+
tp_size = get_tensor_model_parallel_world_size()
|
| 711 |
+
suffix = _suffix_from_mapped_name(weight_name)
|
| 712 |
+
loaded = loaded_weight.detach().contiguous()
|
| 713 |
+
|
| 714 |
+
if shard_id in ("w1", "w3"):
|
| 715 |
+
shard_idx = 0 if shard_id == "w1" else 1
|
| 716 |
+
sharded = shard_exl3_col(loaded, suffix, tp_rank, tp_size)
|
| 717 |
+
dest = param.data[expert_id, shard_idx]
|
| 718 |
+
elif shard_id == "w2":
|
| 719 |
+
sharded = shard_exl3_row(loaded, suffix, tp_rank, tp_size)
|
| 720 |
+
dest = param.data[expert_id]
|
| 721 |
+
else:
|
| 722 |
+
raise ValueError(f"unknown EXL3 shard_id={shard_id}")
|
| 723 |
+
|
| 724 |
+
# KEYS PATCH: codebook markers (mcg/mul1) may be stored as 0-d scalars; accept any
|
| 725 |
+
# shape with the same element count.
|
| 726 |
+
if suffix in ("mcg", "mul1") and sharded.numel() == dest.numel():
|
| 727 |
+
sharded = sharded.reshape(dest.shape)
|
| 728 |
+
if tuple(dest.shape) != tuple(sharded.shape):
|
| 729 |
+
raise RuntimeError(
|
| 730 |
+
f"EXL3 load shape mismatch {weight_name} shard={shard_id} "
|
| 731 |
+
f"expert={expert_id}: dest {tuple(dest.shape)} != "
|
| 732 |
+
f"loaded {tuple(sharded.shape)}"
|
| 733 |
+
)
|
| 734 |
+
dest.copy_(sharded)
|
| 735 |
+
return True if return_success else None
|
| 736 |
+
|
| 737 |
+
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
| 738 |
+
if not hasattr(layer, "w13_trellis"):
|
| 739 |
+
return
|
| 740 |
+
# Bind owner for any late loads; stitch LinearEXL3 handles.
|
| 741 |
+
for name in (
|
| 742 |
+
"w13_trellis",
|
| 743 |
+
"w13_suh",
|
| 744 |
+
"w13_svh",
|
| 745 |
+
"w13_mcg",
|
| 746 |
+
"w13_mul1",
|
| 747 |
+
"w2_trellis",
|
| 748 |
+
"w2_suh",
|
| 749 |
+
"w2_svh",
|
| 750 |
+
"w2_mcg",
|
| 751 |
+
"w2_mul1",
|
| 752 |
+
):
|
| 753 |
+
getattr(layer, name)._exl3_owner = layer
|
| 754 |
+
|
| 755 |
+
# KEYS PATCH: per-layer codebook detection from the loaded marker tensors.
|
| 756 |
+
mcg13 = layer.w13_mcg.reshape(-1)
|
| 757 |
+
mcg2 = layer.w2_mcg.reshape(-1)
|
| 758 |
+
mul13 = layer.w13_mul1.reshape(-1)
|
| 759 |
+
mul2 = layer.w2_mul1.reshape(-1)
|
| 760 |
+
is_mcg = bool(torch.all(mcg13 == MCG_MARKER_SIGNED_INT32)) and bool(
|
| 761 |
+
torch.all(mcg2 == MCG_MARKER_SIGNED_INT32)
|
| 762 |
+
)
|
| 763 |
+
is_mul1 = bool(torch.all(mul13 == MUL1_MARKER_SIGNED_INT32)) and bool(
|
| 764 |
+
torch.all(mul2 == MUL1_MARKER_SIGNED_INT32)
|
| 765 |
+
)
|
| 766 |
+
if is_mcg == is_mul1:
|
| 767 |
+
raise RuntimeError(
|
| 768 |
+
"EXL3 codebook markers inconsistent for this layer: "
|
| 769 |
+
f"mcg={is_mcg} mul1={is_mul1} (expect exactly one; "
|
| 770 |
+
f"MCG {MCG_MARKER_SIGNED_INT32} / MUL1 {MUL1_MARKER_SIGNED_INT32})"
|
| 771 |
+
)
|
| 772 |
+
use_mul1 = is_mul1
|
| 773 |
+
layer._exl3_mul1 = use_mul1
|
| 774 |
+
m13 = layer.w13_mul1 if use_mul1 else layer.w13_mcg
|
| 775 |
+
m2 = layer.w2_mul1 if use_mul1 else layer.w2_mcg
|
| 776 |
+
if not getattr(Exl3MoEMethod, "_logged_codebooks", None):
|
| 777 |
+
Exl3MoEMethod._logged_codebooks = set()
|
| 778 |
+
if use_mul1 not in Exl3MoEMethod._logged_codebooks:
|
| 779 |
+
Exl3MoEMethod._logged_codebooks.add(use_mul1)
|
| 780 |
+
logger.info("EXL3 experts codebook detected: %s", "mul1" if use_mul1 else "mcg")
|
| 781 |
+
|
| 782 |
+
n_exp = int(layer.w13_trellis.shape[0])
|
| 783 |
+
inners: list[dict[str, Any]] = []
|
| 784 |
+
for e in range(n_exp):
|
| 785 |
+
gate = make_linear_exl3(
|
| 786 |
+
layer.w13_trellis[e, 0],
|
| 787 |
+
layer.w13_suh[e, 0],
|
| 788 |
+
layer.w13_svh[e, 0],
|
| 789 |
+
m13[e, 0],
|
| 790 |
+
mul1=use_mul1,
|
| 791 |
+
)
|
| 792 |
+
up = make_linear_exl3(
|
| 793 |
+
layer.w13_trellis[e, 1],
|
| 794 |
+
layer.w13_suh[e, 1],
|
| 795 |
+
layer.w13_svh[e, 1],
|
| 796 |
+
m13[e, 1],
|
| 797 |
+
mul1=use_mul1,
|
| 798 |
+
)
|
| 799 |
+
down = make_linear_exl3(
|
| 800 |
+
layer.w2_trellis[e],
|
| 801 |
+
layer.w2_suh[e],
|
| 802 |
+
layer.w2_svh[e],
|
| 803 |
+
m2[e],
|
| 804 |
+
mul1=use_mul1,
|
| 805 |
+
)
|
| 806 |
+
inners.append({"gate": gate, "up": up, "down": down})
|
| 807 |
+
layer._exl3_inners = inners
|
| 808 |
+
fused_ok = False
|
| 809 |
+
fused_err = None
|
| 810 |
+
if fused_moe_enabled():
|
| 811 |
+
try:
|
| 812 |
+
import exllamav3_ext
|
| 813 |
+
|
| 814 |
+
if hasattr(exllamav3_ext, "exl3_moe"):
|
| 815 |
+
build_exl3_fused_state(layer, inners)
|
| 816 |
+
fused_ok = True
|
| 817 |
+
else:
|
| 818 |
+
fused_err = "exllamav3_ext.exl3_moe missing"
|
| 819 |
+
except Exception as exc:
|
| 820 |
+
fused_err = repr(exc)
|
| 821 |
+
layer._exl3_ptrs = None
|
| 822 |
+
if not self._logged:
|
| 823 |
+
if fused_ok:
|
| 824 |
+
logger.info(
|
| 825 |
+
"EXL3 MCG trellis engaged for routed experts: bits=%s "
|
| 826 |
+
"experts_local=%s hidden=%s intermediate_local=%s "
|
| 827 |
+
"fused_moe=exl3_moe concurrency=%s "
|
| 828 |
+
"(no BF16 expert reconstruct at load)",
|
| 829 |
+
self.bits,
|
| 830 |
+
n_exp,
|
| 831 |
+
layer._exl3_hidden_size,
|
| 832 |
+
layer._exl3_intermediate_local,
|
| 833 |
+
getattr(layer, "_exl3_fused_concurrency", "?"),
|
| 834 |
+
)
|
| 835 |
+
else:
|
| 836 |
+
logger.info(
|
| 837 |
+
"EXL3 MCG trellis engaged for routed experts: bits=%s "
|
| 838 |
+
"experts_local=%s hidden=%s intermediate_local=%s "
|
| 839 |
+
"fused_moe=python_loop (%s) "
|
| 840 |
+
"(no BF16 expert reconstruct at load)",
|
| 841 |
+
self.bits,
|
| 842 |
+
n_exp,
|
| 843 |
+
layer._exl3_hidden_size,
|
| 844 |
+
layer._exl3_intermediate_local,
|
| 845 |
+
fused_err or "EXL3_FUSED_MOE=0",
|
| 846 |
+
)
|
| 847 |
+
self._logged = True
|
| 848 |
+
|
| 849 |
+
def apply(
|
| 850 |
+
self,
|
| 851 |
+
layer: "RoutedExperts",
|
| 852 |
+
x: torch.Tensor,
|
| 853 |
+
topk_weights: torch.Tensor,
|
| 854 |
+
topk_ids: torch.Tensor,
|
| 855 |
+
shared_experts: "SharedExperts | None",
|
| 856 |
+
shared_experts_input: torch.Tensor | None,
|
| 857 |
+
) -> torch.Tensor:
|
| 858 |
+
del shared_experts, shared_experts_input
|
| 859 |
+
limit = getattr(self.moe, "swiglu_limit", None) or SWIGLU_LIMIT_DEFAULT
|
| 860 |
+
return apply_exl3_experts(
|
| 861 |
+
x, topk_ids, topk_weights, layer, limit=float(limit)
|
| 862 |
+
)
|
serving/finalize_config.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Post-compile fixups so the exllamav3 output is a Mia-native vLLM checkpoint.
|
| 3 |
+
|
| 4 |
+
* config.json / quantization_config.json: quant_method=exl3, bits=<int>, codebook=mcg,
|
| 5 |
+
scope=glm53_routed_experts_only, head_bits=16, mtp_bits=16 (Mia's Exl3Config reads bits/codebook/scope;
|
| 6 |
+
everything else is carried as raw config). The FP8 source block is preserved as original_quantization_config.
|
| 7 |
+
* sanity: every routed expert has trellis/suh/svh/mcg; every non-expert linear is a plain .weight; report dtypes.
|
| 8 |
+
Usage: finalize_config.py <out_dir> [expert_bits]
|
| 9 |
+
"""
|
| 10 |
+
import json, os, sys, re, struct, collections
|
| 11 |
+
|
| 12 |
+
out = sys.argv[1]
|
| 13 |
+
bits = int(sys.argv[2]) if len(sys.argv) > 2 else 3
|
| 14 |
+
CODEBOOK = sys.argv[3] if len(sys.argv) > 3 else "mcg"
|
| 15 |
+
|
| 16 |
+
def read_header(path):
|
| 17 |
+
with open(path, "rb") as f:
|
| 18 |
+
n = struct.unpack("<Q", f.read(8))[0]
|
| 19 |
+
return json.loads(f.read(n))
|
| 20 |
+
|
| 21 |
+
# --- tensor inventory
|
| 22 |
+
suffix_by_kind = collections.Counter()
|
| 23 |
+
dtypes = collections.Counter()
|
| 24 |
+
experts_ok, experts_bad = 0, []
|
| 25 |
+
nonexp_quant = []
|
| 26 |
+
shards = sorted(p for p in os.listdir(out) if p.endswith(".safetensors"))
|
| 27 |
+
seen = {}
|
| 28 |
+
for sh in shards:
|
| 29 |
+
for k, v in read_header(os.path.join(out, sh)).items():
|
| 30 |
+
if k == "__metadata__":
|
| 31 |
+
continue
|
| 32 |
+
seen[k] = v["dtype"]
|
| 33 |
+
exp = collections.defaultdict(set)
|
| 34 |
+
for k in seen:
|
| 35 |
+
m = re.match(r"model\.layers\.(\d+)\.mlp\.experts\.(\d+)\.(gate|up|down)_proj\.(\w+)$", k)
|
| 36 |
+
if m:
|
| 37 |
+
exp[(int(m.group(1)), int(m.group(2)), m.group(3))].add(m.group(4))
|
| 38 |
+
continue
|
| 39 |
+
m2 = re.match(r"(model\.layers\.\d+\.(self_attn|mlp\.shared_experts|mlp)\.[\w.]+?)\.(trellis|suh|svh|mcg|mul1)$", k)
|
| 40 |
+
if m2:
|
| 41 |
+
nonexp_quant.append(k)
|
| 42 |
+
for key, sfx in exp.items():
|
| 43 |
+
if {"trellis", "suh", "svh"} <= sfx and ({"mcg"} <= sfx or {"mul1"} <= sfx):
|
| 44 |
+
experts_ok += 1
|
| 45 |
+
else:
|
| 46 |
+
experts_bad.append((key, sorted(sfx)))
|
| 47 |
+
for k, d in seen.items():
|
| 48 |
+
dtypes[(k.rsplit(".", 1)[-1], d)] += 1
|
| 49 |
+
print(f"shards={len(shards)} tensors={len(seen)} experts_ok={experts_ok} experts_bad={len(experts_bad)} nonexpert_quantized={len(nonexp_quant)}")
|
| 50 |
+
if experts_bad:
|
| 51 |
+
print(" bad:", experts_bad[:5])
|
| 52 |
+
if nonexp_quant:
|
| 53 |
+
print(" !! non-expert EXL3 tensors (Mia overlay expects native):", nonexp_quant[:5])
|
| 54 |
+
for (sfx, d), n in sorted(dtypes.items(), key=lambda x: -x[1])[:12]:
|
| 55 |
+
print(f" {sfx:14s} {d:8s} {n}")
|
| 56 |
+
|
| 57 |
+
# --- config fixups
|
| 58 |
+
def fix(path, is_hf_config):
|
| 59 |
+
d = json.load(open(path))
|
| 60 |
+
q = d.get("quantization_config", d if not is_hf_config else {}) if is_hf_config else d
|
| 61 |
+
if is_hf_config:
|
| 62 |
+
q = d.setdefault("quantization_config", {})
|
| 63 |
+
orig = None
|
| 64 |
+
if q.get("quant_method") not in (None, "exl3"):
|
| 65 |
+
orig = dict(q)
|
| 66 |
+
q.clear()
|
| 67 |
+
if orig is not None:
|
| 68 |
+
q["original_quantization_config"] = orig
|
| 69 |
+
q.update({"quant_method": "exl3", "bits": bits, "codebook": CODEBOOK, "scope": "glm53_routed_experts_only",
|
| 70 |
+
"head_bits": 16, "mtp_bits": 16, "keys_profile": f"routed-experts-{bits}bpw-{CODEBOOK}; attn/dense/shared/head bf16",
|
| 71 |
+
"keys_source": "zai-org/GLM-5.3 (FP8) via exllamav3 1.4.5 + keys encode farm"})
|
| 72 |
+
q.pop("tensor_storage", None) if is_hf_config else None
|
| 73 |
+
json.dump(d, open(path, "w"), indent=2)
|
| 74 |
+
print(f"wrote {path}: quant_method={q['quant_method']} bits={q['bits']} codebook={q['codebook']}")
|
| 75 |
+
|
| 76 |
+
fix(os.path.join(out, "config.json"), True)
|
| 77 |
+
qc = os.path.join(out, "quantization_config.json")
|
| 78 |
+
if os.path.exists(qc):
|
| 79 |
+
fix(qc, False)
|
| 80 |
+
for fn in ("tokenizer.json", "tokenizer_config.json", "chat_template.jinja", "generation_config.json", "model.safetensors.index.json"):
|
| 81 |
+
print(f" {fn}: {'ok' if os.path.exists(os.path.join(out, fn)) else 'MISSING'}")
|
serving/glm53_probe.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json,time,urllib.request,sys
|
| 2 |
+
BASE="http://10.100.10.1:8888"
|
| 3 |
+
def chat(prompt,max_tokens,think=False):
|
| 4 |
+
body={"model":"GLM-5.3-EXL3","messages":[{"role":"user","content":prompt}],"max_tokens":max_tokens,"temperature":0,"stream":True,"stream_options":{"include_usage":True},"chat_template_kwargs":{"enable_thinking":think}}
|
| 5 |
+
req=urllib.request.Request(BASE+"/v1/chat/completions",data=json.dumps(body).encode(),headers={"Content-Type":"application/json"})
|
| 6 |
+
t0=time.time(); ttft=None; n=0; text=""
|
| 7 |
+
with urllib.request.urlopen(req,timeout=3600) as r:
|
| 8 |
+
for line in r:
|
| 9 |
+
line=line.decode().strip()
|
| 10 |
+
if not line.startswith("data:") or line=="data: [DONE]": continue
|
| 11 |
+
ev=json.loads(line[5:])
|
| 12 |
+
if ev.get("usage"): n=ev["usage"]["completion_tokens"]; pt=ev["usage"]["prompt_tokens"]
|
| 13 |
+
ch=ev.get("choices")
|
| 14 |
+
if ch:
|
| 15 |
+
d=ch[0].get("delta") or {}
|
| 16 |
+
c=d.get("content") or d.get("reasoning_content") or ""
|
| 17 |
+
if c and ttft is None: ttft=time.time()-t0
|
| 18 |
+
text+=c
|
| 19 |
+
wall=time.time()-t0
|
| 20 |
+
return dict(prompt_tokens=pt,tokens=n,ttft=ttft,wall=wall,decode_tps=n/(wall-ttft) if ttft and wall>ttft else None,text=text)
|
| 21 |
+
mode=sys.argv[1]
|
| 22 |
+
if mode=="decode":
|
| 23 |
+
r=chat("Write a detailed 300-word explanation of how a transformer language model generates text, covering tokenization, attention, and sampling.",256)
|
| 24 |
+
print(f"decode: prompt {r['prompt_tokens']} tok, gen {r['tokens']} tok, TTFT {r['ttft']:.2f}s, decode {r['decode_tps']:.2f} tok/s"); print("head:",repr(r["text"][:200]))
|
| 25 |
+
elif mode=="needle":
|
| 26 |
+
k=int(sys.argv[2])
|
| 27 |
+
filler="The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs. "*(k*1000//19)
|
| 28 |
+
secret="The secret passphrase is VIOLET-KANGAROO-7731."
|
| 29 |
+
pos=len(filler)//2; doc=filler[:pos]+" "+secret+" "+filler[pos:]
|
| 30 |
+
r=chat(doc+"\n\nWhat is the secret passphrase mentioned in the text above? Reply with just the passphrase.",40)
|
| 31 |
+
ok="VIOLET-KANGAROO-7731" in r["text"]
|
| 32 |
+
print(f"needle {k}K: prompt {r['prompt_tokens']} tok, TTFT {r['ttft']:.1f}s ({r['prompt_tokens']/r['ttft']:.0f} tok/s prefill), decode {r['decode_tps']:.2f} tok/s, PASS={ok}, reply={r['text'].strip()[:80]!r}")
|
serving/keys-launch.sh
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# keys wrapper: drop page caches on all 4 ranks (NFS weight reads inflate "used" and trip vLLM's
|
| 3 |
+
# startup free-memory guard on unified-memory GB10), then launch with the standard skip flags.
|
| 4 |
+
# Usage: [ENFORCE_EAGER=0] [EXL3_FUSED_MOE=1] [GPU_MEM_UTIL=0.86] ./keys-launch.sh [logname]
|
| 5 |
+
set -u
|
| 6 |
+
cd ~/glm53-mia
|
| 7 |
+
LOG=${1:-launch-tp4-keys.log}
|
| 8 |
+
./start-tp4.sh stop 2>&1 | tail -1
|
| 9 |
+
for ip in 10.100.10.1 10.100.10.2 10.100.10.3 10.100.10.5; do
|
| 10 |
+
ssh -o BatchMode=yes -o ConnectTimeout=10 "$ip" "sudo sync; echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null; free -g | awk '/Mem:/{print \"\" \" free=\" \$4 \"G avail=\" \$7 \"G\"}'" 2>/dev/null || echo "$ip: drop_caches failed"
|
| 11 |
+
done
|
| 12 |
+
SKIP_PULL=1 SKIP_SHIP=1 SKIP_BUILD=1 SKIP_DOWNLOAD=1 SKIP_SYNC=1 SKIP_OVERLAY_VERIFY=1 \
|
| 13 |
+
ENFORCE_EAGER=${ENFORCE_EAGER:-0} EXL3_FUSED_MOE=${EXL3_FUSED_MOE:-1} \
|
| 14 |
+
setsid nohup ./start-tp4.sh > "$LOG" 2>&1 < /dev/null &
|
| 15 |
+
echo "launched -> $LOG"
|
serving/mla_attn_dcp.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
serving/patch_ablit_dsv2.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Install Mia's ABLIT load-time hook for FULL GLM-5.3 (GlmMoeDsaForCausalLM -> deepseek_v2.py).
|
| 3 |
+
|
| 4 |
+
Mia's overlay/patch_ablit.py splices the hook into the GLM-5.3-Flash classes (glm5next). The full
|
| 5 |
+
model is served by DeepseekV2Model / DeepSeekMTP, so this splices the same call at the end of their
|
| 6 |
+
load_weights. ablit_runtime.py is reused unchanged: its o_proj regexes already match
|
| 7 |
+
``model.layers.N.self_attn.o_proj`` and ``layers.N.mtp_block.self_attn.o_proj``. Idempotent.
|
| 8 |
+
Donor set: <ABLIT_DIR>/transplant/L{N}.bin + MANIFEST.json (make_derisk_donor.py output).
|
| 9 |
+
Runtime env: ABLIT=1 ABLIT_METHOD=transplant ABLIT_LAYERS=<ranges> ABLIT_INCLUDE_MTP=1 ABLIT_DIR=/opt/glm53/ablit
|
| 10 |
+
"""
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
import shutil, sys
|
| 13 |
+
|
| 14 |
+
SITE = Path("/usr/local/lib/python3.12/dist-packages/vllm")
|
| 15 |
+
MODELS = SITE / "model_executor/models"
|
| 16 |
+
NVIDIA_DIR = SITE / "models/glm5next/nvidia"
|
| 17 |
+
RUNTIME_SRC = Path(sys.argv[1] if len(sys.argv) > 1 else "/opt/glm53/ablit_runtime.py")
|
| 18 |
+
RUNTIME_DST = NVIDIA_DIR / "glm53_ablit.py"
|
| 19 |
+
MARKER = "ABLIT-HOOK-DSV2"
|
| 20 |
+
HOOK = (
|
| 21 |
+
" from vllm.models.glm5next.nvidia.glm53_ablit import maybe_apply "
|
| 22 |
+
"as _glm53_ablit_maybe_apply # ABLIT-HOOK-DSV2\n"
|
| 23 |
+
" _glm53_ablit_maybe_apply(self) # ABLIT-HOOK-DSV2\n"
|
| 24 |
+
)
|
| 25 |
+
MODEL_TAIL = """ if name is not None and not is_fusion_moe_shared_experts_layer:
|
| 26 |
+
loaded_params.add(name)
|
| 27 |
+
|
| 28 |
+
return loaded_params"""
|
| 29 |
+
MTP_TAIL = """ f"or disable speculative decoding."
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
return loaded_params"""
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def splice(path: Path, tail: str, label: str) -> None:
|
| 36 |
+
text = path.read_text()
|
| 37 |
+
if MARKER in text:
|
| 38 |
+
print(f"{path.name}: {MARKER} already present")
|
| 39 |
+
return
|
| 40 |
+
n = text.count(tail)
|
| 41 |
+
if n != 1:
|
| 42 |
+
raise SystemExit(f"{path}: expected one patch target for {label}, found {n}")
|
| 43 |
+
new_tail = tail.replace(" return loaded_params", HOOK + " return loaded_params")
|
| 44 |
+
path.write_text(text.replace(tail, new_tail))
|
| 45 |
+
print(f"patched {path.name} ({label})")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
NVIDIA_DIR.mkdir(parents=True, exist_ok=True)
|
| 49 |
+
if RUNTIME_SRC.is_file():
|
| 50 |
+
shutil.copyfile(RUNTIME_SRC, RUNTIME_DST)
|
| 51 |
+
print(f"installed {RUNTIME_DST}")
|
| 52 |
+
elif not RUNTIME_DST.is_file():
|
| 53 |
+
raise SystemExit(f"ablit runtime not found at {RUNTIME_SRC}")
|
| 54 |
+
splice(MODELS / "deepseek_v2.py", MODEL_TAIL, "DeepseekV2Model.load_weights")
|
| 55 |
+
splice(MODELS / "deepseek_mtp.py", MTP_TAIL, "DeepSeekMTP.load_weights")
|
serving/patch_overlay_mtp.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
p=sys.argv[1]; s=open(p).read()
|
| 3 |
+
old=""" def get_quant_method(self, layer: torch.nn.Module, prefix: str):
|
| 4 |
+
from vllm.model_executor.layers.fused_moe.routed_experts import RoutedExperts
|
| 5 |
+
|
| 6 |
+
if isinstance(layer, RoutedExperts):
|
| 7 |
+
return Exl3MoEMethod(layer.moe_config, self)"""
|
| 8 |
+
new=""" def get_quant_method(self, layer: torch.nn.Module, prefix: str):
|
| 9 |
+
from vllm.model_executor.layers.fused_moe.routed_experts import RoutedExperts
|
| 10 |
+
|
| 11 |
+
# KEYS PATCH: the MTP block (model.layers.<num_hidden_layers>+) is stored bf16
|
| 12 |
+
# (-mb 16) -> leave it to the stock unquantized MoE path so SPEC_METHOD=mtp works.
|
| 13 |
+
if isinstance(layer, RoutedExperts) and _is_mtp_prefix(prefix):
|
| 14 |
+
logger.info_once("EXL3: MTP block %s left unquantized (bf16 experts)", prefix)
|
| 15 |
+
return None
|
| 16 |
+
if isinstance(layer, RoutedExperts):
|
| 17 |
+
return Exl3MoEMethod(layer.moe_config, self)"""
|
| 18 |
+
assert s.count(old)==1, s.count(old); s=s.replace(old,new)
|
| 19 |
+
helper='''
|
| 20 |
+
|
| 21 |
+
def _is_mtp_prefix(prefix: str) -> bool:
|
| 22 |
+
"""True for layers at index >= num_hidden_layers (DeepSeek/GLM MTP blocks)."""
|
| 23 |
+
import re as _re
|
| 24 |
+
m = _re.search(r"\\.layers\\.(\\d+)\\.", prefix + ".")
|
| 25 |
+
if not m:
|
| 26 |
+
return "mtp" in prefix.lower()
|
| 27 |
+
idx = int(m.group(1))
|
| 28 |
+
n = None
|
| 29 |
+
try:
|
| 30 |
+
from vllm.config import get_current_vllm_config
|
| 31 |
+
n = int(get_current_vllm_config().model_config.hf_config.num_hidden_layers)
|
| 32 |
+
except Exception: # noqa: BLE001
|
| 33 |
+
n = None
|
| 34 |
+
if n is None:
|
| 35 |
+
n = int(os.environ.get("EXL3_MTP_LAYER_START", "78"))
|
| 36 |
+
return idx >= n
|
| 37 |
+
'''
|
| 38 |
+
anchor='\n@register_quantization_config("exl3")'
|
| 39 |
+
assert s.count(anchor)==1, s.count(anchor); s=s.replace(anchor, helper+anchor)
|
| 40 |
+
open(p,"w").write(s); print("mtp exemption patched")
|
serving/patch_overlay_mul1.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys,re
|
| 2 |
+
src=sys.argv[1]; dst=sys.argv[2]; s=open(src).read()
|
| 3 |
+
def rep(old,new,count=1):
|
| 4 |
+
global s
|
| 5 |
+
assert old in s, f"missing: {old[:70]!r}"
|
| 6 |
+
s=s.replace(old,new,count)
|
| 7 |
+
rep('MCG_MARKER_SIGNED_INT32 = -877912083\nEXL3_SUFFIXES = ("trellis", "suh", "svh", "mcg")',
|
| 8 |
+
'''MCG_MARKER_SIGNED_INT32 = -877912083
|
| 9 |
+
# KEYS PATCH: exllamav3 "mul1" codebook (converter default; 0x83DCD12D marker). LinearEXL3 and
|
| 10 |
+
# exllamav3_ext.exl3_moe in this image already take (mcg, mul1) flag pairs per matrix.
|
| 11 |
+
MUL1_MULTIPLIER = 0x83DCD12D
|
| 12 |
+
MUL1_MARKER_SIGNED_INT32 = -2082680531
|
| 13 |
+
EXL3_SUFFIXES = ("trellis", "suh", "svh", "mcg", "mul1")''')
|
| 14 |
+
rep(''' mcg: torch.Tensor,
|
| 15 |
+
*,
|
| 16 |
+
out_dtype: torch.dtype = torch.float16,
|
| 17 |
+
):
|
| 18 |
+
"""Build a LinearEXL3 over already-sharded packed tensors. No BF16 expand."""
|
| 19 |
+
cls = load_linear_exl3_cls()
|
| 20 |
+
return cls(
|
| 21 |
+
config=None,
|
| 22 |
+
in_features=int(suh.numel()),
|
| 23 |
+
out_features=int(svh.numel()),
|
| 24 |
+
trellis=trellis.contiguous(),
|
| 25 |
+
suh=suh.contiguous(),
|
| 26 |
+
svh=svh.contiguous(),
|
| 27 |
+
mcg=mcg.contiguous(),
|
| 28 |
+
out_dtype=out_dtype,
|
| 29 |
+
transformers_fix=True,
|
| 30 |
+
)''',''' mcg: torch.Tensor,
|
| 31 |
+
*,
|
| 32 |
+
mul1: bool = False,
|
| 33 |
+
out_dtype: torch.dtype = torch.float16,
|
| 34 |
+
):
|
| 35 |
+
"""Build a LinearEXL3 over already-sharded packed tensors. No BF16 expand.
|
| 36 |
+
KEYS PATCH: `mcg` is the codebook marker tensor; with mul1=True it is passed as the
|
| 37 |
+
mul1 marker instead (exllamav3 LinearEXL3 accepts either)."""
|
| 38 |
+
cls = load_linear_exl3_cls()
|
| 39 |
+
marker = mcg.contiguous()
|
| 40 |
+
return cls(
|
| 41 |
+
config=None,
|
| 42 |
+
in_features=int(suh.numel()),
|
| 43 |
+
out_features=int(svh.numel()),
|
| 44 |
+
trellis=trellis.contiguous(),
|
| 45 |
+
suh=suh.contiguous(),
|
| 46 |
+
svh=svh.contiguous(),
|
| 47 |
+
mcg=None if mul1 else marker,
|
| 48 |
+
mul1=marker if mul1 else None,
|
| 49 |
+
out_dtype=out_dtype,
|
| 50 |
+
transformers_fix=True,
|
| 51 |
+
)''')
|
| 52 |
+
rep(''' if self.codebook != "mcg":
|
| 53 |
+
raise ValueError(
|
| 54 |
+
f"this overlay only implements codebook=mcg; got {self.codebook!r}"
|
| 55 |
+
)''',''' if self.codebook not in ("mcg", "mul1", "mixed", "auto"):
|
| 56 |
+
raise ValueError(
|
| 57 |
+
f"this overlay implements codebook mcg/mul1 (per layer); got {self.codebook!r}"
|
| 58 |
+
)''')
|
| 59 |
+
rep(''' w13_mcg = Parameter(
|
| 60 |
+
torch.empty(num_experts, 2, 1, dtype=torch.int32),
|
| 61 |
+
requires_grad=False,
|
| 62 |
+
)''',''' w13_mcg = Parameter(
|
| 63 |
+
torch.zeros(num_experts, 2, 1, dtype=torch.int32),
|
| 64 |
+
requires_grad=False,
|
| 65 |
+
)
|
| 66 |
+
w13_mul1 = Parameter(
|
| 67 |
+
torch.zeros(num_experts, 2, 1, dtype=torch.int32),
|
| 68 |
+
requires_grad=False,
|
| 69 |
+
)''')
|
| 70 |
+
rep(''' w2_mcg = Parameter(
|
| 71 |
+
torch.empty(num_experts, 1, dtype=torch.int32),
|
| 72 |
+
requires_grad=False,
|
| 73 |
+
)''',''' w2_mcg = Parameter(
|
| 74 |
+
torch.zeros(num_experts, 1, dtype=torch.int32),
|
| 75 |
+
requires_grad=False,
|
| 76 |
+
)
|
| 77 |
+
w2_mul1 = Parameter(
|
| 78 |
+
torch.zeros(num_experts, 1, dtype=torch.int32),
|
| 79 |
+
requires_grad=False,
|
| 80 |
+
)''')
|
| 81 |
+
rep(''' "w13_mcg": w13_mcg,
|
| 82 |
+
"w2_trellis": w2_trellis,''',''' "w13_mcg": w13_mcg,
|
| 83 |
+
"w13_mul1": w13_mul1,
|
| 84 |
+
"w2_trellis": w2_trellis,''')
|
| 85 |
+
rep(''' "w2_mcg": w2_mcg,
|
| 86 |
+
}''',''' "w2_mcg": w2_mcg,
|
| 87 |
+
"w2_mul1": w2_mul1,
|
| 88 |
+
}''')
|
| 89 |
+
rep(''' "w13_mcg",
|
| 90 |
+
"w2_trellis",
|
| 91 |
+
"w2_suh",
|
| 92 |
+
"w2_svh",
|
| 93 |
+
"w2_mcg",
|
| 94 |
+
):
|
| 95 |
+
getattr(layer, name)._exl3_owner = layer
|
| 96 |
+
|
| 97 |
+
mcg13 = layer.w13_mcg.reshape(-1)
|
| 98 |
+
mcg2 = layer.w2_mcg.reshape(-1)
|
| 99 |
+
if not torch.all(mcg13 == MCG_MARKER_SIGNED_INT32) or not torch.all(
|
| 100 |
+
mcg2 == MCG_MARKER_SIGNED_INT32
|
| 101 |
+
):
|
| 102 |
+
raise RuntimeError(
|
| 103 |
+
"EXL3 mcg marker is not the MCG int32 0xCBAC1FED / "
|
| 104 |
+
f"{MCG_MARKER_SIGNED_INT32}; packed ABI mismatch"
|
| 105 |
+
)
|
| 106 |
+
''',''' "w13_mcg",
|
| 107 |
+
"w13_mul1",
|
| 108 |
+
"w2_trellis",
|
| 109 |
+
"w2_suh",
|
| 110 |
+
"w2_svh",
|
| 111 |
+
"w2_mcg",
|
| 112 |
+
"w2_mul1",
|
| 113 |
+
):
|
| 114 |
+
getattr(layer, name)._exl3_owner = layer
|
| 115 |
+
|
| 116 |
+
# KEYS PATCH: per-layer codebook detection from the loaded marker tensors.
|
| 117 |
+
mcg13 = layer.w13_mcg.reshape(-1)
|
| 118 |
+
mcg2 = layer.w2_mcg.reshape(-1)
|
| 119 |
+
mul13 = layer.w13_mul1.reshape(-1)
|
| 120 |
+
mul2 = layer.w2_mul1.reshape(-1)
|
| 121 |
+
is_mcg = bool(torch.all(mcg13 == MCG_MARKER_SIGNED_INT32)) and bool(
|
| 122 |
+
torch.all(mcg2 == MCG_MARKER_SIGNED_INT32)
|
| 123 |
+
)
|
| 124 |
+
is_mul1 = bool(torch.all(mul13 == MUL1_MARKER_SIGNED_INT32)) and bool(
|
| 125 |
+
torch.all(mul2 == MUL1_MARKER_SIGNED_INT32)
|
| 126 |
+
)
|
| 127 |
+
if is_mcg == is_mul1:
|
| 128 |
+
raise RuntimeError(
|
| 129 |
+
"EXL3 codebook markers inconsistent for this layer: "
|
| 130 |
+
f"mcg={is_mcg} mul1={is_mul1} (expect exactly one; "
|
| 131 |
+
f"MCG {MCG_MARKER_SIGNED_INT32} / MUL1 {MUL1_MARKER_SIGNED_INT32})"
|
| 132 |
+
)
|
| 133 |
+
use_mul1 = is_mul1
|
| 134 |
+
layer._exl3_mul1 = use_mul1
|
| 135 |
+
m13 = layer.w13_mul1 if use_mul1 else layer.w13_mcg
|
| 136 |
+
m2 = layer.w2_mul1 if use_mul1 else layer.w2_mcg
|
| 137 |
+
if not getattr(Exl3MoEMethod, "_logged_codebooks", None):
|
| 138 |
+
Exl3MoEMethod._logged_codebooks = set()
|
| 139 |
+
if use_mul1 not in Exl3MoEMethod._logged_codebooks:
|
| 140 |
+
Exl3MoEMethod._logged_codebooks.add(use_mul1)
|
| 141 |
+
logger.info("EXL3 experts codebook detected: %s", "mul1" if use_mul1 else "mcg")
|
| 142 |
+
''')
|
| 143 |
+
rep(''' gate = make_linear_exl3(
|
| 144 |
+
layer.w13_trellis[e, 0],
|
| 145 |
+
layer.w13_suh[e, 0],
|
| 146 |
+
layer.w13_svh[e, 0],
|
| 147 |
+
layer.w13_mcg[e, 0],
|
| 148 |
+
)
|
| 149 |
+
up = make_linear_exl3(
|
| 150 |
+
layer.w13_trellis[e, 1],
|
| 151 |
+
layer.w13_suh[e, 1],
|
| 152 |
+
layer.w13_svh[e, 1],
|
| 153 |
+
layer.w13_mcg[e, 1],
|
| 154 |
+
)
|
| 155 |
+
down = make_linear_exl3(
|
| 156 |
+
layer.w2_trellis[e],
|
| 157 |
+
layer.w2_suh[e],
|
| 158 |
+
layer.w2_svh[e],
|
| 159 |
+
layer.w2_mcg[e],
|
| 160 |
+
)''',''' gate = make_linear_exl3(
|
| 161 |
+
layer.w13_trellis[e, 0],
|
| 162 |
+
layer.w13_suh[e, 0],
|
| 163 |
+
layer.w13_svh[e, 0],
|
| 164 |
+
m13[e, 0],
|
| 165 |
+
mul1=use_mul1,
|
| 166 |
+
)
|
| 167 |
+
up = make_linear_exl3(
|
| 168 |
+
layer.w13_trellis[e, 1],
|
| 169 |
+
layer.w13_suh[e, 1],
|
| 170 |
+
layer.w13_svh[e, 1],
|
| 171 |
+
m13[e, 1],
|
| 172 |
+
mul1=use_mul1,
|
| 173 |
+
)
|
| 174 |
+
down = make_linear_exl3(
|
| 175 |
+
layer.w2_trellis[e],
|
| 176 |
+
layer.w2_suh[e],
|
| 177 |
+
layer.w2_svh[e],
|
| 178 |
+
m2[e],
|
| 179 |
+
mul1=use_mul1,
|
| 180 |
+
)''')
|
| 181 |
+
rep(''' ptrs["down_svh"],
|
| 182 |
+
True,
|
| 183 |
+
False,
|
| 184 |
+
True,
|
| 185 |
+
False,
|
| 186 |
+
True,
|
| 187 |
+
False,
|
| 188 |
+
float(limit),''',''' ptrs["down_svh"],
|
| 189 |
+
# KEYS PATCH: (mcg, mul1) flag pairs for gate / up / down, per-layer codebook.
|
| 190 |
+
not _m1,
|
| 191 |
+
_m1,
|
| 192 |
+
not _m1,
|
| 193 |
+
_m1,
|
| 194 |
+
not _m1,
|
| 195 |
+
_m1,
|
| 196 |
+
float(limit),''')
|
| 197 |
+
rep(''' k = int(getattr(layer, "_exl3_k", 4))
|
| 198 |
+
args = (''',''' k = int(getattr(layer, "_exl3_k", 4))
|
| 199 |
+
_m1 = bool(getattr(layer, "_exl3_mul1", False))
|
| 200 |
+
args = (''')
|
| 201 |
+
open(dst,"w").write(s); print("overlay patched")
|
serving/patch_tp4_launcher.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Add LOCAL_MODEL_DIR / LOCAL_DFLASH_DIR support to Mia's start-tp4.sh (serve a local checkpoint dir,
|
| 3 |
+
e.g. an NFS path visible on all four nodes, instead of the HF-cache layout). Idempotent.
|
| 4 |
+
Usage: patch_tp4_launcher.py ~/glm53-mia/start-tp4.sh (then set LOCAL_MODEL_DIR=... in .env.tp4 and run with
|
| 5 |
+
SKIP_DOWNLOAD=1 SKIP_SYNC=1 SKIP_PULL=1)"""
|
| 6 |
+
import sys
|
| 7 |
+
p = sys.argv[1]
|
| 8 |
+
s = open(p).read()
|
| 9 |
+
if "LOCAL_MODEL_DIR" in s:
|
| 10 |
+
print("already patched"); sys.exit(0)
|
| 11 |
+
|
| 12 |
+
# workers: extend the per-rank preload string right before each rank's docker run
|
| 13 |
+
old_w = ''' worker_ssh_n "$r" "docker run -d --name '$cname' \\'''
|
| 14 |
+
new_w = ''' [ -n "${LOCAL_MODEL_DIR:-}" ] && worker_preload="$worker_preload -v '$LOCAL_MODEL_DIR:/model:ro'"
|
| 15 |
+
[ -n "${LOCAL_DFLASH_DIR:-}" ] && worker_preload="$worker_preload -v '$LOCAL_DFLASH_DIR:/draft:ro'"
|
| 16 |
+
worker_ssh_n "$r" "docker run -d --name '$cname' \\'''
|
| 17 |
+
assert s.count(old_w) == 1, s.count(old_w)
|
| 18 |
+
s = s.replace(old_w, new_w, 1)
|
| 19 |
+
|
| 20 |
+
# head
|
| 21 |
+
old_h = ''' docker run -d --name "$CONTAINER_HEAD" \\'''
|
| 22 |
+
new_h = ''' [ -n "${LOCAL_MODEL_DIR:-}" ] && head_preload+=(-v "$LOCAL_MODEL_DIR:/model:ro")
|
| 23 |
+
[ -n "${LOCAL_DFLASH_DIR:-}" ] && head_preload+=(-v "$LOCAL_DFLASH_DIR:/draft:ro")
|
| 24 |
+
docker run -d --name "$CONTAINER_HEAD" \\'''
|
| 25 |
+
assert s.count(old_h) == 1, s.count(old_h)
|
| 26 |
+
s = s.replace(old_h, new_h, 1)
|
| 27 |
+
|
| 28 |
+
# in-container model paths
|
| 29 |
+
old_m = ''' MODEL_DIR="$(resolve_model_dir)"
|
| 30 |
+
DFLASH_MODEL_DIR=""
|
| 31 |
+
if [ "$SPEC_METHOD" = "dflash" ]; then
|
| 32 |
+
DFLASH_MODEL_DIR="$(resolve_dflash_dir)"'''
|
| 33 |
+
new_m = ''' if [ -n "${LOCAL_MODEL_DIR:-}" ]; then
|
| 34 |
+
MODEL_DIR="/model"
|
| 35 |
+
log "LOCAL_MODEL_DIR mode: $LOCAL_MODEL_DIR -> /model on all ranks"
|
| 36 |
+
else
|
| 37 |
+
MODEL_DIR="$(resolve_model_dir)"
|
| 38 |
+
fi
|
| 39 |
+
DFLASH_MODEL_DIR=""
|
| 40 |
+
if [ "$SPEC_METHOD" = "dflash" ]; then
|
| 41 |
+
if [ -n "${LOCAL_DFLASH_DIR:-}" ]; then
|
| 42 |
+
DFLASH_MODEL_DIR="/draft"
|
| 43 |
+
else
|
| 44 |
+
DFLASH_MODEL_DIR="$(resolve_dflash_dir)"
|
| 45 |
+
fi'''
|
| 46 |
+
assert s.count(old_m) == 1, s.count(old_m)
|
| 47 |
+
s = s.replace(old_m, new_m, 1)
|
| 48 |
+
open(p, "w").write(s)
|
| 49 |
+
print("patched", p)
|
serving/sm120_dcp.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
| 3 |
+
"""SM120 implementation variant for ``FLASHINFER_MLA_SPARSE_SM120``.
|
| 4 |
+
|
| 5 |
+
keys-DCP port (2026-09-01): adds Decode Context Parallel (KV sharded across the TP ranks)
|
| 6 |
+
support, mirroring the SM100 ``FlashInferMLASparseImpl`` branch:
|
| 7 |
+
* ``can_return_lse_for_decode = True`` so the CP compatibility check passes and the layer
|
| 8 |
+
combines per-rank partial attention with ``MLADCPManager.combine``;
|
| 9 |
+
* under DCP the global top-k token indices are filtered to this rank's local KV slots with
|
| 10 |
+
``triton_filter_and_convert_dcp_index`` (interleave-aware);
|
| 11 |
+
* the flashinfer SM120 sparse kernel is asked for the softmax LSE (``return_lse``), which is
|
| 12 |
+
normalized to ``(num_tokens, num_heads)``; rows with no local top-k tokens get output 0 and
|
| 13 |
+
LSE ``-inf`` so they drop out of the cross-rank softmax merge;
|
| 14 |
+
* the output is allocated from ``q.shape[1]`` (all heads after the DCP query all-gather),
|
| 15 |
+
not the layer's TP-local head count.
|
| 16 |
+
``EXL3_SM120_LSE_BASE_E`` (default 1) selects the LSE base reported to the combine kernel; the
|
| 17 |
+
SM120 sparse kernel follows the FlashMLA (natural-log) convention. Set 0 for base-2.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import os
|
| 21 |
+
from typing import TYPE_CHECKING, cast
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
|
| 25 |
+
from vllm.v1.attention.backend import (
|
| 26 |
+
AttentionLayer,
|
| 27 |
+
AttentionType,
|
| 28 |
+
MLAAttentionImpl,
|
| 29 |
+
)
|
| 30 |
+
from vllm.v1.attention.backends.mla.flashinfer_mla_sparse import (
|
| 31 |
+
FlashInferMLASparseMetadata,
|
| 32 |
+
_get_workspace_buffer,
|
| 33 |
+
)
|
| 34 |
+
from vllm.v1.attention.backends.mla.sparse_utils import (
|
| 35 |
+
triton_convert_req_index_to_global_index,
|
| 36 |
+
triton_filter_and_convert_dcp_index,
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
if TYPE_CHECKING:
|
| 40 |
+
from vllm.model_executor.models.deepseek_v2 import Indexer
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# The layer's DCP combine reads ``attn_metadata.decode``; the sparse metadata has no such field.
|
| 44 |
+
if not hasattr(FlashInferMLASparseMetadata, "decode"):
|
| 45 |
+
FlashInferMLASparseMetadata.decode = None # type: ignore[attr-defined]
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _kv_scale_format_for_model(model_type: str | None) -> str:
|
| 49 |
+
if model_type is not None and model_type.startswith("glm"):
|
| 50 |
+
return "arbitrary_fp32"
|
| 51 |
+
return "pow2_fp32"
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class FlashInferMLASparseSM120Impl(MLAAttentionImpl[FlashInferMLASparseMetadata]):
|
| 55 |
+
"""SM120 FlashInfer sparse-MLA implementation (+ DCP)."""
|
| 56 |
+
|
| 57 |
+
is_sparse = True
|
| 58 |
+
supports_dense_mha_prefill = False
|
| 59 |
+
# DCP: we can hand the softmax LSE to the cross-rank combine.
|
| 60 |
+
can_return_lse_for_decode = True
|
| 61 |
+
lse_base_on_e = os.environ.get("EXL3_SM120_LSE_BASE_E", "1") == "1"
|
| 62 |
+
|
| 63 |
+
def __init__(
|
| 64 |
+
self,
|
| 65 |
+
num_heads: int,
|
| 66 |
+
head_size: int,
|
| 67 |
+
scale: float,
|
| 68 |
+
num_kv_heads: int,
|
| 69 |
+
alibi_slopes: list[float] | None,
|
| 70 |
+
sliding_window: int | None,
|
| 71 |
+
kv_cache_dtype: str,
|
| 72 |
+
logits_soft_cap: float | None,
|
| 73 |
+
attn_type: str,
|
| 74 |
+
kv_sharing_target_layer_name: str | None,
|
| 75 |
+
indexer: "Indexer | None" = None,
|
| 76 |
+
**mla_args,
|
| 77 |
+
) -> None:
|
| 78 |
+
if any([alibi_slopes, sliding_window, logits_soft_cap]):
|
| 79 |
+
raise NotImplementedError(
|
| 80 |
+
"FLASHINFER_MLA_SPARSE_SM120 does not support alibi_slopes / "
|
| 81 |
+
"sliding_window / logits_soft_cap"
|
| 82 |
+
)
|
| 83 |
+
if attn_type != AttentionType.DECODER:
|
| 84 |
+
raise NotImplementedError(
|
| 85 |
+
"FLASHINFER_MLA_SPARSE_SM120 only supports decoder self-attention"
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
self.num_heads = num_heads
|
| 89 |
+
self.head_size = head_size
|
| 90 |
+
self.scale = float(scale)
|
| 91 |
+
self.num_kv_heads = num_kv_heads
|
| 92 |
+
self.kv_cache_dtype = kv_cache_dtype
|
| 93 |
+
if self.kv_cache_dtype != "fp8_ds_mla":
|
| 94 |
+
raise NotImplementedError(
|
| 95 |
+
"FLASHINFER_MLA_SPARSE_SM120 requires the packed fp8_ds_mla "
|
| 96 |
+
f"KV cache layout; got kv_cache_dtype={kv_cache_dtype!r}."
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
self.kv_lora_rank: int = mla_args["kv_lora_rank"]
|
| 100 |
+
self.qk_nope_head_dim: int = mla_args["qk_nope_head_dim"]
|
| 101 |
+
self.qk_rope_head_dim: int = mla_args["qk_rope_head_dim"]
|
| 102 |
+
self.rope_pad = 0
|
| 103 |
+
if self.qk_rope_head_dim == 0:
|
| 104 |
+
if self.kv_lora_rank != 512:
|
| 105 |
+
raise NotImplementedError(
|
| 106 |
+
"FLASHINFER_MLA_SPARSE_SM120 pads NoPE MLA into the "
|
| 107 |
+
"576-wide GLM_NSA geometry, which requires "
|
| 108 |
+
f"kv_lora_rank=512; got {self.kv_lora_rank}."
|
| 109 |
+
)
|
| 110 |
+
self.rope_pad = 64
|
| 111 |
+
self.kernel_qk_rope_head_dim = self.qk_rope_head_dim + self.rope_pad
|
| 112 |
+
from vllm.config import get_current_vllm_config
|
| 113 |
+
|
| 114 |
+
vllm_config = get_current_vllm_config()
|
| 115 |
+
model_type = None
|
| 116 |
+
if vllm_config.model_config is not None:
|
| 117 |
+
model_type = getattr(
|
| 118 |
+
vllm_config.model_config.hf_text_config, "model_type", None
|
| 119 |
+
)
|
| 120 |
+
self.kv_scale_format = _kv_scale_format_for_model(model_type)
|
| 121 |
+
|
| 122 |
+
# Skip-topk layers are built with indexer=None and get the shared
|
| 123 |
+
# buffer via mla_args instead (cf. FLASHMLA_SPARSE).
|
| 124 |
+
self.topk_indices_buffer: torch.Tensor | None = (
|
| 125 |
+
indexer.topk_indices_buffer
|
| 126 |
+
if indexer is not None
|
| 127 |
+
else mla_args.get("topk_indices_buffer")
|
| 128 |
+
)
|
| 129 |
+
from vllm.utils.flashinfer import has_flashinfer_sparse_mla_sm120
|
| 130 |
+
|
| 131 |
+
if not has_flashinfer_sparse_mla_sm120():
|
| 132 |
+
raise RuntimeError(
|
| 133 |
+
"FLASHINFER_MLA_SPARSE_SM120 requires FlashInfer's "
|
| 134 |
+
"sparse MLA decode API."
|
| 135 |
+
)
|
| 136 |
+
assert self.topk_indices_buffer is not None
|
| 137 |
+
|
| 138 |
+
self.supports_quant_query_input = False
|
| 139 |
+
self._workspace_buffer: torch.Tensor | None = None
|
| 140 |
+
self._dcp_logged = False
|
| 141 |
+
|
| 142 |
+
@staticmethod
|
| 143 |
+
def _normalize_lse(lse: torch.Tensor, num_tokens: int, num_heads: int) -> torch.Tensor:
|
| 144 |
+
# (num_tokens, num_heads) is what the shared DCP reducer expects; the kernel may
|
| 145 |
+
# hand back (T, H, 1) / (T, 1, H) / (B, q_len, H).
|
| 146 |
+
if lse.dim() == 3:
|
| 147 |
+
if lse.shape[-1] == 1:
|
| 148 |
+
lse = lse.squeeze(-1)
|
| 149 |
+
elif lse.shape[1] == 1:
|
| 150 |
+
lse = lse.squeeze(1)
|
| 151 |
+
elif lse.shape[0] * lse.shape[1] == num_tokens:
|
| 152 |
+
lse = lse.reshape(num_tokens, lse.shape[-1])
|
| 153 |
+
if lse.shape != (num_tokens, num_heads):
|
| 154 |
+
raise RuntimeError(
|
| 155 |
+
"Unexpected SM120 sparse MLA LSE shape: "
|
| 156 |
+
f"{tuple(lse.shape)}, expected ({num_tokens}, {num_heads})."
|
| 157 |
+
)
|
| 158 |
+
return lse
|
| 159 |
+
|
| 160 |
+
def forward_mqa(
|
| 161 |
+
self,
|
| 162 |
+
q: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
|
| 163 |
+
kv_c_and_k_pe_cache: torch.Tensor,
|
| 164 |
+
attn_metadata: FlashInferMLASparseMetadata,
|
| 165 |
+
layer: AttentionLayer,
|
| 166 |
+
) -> tuple[torch.Tensor, torch.Tensor | None]:
|
| 167 |
+
if isinstance(q, tuple):
|
| 168 |
+
q = torch.cat(q, dim=-1)
|
| 169 |
+
if self.rope_pad:
|
| 170 |
+
q = torch.nn.functional.pad(q, (0, self.rope_pad))
|
| 171 |
+
|
| 172 |
+
num_actual_toks = q.shape[0]
|
| 173 |
+
# Under DCP the layer all-gathers queries across the CP ranks first, so q carries
|
| 174 |
+
# every head (not just this rank's TP slice).
|
| 175 |
+
num_heads = q.shape[1]
|
| 176 |
+
|
| 177 |
+
assert self.topk_indices_buffer is not None
|
| 178 |
+
topk_indices = self.topk_indices_buffer[:num_actual_toks]
|
| 179 |
+
|
| 180 |
+
use_dcp = self.dcp_world_size > 1
|
| 181 |
+
if use_dcp:
|
| 182 |
+
if not self._dcp_logged:
|
| 183 |
+
from vllm.logger import init_logger
|
| 184 |
+
|
| 185 |
+
init_logger(__name__).info(
|
| 186 |
+
"[keys-DCP] SM120 sparse MLA: dcp_world=%d rank=%d interleave=%d "
|
| 187 |
+
"heads=%d lse_base_e=%s",
|
| 188 |
+
self.dcp_world_size,
|
| 189 |
+
self.dcp_rank,
|
| 190 |
+
attn_metadata.cp_kv_cache_interleave_size,
|
| 191 |
+
num_heads,
|
| 192 |
+
self.lse_base_on_e,
|
| 193 |
+
)
|
| 194 |
+
self._dcp_logged = True
|
| 195 |
+
topk_indices_physical, topk_lengths = cast(
|
| 196 |
+
tuple[torch.Tensor, torch.Tensor],
|
| 197 |
+
triton_filter_and_convert_dcp_index(
|
| 198 |
+
attn_metadata.req_id_per_token[:num_actual_toks],
|
| 199 |
+
attn_metadata.block_table,
|
| 200 |
+
topk_indices,
|
| 201 |
+
dcp_size=self.dcp_world_size,
|
| 202 |
+
dcp_rank=self.dcp_rank,
|
| 203 |
+
cp_kv_cache_interleave_size=attn_metadata.cp_kv_cache_interleave_size,
|
| 204 |
+
BLOCK_SIZE=attn_metadata.block_size,
|
| 205 |
+
NUM_TOPK_TOKENS=topk_indices.shape[1],
|
| 206 |
+
return_valid_counts=True,
|
| 207 |
+
),
|
| 208 |
+
)
|
| 209 |
+
else:
|
| 210 |
+
topk_indices_physical, topk_lengths = cast(
|
| 211 |
+
tuple[torch.Tensor, torch.Tensor],
|
| 212 |
+
triton_convert_req_index_to_global_index(
|
| 213 |
+
attn_metadata.req_id_per_token[:num_actual_toks],
|
| 214 |
+
attn_metadata.block_table,
|
| 215 |
+
topk_indices,
|
| 216 |
+
BLOCK_SIZE=attn_metadata.block_size,
|
| 217 |
+
NUM_TOPK_TOKENS=topk_indices.shape[1],
|
| 218 |
+
return_valid_counts=True,
|
| 219 |
+
),
|
| 220 |
+
)
|
| 221 |
+
sparse_topk_capacity = topk_indices_physical.shape[1]
|
| 222 |
+
empty_rows = topk_lengths == 0
|
| 223 |
+
topk_indices_physical[:, 0] = topk_indices_physical[:, 0].masked_fill(
|
| 224 |
+
empty_rows, 0
|
| 225 |
+
)
|
| 226 |
+
topk_lengths = topk_lengths.clamp(min=1)
|
| 227 |
+
|
| 228 |
+
output = q.new_empty(
|
| 229 |
+
(num_actual_toks, num_heads, self.kv_lora_rank),
|
| 230 |
+
dtype=q.dtype,
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
if self._workspace_buffer is None:
|
| 234 |
+
self._workspace_buffer = _get_workspace_buffer(q.device)
|
| 235 |
+
|
| 236 |
+
from vllm.utils.flashinfer import (
|
| 237 |
+
flashinfer_trtllm_batch_decode_with_kv_cache_mla,
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
want_lse = bool(self.need_to_return_lse_for_decode)
|
| 241 |
+
kernel_out = flashinfer_trtllm_batch_decode_with_kv_cache_mla(
|
| 242 |
+
query=q.unsqueeze(1),
|
| 243 |
+
kv_cache=kv_c_and_k_pe_cache.view(torch.uint8).unsqueeze(1),
|
| 244 |
+
workspace_buffer=self._workspace_buffer,
|
| 245 |
+
qk_nope_head_dim=self.qk_nope_head_dim,
|
| 246 |
+
kv_lora_rank=self.kv_lora_rank,
|
| 247 |
+
qk_rope_head_dim=self.kernel_qk_rope_head_dim,
|
| 248 |
+
block_tables=topk_indices_physical.unsqueeze(1),
|
| 249 |
+
seq_lens=topk_lengths,
|
| 250 |
+
max_seq_len=sparse_topk_capacity,
|
| 251 |
+
out=output.unsqueeze(1),
|
| 252 |
+
bmm1_scale=self.scale,
|
| 253 |
+
bmm2_scale=1.0,
|
| 254 |
+
sparse_mla_top_k=sparse_topk_capacity,
|
| 255 |
+
kv_scale_format=self.kv_scale_format,
|
| 256 |
+
return_lse=want_lse,
|
| 257 |
+
)
|
| 258 |
+
lse: torch.Tensor | None = None
|
| 259 |
+
if want_lse:
|
| 260 |
+
assert isinstance(kernel_out, tuple), "SM120 sparse MLA did not return LSE"
|
| 261 |
+
out, lse = kernel_out
|
| 262 |
+
else:
|
| 263 |
+
out = kernel_out
|
| 264 |
+
out = out.view(-1, out.shape[-2], out.shape[-1])
|
| 265 |
+
out.masked_fill_(empty_rows.view(-1, 1, 1), 0.0)
|
| 266 |
+
if lse is not None:
|
| 267 |
+
lse = self._normalize_lse(lse, out.shape[0], out.shape[1])
|
| 268 |
+
lse = lse.masked_fill(empty_rows.view(-1, 1), float("-inf"))
|
| 269 |
+
return out, lse
|
| 270 |
+
|
| 271 |
+
def do_kv_cache_update(
|
| 272 |
+
self,
|
| 273 |
+
kv_c_normed: torch.Tensor,
|
| 274 |
+
k_pe: torch.Tensor,
|
| 275 |
+
kv_cache: torch.Tensor,
|
| 276 |
+
slot_mapping: torch.Tensor,
|
| 277 |
+
kv_cache_dtype: str,
|
| 278 |
+
k_scale: torch.Tensor,
|
| 279 |
+
) -> None:
|
| 280 |
+
if self.rope_pad:
|
| 281 |
+
k_pe = k_pe.new_zeros((k_pe.shape[0], 1, self.rope_pad))
|
| 282 |
+
super().do_kv_cache_update(
|
| 283 |
+
kv_c_normed, k_pe, kv_cache, slot_mapping, kv_cache_dtype, k_scale
|
| 284 |
+
)
|
serving/start-tp4.sh
ADDED
|
@@ -0,0 +1,1776 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# ============================================================================
|
| 3 |
+
# start-tp4.sh — EXPERIMENTAL 4× DGX Spark TP=4 launcher for GLM-5.3-Flash EXL3
|
| 4 |
+
# ============================================================================
|
| 5 |
+
#
|
| 6 |
+
# Optional sibling of start.sh. Does not change the supported 2× TP=2 path.
|
| 7 |
+
# Untested here (no 4-Spark kit). A user with four GB10s can try:
|
| 8 |
+
#
|
| 9 |
+
# ./start-tp4.sh
|
| 10 |
+
#
|
| 11 |
+
# Layout (mp executor, not Ray):
|
| 12 |
+
# rank 0 HEAD_IP (default 10.0.0.1) — vLLM API on :8888
|
| 13 |
+
# rank 1 WORKER_IP (default 10.0.0.2) — --headless
|
| 14 |
+
# rank 2 WORKER2_IP (default 10.0.0.3) — --headless
|
| 15 |
+
# rank 3 WORKER3_IP (default 10.0.0.4) — --headless
|
| 16 |
+
# --tensor-parallel-size 4 --nnodes 4
|
| 17 |
+
#
|
| 18 |
+
# Fabric: four Sparks need a RoCE path among all ranks (QSFP ring using both
|
| 19 |
+
# CX7 ports, or a switch). Defaults set NCCL_CROSS_NIC=1. Pin per-rank
|
| 20 |
+
# WORKER2_CX7_IF / WORKER2_CX7_IB / WORKER2_GID (same for 3) in .env.
|
| 21 |
+
# Dual-port example: RANK NCCL_IB_HCA="rocep1s0f0,rocep1s0f1".
|
| 22 |
+
#
|
| 23 |
+
# Same image/weights as start.sh. Container names are glm53-exl3-tp4-* so a
|
| 24 |
+
# TP=2 serve is not accidentally reused. Stop with ./start-tp4.sh stop
|
| 25 |
+
# (./start.sh stop does not know about ranks 2/3).
|
| 26 |
+
#
|
| 27 |
+
# Usage:
|
| 28 |
+
# ./start-tp4.sh start TP=4
|
| 29 |
+
# ./start-tp4.sh download head HF cache only
|
| 30 |
+
# ./start-tp4.sh stop|restart|status
|
| 31 |
+
# ./start-tp4.sh logs follow head
|
| 32 |
+
# ./start-tp4.sh logs 1|2|3 follow that worker rank
|
| 33 |
+
#
|
| 34 |
+
# Extra knobs live in .env.tp4 (copied from .env.tp4.example). start.sh
|
| 35 |
+
# never reads that file. Shared tokens/IPs can stay in .env.
|
| 36 |
+
# ============================================================================
|
| 37 |
+
set -euo pipefail
|
| 38 |
+
|
| 39 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
|
| 40 |
+
|
| 41 |
+
if [ ! -f "$SCRIPT_DIR/.env" ]; then
|
| 42 |
+
[ -f "$SCRIPT_DIR/.env.example" ] || {
|
| 43 |
+
echo "ERROR: missing .env.example" >&2
|
| 44 |
+
exit 1
|
| 45 |
+
}
|
| 46 |
+
cp "$SCRIPT_DIR/.env.example" "$SCRIPT_DIR/.env"
|
| 47 |
+
printf '\033[1;36m[glm53-exl3-tp4]\033[0m wrote .env from .env.example\n'
|
| 48 |
+
fi
|
| 49 |
+
if [ ! -f "$SCRIPT_DIR/.env.tp4" ]; then
|
| 50 |
+
[ -f "$SCRIPT_DIR/.env.tp4.example" ] || {
|
| 51 |
+
echo "ERROR: missing .env.tp4.example" >&2
|
| 52 |
+
exit 1
|
| 53 |
+
}
|
| 54 |
+
cp "$SCRIPT_DIR/.env.tp4.example" "$SCRIPT_DIR/.env.tp4"
|
| 55 |
+
printf '\033[1;36m[glm53-exl3-tp4]\033[0m wrote .env.tp4 from .env.tp4.example — edit WORKER2_IP / WORKER3_IP / CX7 pins\n'
|
| 56 |
+
fi
|
| 57 |
+
# Caller exports (MTP_TOKENS=2 ./start.sh restart) must win over .env.
|
| 58 |
+
_cli_mtp="${MTP_TOKENS-}"
|
| 59 |
+
_cli_spec="${SPEC_METHOD-}"
|
| 60 |
+
_cli_eager="${ENFORCE_EAGER-}"
|
| 61 |
+
_cli_fused="${EXL3_FUSED_MOE-}"
|
| 62 |
+
_cli_row_tile="${EXL3_MOE_ROW_TILE-}"
|
| 63 |
+
_cli_temp_rows="${EXL3_TEMP_ROWS_FUSED-}"
|
| 64 |
+
_cli_fat_sorted="${EXL3_FAT_SORTED-}"
|
| 65 |
+
_cli_fat_batched="${EXL3_FAT_BATCHED-}"
|
| 66 |
+
_cli_fat_kernel="${EXL3_FAT_KERNEL-}"
|
| 67 |
+
_cli_mnbt="${MAX_NUM_BATCHED_TOKENS-}"
|
| 68 |
+
_cli_image="${IMAGE-}"
|
| 69 |
+
_cli_util="${GPU_MEM_UTIL-}"
|
| 70 |
+
_cli_lm="${LANGUAGE_MODEL_ONLY-}"
|
| 71 |
+
_cli_max_num_seqs="${MAX_NUM_SEQS-}"
|
| 72 |
+
_cli_ablit="${ABLIT-}"
|
| 73 |
+
_cli_ablit_method="${ABLIT_METHOD-}"
|
| 74 |
+
_cli_ablit_direction="${ABLIT_DIRECTION-}"
|
| 75 |
+
_cli_ablit_layers="${ABLIT_LAYERS-}"
|
| 76 |
+
_cli_ablit_alpha="${ABLIT_ALPHA-}"
|
| 77 |
+
_cli_ablit_mtp="${ABLIT_INCLUDE_MTP-}"
|
| 78 |
+
# Setness-aware: an explicitly empty caller value is an operator error and
|
| 79 |
+
# must reach validate_numeric_config, not be swallowed by a .env value.
|
| 80 |
+
_cli_indexer_workspace_set="${GLM53_INDEXER_WORKSPACE+1}"
|
| 81 |
+
_cli_indexer_workspace="${GLM53_INDEXER_WORKSPACE-}"
|
| 82 |
+
_cli_spinwait_ms_set="${GLM53_SPINWAIT_MS+1}"
|
| 83 |
+
_cli_spinwait_ms="${GLM53_SPINWAIT_MS-}"
|
| 84 |
+
set -a
|
| 85 |
+
# shellcheck disable=SC1091
|
| 86 |
+
source "$SCRIPT_DIR/.env"
|
| 87 |
+
# TP=4 overlay wins over the 2× knobs in .env.
|
| 88 |
+
# shellcheck disable=SC1091
|
| 89 |
+
source "$SCRIPT_DIR/.env.tp4"
|
| 90 |
+
set +a
|
| 91 |
+
[ -n "${_cli_mtp}" ] && MTP_TOKENS="$_cli_mtp"
|
| 92 |
+
[ -n "${_cli_spec}" ] && SPEC_METHOD="$_cli_spec"
|
| 93 |
+
[ -n "${_cli_eager}" ] && ENFORCE_EAGER="$_cli_eager"
|
| 94 |
+
[ -n "${_cli_fused}" ] && EXL3_FUSED_MOE="$_cli_fused"
|
| 95 |
+
[ -n "${_cli_row_tile}" ] && EXL3_MOE_ROW_TILE="$_cli_row_tile"
|
| 96 |
+
[ -n "${_cli_temp_rows}" ] && EXL3_TEMP_ROWS_FUSED="$_cli_temp_rows"
|
| 97 |
+
[ -n "${_cli_fat_sorted}" ] && EXL3_FAT_SORTED="$_cli_fat_sorted"
|
| 98 |
+
[ -n "${_cli_fat_batched}" ] && EXL3_FAT_BATCHED="$_cli_fat_batched"
|
| 99 |
+
[ -n "${_cli_fat_kernel}" ] && EXL3_FAT_KERNEL="$_cli_fat_kernel"
|
| 100 |
+
[ -n "${_cli_mnbt}" ] && MAX_NUM_BATCHED_TOKENS="$_cli_mnbt"
|
| 101 |
+
[ -n "${_cli_image}" ] && IMAGE="$_cli_image"
|
| 102 |
+
[ -n "${_cli_util}" ] && GPU_MEM_UTIL="$_cli_util"
|
| 103 |
+
[ -n "${_cli_lm}" ] && LANGUAGE_MODEL_ONLY="$_cli_lm"
|
| 104 |
+
[ -n "${_cli_max_num_seqs}" ] && MAX_NUM_SEQS="$_cli_max_num_seqs"
|
| 105 |
+
[ -n "${_cli_ablit}" ] && ABLIT="$_cli_ablit"
|
| 106 |
+
[ -n "${_cli_ablit_method}" ] && ABLIT_METHOD="$_cli_ablit_method"
|
| 107 |
+
[ -n "${_cli_ablit_direction}" ] && ABLIT_DIRECTION="$_cli_ablit_direction"
|
| 108 |
+
[ -n "${_cli_ablit_layers}" ] && ABLIT_LAYERS="$_cli_ablit_layers"
|
| 109 |
+
[ -n "${_cli_ablit_alpha}" ] && ABLIT_ALPHA="$_cli_ablit_alpha"
|
| 110 |
+
[ -n "${_cli_ablit_mtp}" ] && ABLIT_INCLUDE_MTP="$_cli_ablit_mtp"
|
| 111 |
+
[ -n "${_cli_indexer_workspace_set}" ] && GLM53_INDEXER_WORKSPACE="$_cli_indexer_workspace"
|
| 112 |
+
[ -n "${_cli_spinwait_ms_set}" ] && GLM53_SPINWAIT_MS="$_cli_spinwait_ms"
|
| 113 |
+
|
| 114 |
+
# ----------------------------- configuration -------------------------------
|
| 115 |
+
MODEL="${MODEL:-Mia-AiLab/GLM-5.3-Flash-EXL3-TR3-4bpw}"
|
| 116 |
+
# If the durable mirror is empty/moved, download.sh falls back to this id.
|
| 117 |
+
MODEL_FALLBACK="${MODEL_FALLBACK:-brandonmusic/GLM-5.3-Flash-tr3-4bpw}"
|
| 118 |
+
MODEL_CACHE_NAME="${MODEL_CACHE_NAME:-models--${MODEL//\//--}}"
|
| 119 |
+
MODEL_FALLBACK_CACHE_NAME="${MODEL_FALLBACK_CACHE_NAME:-models--${MODEL_FALLBACK//\//--}}"
|
| 120 |
+
# Hub commit on the Mia-AiLab mirror (the 5ab363a8-byte-identical upload).
|
| 121 |
+
MODEL_REVISION="${MODEL_REVISION:-25a44fdbf16862a46b7cc9921142c6c81350af2f}"
|
| 122 |
+
IMAGE="${IMAGE:-ghcr.io/miaai-lab/glm-5.3-flash-2x-dgx-sparks:exl3}"
|
| 123 |
+
SERVED_MODEL_NAME="${SERVED_MODEL_NAME:-GLM-5.3-Flash-EXL3}"
|
| 124 |
+
GHCR_USER="${GHCR_USER:-MiaAI-Lab}"
|
| 125 |
+
|
| 126 |
+
HEAD_IP="${HEAD_IP:-10.0.0.1}"
|
| 127 |
+
WORKER_IP="${WORKER_IP:-10.0.0.2}"
|
| 128 |
+
# Same OS user on both Sparks unless .env sets WORKER_USER (mixed-account kits).
|
| 129 |
+
WORKER_USER="${WORKER_USER:-$USER}"
|
| 130 |
+
if [ "$WORKER_USER" = "$USER" ]; then
|
| 131 |
+
WORKER_HOME="${WORKER_HOME:-$HOME}"
|
| 132 |
+
else
|
| 133 |
+
WORKER_HOME="${WORKER_HOME:-/home/${WORKER_USER}}"
|
| 134 |
+
fi
|
| 135 |
+
WORKER_SSH="${WORKER_SSH:-${WORKER_USER}@${WORKER_IP}}"
|
| 136 |
+
|
| 137 |
+
# Ranks 2 and 3 (experimental TP=4). Same OS user as WORKER_USER unless set.
|
| 138 |
+
WORKER2_IP="${WORKER2_IP:-10.0.0.3}"
|
| 139 |
+
WORKER3_IP="${WORKER3_IP:-10.0.0.4}"
|
| 140 |
+
WORKER2_USER="${WORKER2_USER:-$WORKER_USER}"
|
| 141 |
+
WORKER3_USER="${WORKER3_USER:-$WORKER_USER}"
|
| 142 |
+
if [ "$WORKER2_USER" = "$USER" ]; then
|
| 143 |
+
WORKER2_HOME="${WORKER2_HOME:-$HOME}"
|
| 144 |
+
else
|
| 145 |
+
WORKER2_HOME="${WORKER2_HOME:-/home/${WORKER2_USER}}"
|
| 146 |
+
fi
|
| 147 |
+
if [ "$WORKER3_USER" = "$USER" ]; then
|
| 148 |
+
WORKER3_HOME="${WORKER3_HOME:-$HOME}"
|
| 149 |
+
else
|
| 150 |
+
WORKER3_HOME="${WORKER3_HOME:-/home/${WORKER3_USER}}"
|
| 151 |
+
fi
|
| 152 |
+
WORKER2_SSH="${WORKER2_SSH:-${WORKER2_USER}@${WORKER2_IP}}"
|
| 153 |
+
WORKER3_SSH="${WORKER3_SSH:-${WORKER3_USER}@${WORKER3_IP}}"
|
| 154 |
+
|
| 155 |
+
HEAD_CX7_IF="${HEAD_CX7_IF:-enp1s0f1np1}"
|
| 156 |
+
WORKER_CX7_IF="${WORKER_CX7_IF:-enp1s0f0np0}"
|
| 157 |
+
HEAD_CX7_IB="${HEAD_CX7_IB:-rocep1s0f1}"
|
| 158 |
+
WORKER_CX7_IB="${WORKER_CX7_IB:-rocep1s0f0}"
|
| 159 |
+
WORKER2_CX7_IF="${WORKER2_CX7_IF:-$WORKER_CX7_IF}"
|
| 160 |
+
WORKER2_CX7_IB="${WORKER2_CX7_IB:-$WORKER_CX7_IB}"
|
| 161 |
+
WORKER3_CX7_IF="${WORKER3_CX7_IF:-$WORKER_CX7_IF}"
|
| 162 |
+
WORKER3_CX7_IB="${WORKER3_CX7_IB:-$WORKER_CX7_IB}"
|
| 163 |
+
NCCL_DEBUG="${NCCL_DEBUG:-WARN}"
|
| 164 |
+
NCCL_IB_GID_INDEX="${NCCL_IB_GID_INDEX:-3}"
|
| 165 |
+
# The RoCEv2 GID index is per-NIC: the usable entry is the one whose GID matches
|
| 166 |
+
# that node's own fabric IP. Most pairs share a good index; some do not (this kit
|
| 167 |
+
# needs head=4, worker=3). Unset, both inherit NCCL_IB_GID_INDEX -> unchanged.
|
| 168 |
+
HEAD_GID="${HEAD_GID:-$NCCL_IB_GID_INDEX}"
|
| 169 |
+
WORKER_GID="${WORKER_GID:-$NCCL_IB_GID_INDEX}"
|
| 170 |
+
WORKER2_GID="${WORKER2_GID:-$NCCL_IB_GID_INDEX}"
|
| 171 |
+
WORKER3_GID="${WORKER3_GID:-$NCCL_IB_GID_INDEX}"
|
| 172 |
+
# vLLM subtracts a CUDA-graph memory ESTIMATE from the KV pool. On this kit the
|
| 173 |
+
# estimate is 2.43 GiB while the captured graphs actually consume -0.19 GiB, so
|
| 174 |
+
# ~2.6 GiB of KV is reserved and never used. 0 keeps CUDA graphs ON and drops only
|
| 175 |
+
# the deduction. 1 = upstream default.
|
| 176 |
+
CG_ESTIMATE="${CG_ESTIMATE:-1}"
|
| 177 |
+
NCCL_CROSS_NIC="${NCCL_CROSS_NIC:-1}"
|
| 178 |
+
NCCL_HOST_DIR="${NCCL_HOST_DIR:-$HOME/nccl-2.30.7}"
|
| 179 |
+
WORKER_NCCL_HOST_DIR="${WORKER_NCCL_HOST_DIR:-$WORKER_HOME/nccl-2.30.7}"
|
| 180 |
+
WORKER2_NCCL_HOST_DIR="${WORKER2_NCCL_HOST_DIR:-$WORKER2_HOME/nccl-2.30.7}"
|
| 181 |
+
WORKER3_NCCL_HOST_DIR="${WORKER3_NCCL_HOST_DIR:-$WORKER3_HOME/nccl-2.30.7}"
|
| 182 |
+
NCCL_SO_NAME="${NCCL_SO_NAME:-libnccl.so.2.30.7}"
|
| 183 |
+
# glm53-flash already ships nvidia-nccl. LD_PRELOAD of the host 2.30.7 SO
|
| 184 |
+
# makes DeepEP assert duplicate NCCL (/nccl/... vs nvidia/nccl/lib/...).
|
| 185 |
+
# Set USE_HOST_NCCL=1 only if image NCCL cannot talk CX7.
|
| 186 |
+
USE_HOST_NCCL="${USE_HOST_NCCL:-0}"
|
| 187 |
+
|
| 188 |
+
TP="${TP:-4}"
|
| 189 |
+
NNODES="${NNODES:-4}"
|
| 190 |
+
PORT="${PORT:-8888}"
|
| 191 |
+
MASTER_PORT="${MASTER_PORT:-29521}"
|
| 192 |
+
|
| 193 |
+
MTP_TOKENS="${MTP_TOKENS:-2}"
|
| 194 |
+
# dflash (default, incoai/GLM-5.3-Flash-DFlash2, k=7) | mtp | none
|
| 195 |
+
SPEC_METHOD="${SPEC_METHOD:-dflash}"
|
| 196 |
+
DFLASH_MODEL="${DFLASH_MODEL:-incoai/GLM-5.3-Flash-DFlash2}"
|
| 197 |
+
DFLASH_CACHE_NAME="${DFLASH_CACHE_NAME:-models--${DFLASH_MODEL//\//--}}"
|
| 198 |
+
DFLASH_TOKENS="${DFLASH_TOKENS:-7}"
|
| 199 |
+
# 2 = shard the ~2.3 GiB DFlash2 drafter across TP (C4 keep, 2026-08-30:
|
| 200 |
+
# idle 8k 938 / 16k 972 / 100k 997; decode structured 65.1 / prose 27.1).
|
| 201 |
+
# 1 = rank 0 only (no CX7 on every draft step). Empty = inherit target TP.
|
| 202 |
+
# Do not pin attention_backend: SM121 already prefers FLASH_ATTN for
|
| 203 |
+
# non-causal dense SWA. TRITON_ATTN was an SM120 mask-fix this image lacks.
|
| 204 |
+
DFLASH_DRAFT_TP="${DFLASH_DRAFT_TP-4}"
|
| 205 |
+
MAX_MODEL_LEN="${MAX_MODEL_LEN:-1000000}"
|
| 206 |
+
GPU_MEM_UTIL="${GPU_MEM_UTIL:-0.87}"
|
| 207 |
+
MAX_NUM_SEQS="${MAX_NUM_SEQS:-4}"
|
| 208 |
+
# 8192 chunk × long history oversubscribes GB10 persistent_topk smem (300k crash).
|
| 209 |
+
# E2 one-shot 2026-09-01: 7168 keep (100k ~1148 / 300k ~1107); 2048/3548 similar or slower.
|
| 210 |
+
MAX_NUM_BATCHED_TOKENS="${MAX_NUM_BATCHED_TOKENS:-7168}"
|
| 211 |
+
CHAT_TEMPLATE_HOST="${CHAT_TEMPLATE_HOST:-$SCRIPT_DIR/files/chat_template.jinja}"
|
| 212 |
+
CHAT_TEMPLATE="${CHAT_TEMPLATE:-/opt/glm53/chat_template.jinja}"
|
| 213 |
+
VIDEO_PATCH_HOST="${VIDEO_PATCH_HOST:-$SCRIPT_DIR/overlay/patch_glm_video_placeholders.py}"
|
| 214 |
+
STOP_PATCH_HOST="${STOP_PATCH_HOST:-$SCRIPT_DIR/overlay/patch_suppress_stops_in_reasoning.py}"
|
| 215 |
+
SCHED_PATCH_HOST="${SCHED_PATCH_HOST:-$SCRIPT_DIR/overlay/patch_scheduler_decode_floor.py}"
|
| 216 |
+
DRAFTER_PATCH_HOST="${DRAFTER_PATCH_HOST:-$SCRIPT_DIR/overlay/patch_glm5_drafter_group.py}"
|
| 217 |
+
APC_PATCH_HOST="${APC_PATCH_HOST:-$SCRIPT_DIR/overlay/patch_hybrid_prefix_hit.py}"
|
| 218 |
+
XGRAMMAR_PATCH_HOST="${XGRAMMAR_PATCH_HOST:-$SCRIPT_DIR/overlay/patch_xgrammar_termination.py}"
|
| 219 |
+
KPOOL_TAIL_PATCH_HOST="${KPOOL_TAIL_PATCH_HOST:-$SCRIPT_DIR/overlay/patch_kpool_tail_slotmap.py}"
|
| 220 |
+
SPINWAIT_PATCH_HOST="${SPINWAIT_PATCH_HOST:-$SCRIPT_DIR/overlay/patch_spinwait.py}"
|
| 221 |
+
KV_CACHE_DTYPE="${KV_CACHE_DTYPE:-fp8}"
|
| 222 |
+
QUANTIZATION="${QUANTIZATION:-exl3}"
|
| 223 |
+
LANGUAGE_MODEL_ONLY="${LANGUAGE_MODEL_ONLY:-0}"
|
| 224 |
+
SKIP_MM_PROFILING="${SKIP_MM_PROFILING:-1}"
|
| 225 |
+
# JSON default cannot sit in ${LIMIT_MM:-{...}} — } ends the expansion.
|
| 226 |
+
if [ -z "${LIMIT_MM:-}" ]; then
|
| 227 |
+
LIMIT_MM='{"image":4,"video":1}'
|
| 228 |
+
fi
|
| 229 |
+
TORCH_CUDA_ARCH_LIST="${TORCH_CUDA_ARCH_LIST:-12.1a}"
|
| 230 |
+
FLASHINFER_CUDA_ARCH_LIST="${FLASHINFER_CUDA_ARCH_LIST:-12.1a}"
|
| 231 |
+
# Graph-safe fused apply (device-side expert grouping). MTP k=2 decode is
|
| 232 |
+
# 1..4 seqs × 3 tokens (must include 3). DFlash2 k=7 is 1..4 seqs × 8 tokens
|
| 233 |
+
# (must include 8, 16, 24, 32).
|
| 234 |
+
ENFORCE_EAGER="${ENFORCE_EAGER:-0}"
|
| 235 |
+
if [ "${ENFORCE_EAGER}" != "1" ]; then
|
| 236 |
+
case " ${EXTRA_ARGS:-} " in
|
| 237 |
+
*" --cudagraph-capture-sizes "*|*" cudagraph-capture-sizes "*) ;;
|
| 238 |
+
*)
|
| 239 |
+
if [ "$SPEC_METHOD" = "dflash" ]; then
|
| 240 |
+
EXTRA_ARGS="${EXTRA_ARGS:+$EXTRA_ARGS }--cudagraph-capture-sizes 1 2 4 8 16 24 32"
|
| 241 |
+
else
|
| 242 |
+
EXTRA_ARGS="${EXTRA_ARGS:+$EXTRA_ARGS }--cudagraph-capture-sizes 1 2 3 4 6 8 12"
|
| 243 |
+
fi
|
| 244 |
+
;;
|
| 245 |
+
esac
|
| 246 |
+
fi
|
| 247 |
+
# 1 = fused exl3_moe (decode). 0 restores the unique-expert LinearEXL3 loop.
|
| 248 |
+
EXL3_FUSED_MOE="${EXL3_FUSED_MOE:-1}"
|
| 249 |
+
# 1 = GPU row tiles for fat experts (prefill). 0 = LinearEXL3 fallback.
|
| 250 |
+
# Tile (P2a) and TEMP_ROWS=1024 (P2b) both lost at MNBT=1024 — leave 128.
|
| 251 |
+
EXL3_MOE_ROW_TILE="${EXL3_MOE_ROW_TILE:-0}"
|
| 252 |
+
# Fused exl3_moe temp rows/expert. 1024 was slower than 128+fallback (P2b).
|
| 253 |
+
EXL3_TEMP_ROWS_FUSED="${EXL3_TEMP_ROWS_FUSED:-128}"
|
| 254 |
+
# Sorted routing tier; higher tiers imply it even when this is 0.
|
| 255 |
+
EXL3_FAT_SORTED="${EXL3_FAT_SORTED:-0}"
|
| 256 |
+
# E1 batched tier: persistent scratch + combined gate/up; implies SORTED=1.
|
| 257 |
+
EXL3_FAT_BATCHED="${EXL3_FAT_BATCHED:-0}"
|
| 258 |
+
# E2 direct trellis kernel (default on). Implies BATCHED=1 and SORTED=1.
|
| 259 |
+
# Needs the patched extension — start.sh rebuilds when the recipe stamp drifts.
|
| 260 |
+
# Set all three flags to 0 for the legacy fat-expert path.
|
| 261 |
+
EXL3_FAT_KERNEL="${EXL3_FAT_KERNEL:-1}"
|
| 262 |
+
|
| 263 |
+
# --- abliteration (ablit/) --------------------------------------------------
|
| 264 |
+
# Load-time o_proj orthogonalization (overlay/ablit_runtime.py). Published
|
| 265 |
+
# recipe: layers 15-45 edited with the dealign direction, 0-14 stay stock
|
| 266 |
+
# safety anchors, MTP block included. 0 = stock weights. Applied identically
|
| 267 |
+
# on both TP ranks; the DFlash2 drafter is never touched.
|
| 268 |
+
ABLIT="${ABLIT:-0}"
|
| 269 |
+
ABLIT_METHOD="${ABLIT_METHOD:-auto}" # auto | transplant | proj
|
| 270 |
+
ABLIT_DIRECTION="${ABLIT_DIRECTION:-dealign}" # dealign | bf_oproj | /path/dir.pt
|
| 271 |
+
ABLIT_LAYERS="${ABLIT_LAYERS:-15-45}" # inclusive; 45 = checkpoint MTP block
|
| 272 |
+
ABLIT_ALPHA="${ABLIT_ALPHA:-3.0}" # 1.0 = plain projection, >1 over-projects
|
| 273 |
+
ABLIT_INCLUDE_MTP="${ABLIT_INCLUDE_MTP:-1}"
|
| 274 |
+
|
| 275 |
+
READY_TIMEOUT="${READY_TIMEOUT:-3600}"
|
| 276 |
+
# 1 = suppress client stop strings until </think> (DSpark #42 class).
|
| 277 |
+
GLM53_SUPPRESS_STOPS_IN_REASONING="${GLM53_SUPPRESS_STOPS_IN_REASONING:-1}"
|
| 278 |
+
# Mixed-step prefill policy when a peer is already decoding (issue #6).
|
| 279 |
+
# skip = do not mix; N>0 = cap tokens; 0 = off.
|
| 280 |
+
GLM53_MIXED_PREFILL_CHUNK="${GLM53_MIXED_PREFILL_CHUNK:-skip}"
|
| 281 |
+
# Sparse-indexer prefill gather workspace (overlay/patch_indexer_workspace.py).
|
| 282 |
+
# stock = max_model_len * 40 entries (5036.40 MB locked at 1M, measured);
|
| 283 |
+
# rightsize = the legal per-step maximum, ~+26% KV. Default applies only
|
| 284 |
+
# when UNSET: an explicitly empty value is an operator error and
|
| 285 |
+
# validate_numeric_config rejects it rather than guessing a serving mode.
|
| 286 |
+
GLM53_INDEXER_WORKSPACE="${GLM53_INDEXER_WORKSPACE-stock}"
|
| 287 |
+
# SpinCondition reader busy-loop window. "stock" preserves vLLM's 1 s default;
|
| 288 |
+
# 1..1000 selects milliseconds. The frozen TP=2 sweep selected 16 ms.
|
| 289 |
+
GLM53_SPINWAIT_MS="${GLM53_SPINWAIT_MS-stock}"
|
| 290 |
+
# EngineCore stock timeout is 300s; mid-serve Triton/TileLang JIT on TP=2 can
|
| 291 |
+
# exceed that without being a true hang. NCCL watchdog is still 600s.
|
| 292 |
+
VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS="${VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS:-1800}"
|
| 293 |
+
# 1 = after /health, burn DFlash2 BLOCK / sampler / kpool shapes. Nonfatal.
|
| 294 |
+
GLM53_BOOT_SHAPE_WARMUP="${GLM53_BOOT_SHAPE_WARMUP:-1}"
|
| 295 |
+
GLM53_WARMUP_REQ_TIMEOUT="${GLM53_WARMUP_REQ_TIMEOUT:-240}"
|
| 296 |
+
|
| 297 |
+
# OpenAI-compatible API bearer token. Read the native VLLM_API_KEY env var
|
| 298 |
+
# (vLLM falls back to it when --api-key is absent on the CLI), so the key
|
| 299 |
+
# never lands in argv / `non-default args` startup log. Empty = no auth.
|
| 300 |
+
# Same single-key semantics as the DeepSeek V4 Flash DSpark deployment.
|
| 301 |
+
VLLM_API_KEY="${VLLM_API_KEY:-}"
|
| 302 |
+
|
| 303 |
+
CONTAINER_HEAD="${CONTAINER_HEAD:-glm53-exl3-tp4-head}"
|
| 304 |
+
CONTAINER_WORKER="${CONTAINER_WORKER:-glm53-exl3-tp4-w1}"
|
| 305 |
+
CONTAINER_WORKER2="${CONTAINER_WORKER2:-glm53-exl3-tp4-w2}"
|
| 306 |
+
CONTAINER_WORKER3="${CONTAINER_WORKER3:-glm53-exl3-tp4-w3}"
|
| 307 |
+
|
| 308 |
+
HF_CACHE_DIR="${HF_HOME:-$HOME/.cache/huggingface}"
|
| 309 |
+
MODEL_PATH="$HF_CACHE_DIR/hub/$MODEL_CACHE_NAME"
|
| 310 |
+
FALLBACK_MODEL_PATH="$HF_CACHE_DIR/hub/$MODEL_FALLBACK_CACHE_NAME"
|
| 311 |
+
DFLASH_PATH="$HF_CACHE_DIR/hub/$DFLASH_CACHE_NAME"
|
| 312 |
+
WORKER_CACHE_DIR="$WORKER_HOME/.cache/huggingface"
|
| 313 |
+
WORKER2_CACHE_DIR="${WORKER2_CACHE_DIR:-$WORKER2_HOME/.cache/huggingface}"
|
| 314 |
+
WORKER3_CACHE_DIR="${WORKER3_CACHE_DIR:-$WORKER3_HOME/.cache/huggingface}"
|
| 315 |
+
CACHE_ROOT="${CACHE_ROOT:-$HOME/.cache/vllm-glm53-flash}"
|
| 316 |
+
WORKER_VLLM_CACHE="${WORKER_VLLM_CACHE:-$WORKER_HOME/.cache/vllm-glm53-flash}"
|
| 317 |
+
WORKER2_VLLM_CACHE="${WORKER2_VLLM_CACHE:-$WORKER2_HOME/.cache/vllm-glm53-flash}"
|
| 318 |
+
WORKER3_VLLM_CACHE="${WORKER3_VLLM_CACHE:-$WORKER3_HOME/.cache/vllm-glm53-flash}"
|
| 319 |
+
# Overlay FS ~/.triton and ~/.tilelang die on container recreate (TP=2 JIT
|
| 320 |
+
# stall → 600s NCCL watchdog). Persist next to the vLLM cache.
|
| 321 |
+
TRITON_HOST_CACHE="${TRITON_HOST_CACHE:-$CACHE_ROOT/triton}"
|
| 322 |
+
TILELANG_HOST_CACHE="${TILELANG_HOST_CACHE:-$CACHE_ROOT/tilelang}"
|
| 323 |
+
WORKER_TRITON_CACHE="${WORKER_TRITON_CACHE:-$WORKER_VLLM_CACHE/triton}"
|
| 324 |
+
WORKER_TILELANG_CACHE="${WORKER_TILELANG_CACHE:-$WORKER_VLLM_CACHE/tilelang}"
|
| 325 |
+
WORKER2_TRITON_CACHE="${WORKER2_TRITON_CACHE:-$WORKER2_VLLM_CACHE/triton}"
|
| 326 |
+
WORKER2_TILELANG_CACHE="${WORKER2_TILELANG_CACHE:-$WORKER2_VLLM_CACHE/tilelang}"
|
| 327 |
+
WORKER3_TRITON_CACHE="${WORKER3_TRITON_CACHE:-$WORKER3_VLLM_CACHE/triton}"
|
| 328 |
+
WORKER3_TILELANG_CACHE="${WORKER3_TILELANG_CACHE:-$WORKER3_VLLM_CACHE/tilelang}"
|
| 329 |
+
TRITON_CACHE_DIR="${TRITON_CACHE_DIR:-/root/.triton/cache}"
|
| 330 |
+
TILELANG_CACHE_DIR="${TILELANG_CACHE_DIR:-/root/.tilelang/cache}"
|
| 331 |
+
|
| 332 |
+
LOGDIR="$SCRIPT_DIR/logs"
|
| 333 |
+
HEAD_SCRIPT="$SCRIPT_DIR/.glm53-exl3-tp4-head.inner.sh"
|
| 334 |
+
WORKER_SCRIPT="$SCRIPT_DIR/.glm53-exl3-tp4-worker.inner.sh"
|
| 335 |
+
EXPECTED_SHARDS="${EXPECTED_SHARDS:-120}"
|
| 336 |
+
|
| 337 |
+
# ------------------------------- helpers -----------------------------------
|
| 338 |
+
log() { printf '\033[1;36m[glm53-exl3-tp4]\033[0m %s\n' "$*"; }
|
| 339 |
+
warn() { printf '\033[1;33m[glm53-exl3-tp4]\033[0m %s\n' "$*" >&2; }
|
| 340 |
+
die() { printf '\033[1;31m[glm53-exl3-tp4]\033[0m ERROR: %s\n' "$*" >&2; exit 1; }
|
| 341 |
+
|
| 342 |
+
# GLM53 numeric config guard (begin)
|
| 343 |
+
_glm53_canonical_positive_int() {
|
| 344 |
+
local name="$1" value="$2" maximum="$3" canonical
|
| 345 |
+
if ! [[ "$value" =~ ^[0-9]+$ ]]; then
|
| 346 |
+
echo "$name must be a positive base-10 integer (got: $value)" >&2
|
| 347 |
+
return 2
|
| 348 |
+
fi
|
| 349 |
+
canonical="$value"
|
| 350 |
+
while [ "${canonical#0}" != "$canonical" ]; do canonical="${canonical#0}"; done
|
| 351 |
+
[ -n "$canonical" ] || canonical=0
|
| 352 |
+
if [ "$canonical" = 0 ] \
|
| 353 |
+
|| [ "${#canonical}" -gt "${#maximum}" ] \
|
| 354 |
+
|| [ "$canonical" -gt "$maximum" ]; then
|
| 355 |
+
echo "$name must be between 1 and $maximum (got: $value)" >&2
|
| 356 |
+
return 2
|
| 357 |
+
fi
|
| 358 |
+
printf -v "$name" '%s' "$canonical"
|
| 359 |
+
# $name is a validated integer configuration variable.
|
| 360 |
+
# shellcheck disable=SC2163
|
| 361 |
+
export "$name"
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
# Enum knobs are exactly one of a fixed set. Not "non-empty means on": a
|
| 365 |
+
# typo'd knob must not silently pick a serving mode. GLM53_INDEXER_WORKSPACE
|
| 366 |
+
# sizes the sparse-indexer prefill workspace, and the patched
|
| 367 |
+
# get_max_prefill_buffer_size itself raises on anything but stock/rightsize
|
| 368 |
+
# (overlay/patch_indexer_workspace.py, _glm53_workspace_mode), so catching it
|
| 369 |
+
# here turns a container boot failure into a launcher error. The match is
|
| 370 |
+
# literal on both sides -- the "-stock" default applies only to an UNSET var,
|
| 371 |
+
# so "", " rightsize " and "RIGHTSIZE" all fail here and would fail there.
|
| 372 |
+
_glm53_validate_enum() {
|
| 373 |
+
local name="$1" value="$2" allowed
|
| 374 |
+
shift 2
|
| 375 |
+
for allowed in "$@"; do
|
| 376 |
+
[ "$value" = "$allowed" ] && return 0
|
| 377 |
+
done
|
| 378 |
+
echo "$name must be one of: $* (got: $value)" >&2
|
| 379 |
+
return 2
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
_glm53_validate_spinwait_ms() {
|
| 383 |
+
if [ "$GLM53_SPINWAIT_MS" = "stock" ]; then
|
| 384 |
+
export GLM53_SPINWAIT_MS
|
| 385 |
+
return 0
|
| 386 |
+
fi
|
| 387 |
+
_glm53_canonical_positive_int \
|
| 388 |
+
GLM53_SPINWAIT_MS "$GLM53_SPINWAIT_MS" 1000
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
validate_numeric_config() {
|
| 392 |
+
if ! [[ "$GPU_MEM_UTIL" =~ ^(0([.][0-9]+)?|[.][0-9]+|1([.]0+)?)$ ]] \
|
| 393 |
+
|| ! awk -v u="$GPU_MEM_UTIL" 'BEGIN { exit !(u > 0 && u <= 1) }'; then
|
| 394 |
+
echo "GPU_MEM_UTIL must be greater than 0 and at most 1 (got: $GPU_MEM_UTIL)" >&2
|
| 395 |
+
return 2
|
| 396 |
+
fi
|
| 397 |
+
_glm53_canonical_positive_int MAX_MODEL_LEN "$MAX_MODEL_LEN" 1000000 || return
|
| 398 |
+
_glm53_canonical_positive_int MAX_NUM_SEQS "$MAX_NUM_SEQS" 4096 || return
|
| 399 |
+
_glm53_canonical_positive_int MAX_NUM_BATCHED_TOKENS "$MAX_NUM_BATCHED_TOKENS" 8388608 || return
|
| 400 |
+
_glm53_validate_enum GLM53_INDEXER_WORKSPACE "${GLM53_INDEXER_WORKSPACE-stock}" \
|
| 401 |
+
stock rightsize || return
|
| 402 |
+
_glm53_validate_spinwait_ms || return
|
| 403 |
+
}
|
| 404 |
+
# GLM53 numeric config guard (end)
|
| 405 |
+
|
| 406 |
+
banner() {
|
| 407 |
+
local label="${1:-start-tp4.sh}"
|
| 408 |
+
printf '\n'
|
| 409 |
+
printf ' \033[1;36m┌────────────────────────────────────────────┐\033[0m\n'
|
| 410 |
+
printf ' \033[1;36m│\033[0m \033[1mGLM-5.3 Flash EXL3\033[0m \033[2m· %-11s\033[0m \033[1;36m│\033[0m\n' "$label"
|
| 411 |
+
printf ' \033[1;36m└────────────────────────────────────────────┘\033[0m\n'
|
| 412 |
+
printf '\n'
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
# rank 1..3
|
| 416 |
+
_tp4_ssh_target() {
|
| 417 |
+
case "$1" in
|
| 418 |
+
1) printf '%s' "$WORKER_SSH" ;;
|
| 419 |
+
2) printf '%s' "$WORKER2_SSH" ;;
|
| 420 |
+
3) printf '%s' "$WORKER3_SSH" ;;
|
| 421 |
+
*) die "internal: bad worker rank $1" ;;
|
| 422 |
+
esac
|
| 423 |
+
}
|
| 424 |
+
_tp4_rank_ip() {
|
| 425 |
+
case "$1" in
|
| 426 |
+
1) printf '%s' "$WORKER_IP" ;;
|
| 427 |
+
2) printf '%s' "$WORKER2_IP" ;;
|
| 428 |
+
3) printf '%s' "$WORKER3_IP" ;;
|
| 429 |
+
esac
|
| 430 |
+
}
|
| 431 |
+
_tp4_rank_home() {
|
| 432 |
+
case "$1" in
|
| 433 |
+
1) printf '%s' "$WORKER_HOME" ;;
|
| 434 |
+
2) printf '%s' "$WORKER2_HOME" ;;
|
| 435 |
+
3) printf '%s' "$WORKER3_HOME" ;;
|
| 436 |
+
esac
|
| 437 |
+
}
|
| 438 |
+
_tp4_rank_hf() {
|
| 439 |
+
case "$1" in
|
| 440 |
+
1) printf '%s' "$WORKER_CACHE_DIR" ;;
|
| 441 |
+
2) printf '%s' "$WORKER2_CACHE_DIR" ;;
|
| 442 |
+
3) printf '%s' "$WORKER3_CACHE_DIR" ;;
|
| 443 |
+
esac
|
| 444 |
+
}
|
| 445 |
+
_tp4_rank_vllm() {
|
| 446 |
+
case "$1" in
|
| 447 |
+
1) printf '%s' "$WORKER_VLLM_CACHE" ;;
|
| 448 |
+
2) printf '%s' "$WORKER2_VLLM_CACHE" ;;
|
| 449 |
+
3) printf '%s' "$WORKER3_VLLM_CACHE" ;;
|
| 450 |
+
esac
|
| 451 |
+
}
|
| 452 |
+
_tp4_rank_triton() {
|
| 453 |
+
case "$1" in
|
| 454 |
+
1) printf '%s' "$WORKER_TRITON_CACHE" ;;
|
| 455 |
+
2) printf '%s' "$WORKER2_TRITON_CACHE" ;;
|
| 456 |
+
3) printf '%s' "$WORKER3_TRITON_CACHE" ;;
|
| 457 |
+
esac
|
| 458 |
+
}
|
| 459 |
+
_tp4_rank_tilelang() {
|
| 460 |
+
case "$1" in
|
| 461 |
+
1) printf '%s' "$WORKER_TILELANG_CACHE" ;;
|
| 462 |
+
2) printf '%s' "$WORKER2_TILELANG_CACHE" ;;
|
| 463 |
+
3) printf '%s' "$WORKER3_TILELANG_CACHE" ;;
|
| 464 |
+
esac
|
| 465 |
+
}
|
| 466 |
+
_tp4_rank_cx7_if() {
|
| 467 |
+
case "$1" in
|
| 468 |
+
1) printf '%s' "$WORKER_CX7_IF" ;;
|
| 469 |
+
2) printf '%s' "$WORKER2_CX7_IF" ;;
|
| 470 |
+
3) printf '%s' "$WORKER3_CX7_IF" ;;
|
| 471 |
+
esac
|
| 472 |
+
}
|
| 473 |
+
_tp4_rank_cx7_ib() {
|
| 474 |
+
case "$1" in
|
| 475 |
+
1) printf '%s' "$WORKER_CX7_IB" ;;
|
| 476 |
+
2) printf '%s' "$WORKER2_CX7_IB" ;;
|
| 477 |
+
3) printf '%s' "$WORKER3_CX7_IB" ;;
|
| 478 |
+
esac
|
| 479 |
+
}
|
| 480 |
+
_tp4_rank_gid() {
|
| 481 |
+
case "$1" in
|
| 482 |
+
1) printf '%s' "$WORKER_GID" ;;
|
| 483 |
+
2) printf '%s' "$WORKER2_GID" ;;
|
| 484 |
+
3) printf '%s' "$WORKER3_GID" ;;
|
| 485 |
+
esac
|
| 486 |
+
}
|
| 487 |
+
_tp4_rank_container() {
|
| 488 |
+
case "$1" in
|
| 489 |
+
1) printf '%s' "$CONTAINER_WORKER" ;;
|
| 490 |
+
2) printf '%s' "$CONTAINER_WORKER2" ;;
|
| 491 |
+
3) printf '%s' "$CONTAINER_WORKER3" ;;
|
| 492 |
+
esac
|
| 493 |
+
}
|
| 494 |
+
_tp4_rank_nccl_dir() {
|
| 495 |
+
case "$1" in
|
| 496 |
+
1) printf '%s' "$WORKER_NCCL_HOST_DIR" ;;
|
| 497 |
+
2) printf '%s' "$WORKER2_NCCL_HOST_DIR" ;;
|
| 498 |
+
3) printf '%s' "$WORKER3_NCCL_HOST_DIR" ;;
|
| 499 |
+
esac
|
| 500 |
+
}
|
| 501 |
+
_tp4_first_ib() { printf '%s' "${1%%,*}"; }
|
| 502 |
+
|
| 503 |
+
worker_ssh() { ssh -T -o BatchMode=yes -o ConnectTimeout=15 "$WORKER_SSH" "$@"; }
|
| 504 |
+
worker_ssh_n() {
|
| 505 |
+
local r="$1"; shift
|
| 506 |
+
ssh -T -o BatchMode=yes -o ConnectTimeout=15 "$(_tp4_ssh_target "$r")" "$@"
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
usage() { sed -n '2,40p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
|
| 510 |
+
|
| 511 |
+
count_shards() {
|
| 512 |
+
find "$1/snapshots" -name '*.safetensors' 2>/dev/null | wc -l | tr -d '[:space:]' || true
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
ensure_refs_main() {
|
| 516 |
+
local ref="$MODEL_PATH/refs/main" snap
|
| 517 |
+
[ -f "$ref" ] && [ -n "$(<"$ref")" ] && return 0
|
| 518 |
+
snap="$(ls -1t "$MODEL_PATH/snapshots" 2>/dev/null | head -n 1 || true)"
|
| 519 |
+
[ -n "$snap" ] || die "no snapshots under $MODEL_PATH — re-run download"
|
| 520 |
+
mkdir -p "$MODEL_PATH/refs"
|
| 521 |
+
printf '%s' "$snap" >"$ref"
|
| 522 |
+
log "wrote refs/main -> $snap (hf download left it empty)"
|
| 523 |
+
}
|
| 524 |
+
|
| 525 |
+
resolve_model_dir() {
|
| 526 |
+
local ref="$MODEL_PATH/refs/main" hash dir
|
| 527 |
+
ensure_refs_main
|
| 528 |
+
hash="$(<"$ref")"
|
| 529 |
+
dir="$MODEL_PATH/snapshots/$hash"
|
| 530 |
+
[ -f "$dir/config.json" ] || die "config.json missing in $dir — re-run with REFRESH_WEIGHTS=1"
|
| 531 |
+
printf '/root/.cache/huggingface/hub/%s/snapshots/%s' "$MODEL_CACHE_NAME" "$hash"
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
ensure_dflash_refs_main() {
|
| 535 |
+
local ref="$DFLASH_PATH/refs/main" snap
|
| 536 |
+
[ -f "$ref" ] && [ -n "$(<"$ref")" ] && return 0
|
| 537 |
+
snap="$(ls -1t "$DFLASH_PATH/snapshots" 2>/dev/null | head -n 1 || true)"
|
| 538 |
+
[ -n "$snap" ] || die "no snapshots under $DFLASH_PATH — re-run download"
|
| 539 |
+
mkdir -p "$DFLASH_PATH/refs"
|
| 540 |
+
printf '%s' "$snap" >"$ref"
|
| 541 |
+
log "wrote DFlash2 refs/main -> $snap"
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
resolve_dflash_dir() {
|
| 545 |
+
local ref="$DFLASH_PATH/refs/main" hash dir
|
| 546 |
+
ensure_dflash_refs_main
|
| 547 |
+
hash="$(<"$ref")"
|
| 548 |
+
dir="$DFLASH_PATH/snapshots/$hash"
|
| 549 |
+
[ -f "$dir/config.json" ] || die "DFlash2 config.json missing in $dir"
|
| 550 |
+
printf '/root/.cache/huggingface/hub/%s/snapshots/%s' "$DFLASH_CACHE_NAME" "$hash"
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
check_port_free() {
|
| 554 |
+
local port="$1" envname="$2"
|
| 555 |
+
command -v ss >/dev/null 2>&1 || return 0
|
| 556 |
+
if ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]${port}\$"; then
|
| 557 |
+
if docker inspect -f '{{.State.Running}}' "$CONTAINER_HEAD" 2>/dev/null | grep -q true; then
|
| 558 |
+
die "port ${port} is held by ${CONTAINER_HEAD} — use './start-tp4.sh restart' or './start-tp4.sh stop' first"
|
| 559 |
+
fi
|
| 560 |
+
die "port ${port} is already in use — stop it or rerun with ${envname}=<free-port>"
|
| 561 |
+
fi
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
trap 'warn "interrupted — containers keep running ('"'"'./start-tp4.sh logs'"'"' to watch, '"'"'./start-tp4.sh stop'"'"' to stop)"; exit 130' INT
|
| 565 |
+
|
| 566 |
+
# ------------------------------ preflight ----------------------------------
|
| 567 |
+
preflight() {
|
| 568 |
+
command -v docker >/dev/null 2>&1 || die "docker not found on head"
|
| 569 |
+
command -v curl >/dev/null 2>&1 || die "curl not found on head"
|
| 570 |
+
command -v rsync >/dev/null 2>&1 || die "rsync not found on head"
|
| 571 |
+
docker info >/dev/null 2>&1 || die "cannot talk to docker daemon on head"
|
| 572 |
+
|
| 573 |
+
ip -4 addr show 2>/dev/null | grep -q "inet ${HEAD_IP}/" \
|
| 574 |
+
|| die "HEAD_IP=${HEAD_IP} is not assigned on this host — set it in .env"
|
| 575 |
+
|
| 576 |
+
[ "$TP" = "4" ] || warn "TP=${TP} — this script is meant for TP=4"
|
| 577 |
+
[ "$NNODES" = "4" ] || warn "NNODES=${NNODES} — this script is meant for nnodes=4"
|
| 578 |
+
local r ssh_t
|
| 579 |
+
for r in 1 2 3; do
|
| 580 |
+
ssh_t="$(_tp4_ssh_target "$r")"
|
| 581 |
+
log "checking worker rank ${r} ${ssh_t} ..."
|
| 582 |
+
worker_ssh_n "$r" true 2>/dev/null \
|
| 583 |
+
|| die "cannot ssh (key-based) to ${ssh_t} — set up passwordless ssh first"
|
| 584 |
+
worker_ssh_n "$r" "docker info >/dev/null 2>&1" \
|
| 585 |
+
|| die "rank ${r} (${ssh_t}) cannot talk to its docker daemon"
|
| 586 |
+
worker_ssh_n "$r" "nvidia-smi -L 2>/dev/null | grep -q GB10" \
|
| 587 |
+
|| warn "no GB10 GPU visible on rank ${r} (${ssh_t})"
|
| 588 |
+
done
|
| 589 |
+
|
| 590 |
+
# Each rank's GID index must name a populated entry on ITS OWN CX7 device.
|
| 591 |
+
# An empty (all-zero) entry passes every earlier check and then kills that
|
| 592 |
+
# rank ~60 s in with ibv_modify_qp errno 61 "No data available". The index is
|
| 593 |
+
# per-NIC, so validate head and worker separately: some pairs share one good
|
| 594 |
+
# index, others need different ones (HEAD_GID / WORKER_GID).
|
| 595 |
+
local gid_head gid_worker gid_path ib r ssh_t gid_ok=1
|
| 596 |
+
ib="$(_tp4_first_ib "$HEAD_CX7_IB")"
|
| 597 |
+
gid_path="/sys/class/infiniband/${ib}/ports/1/gids/${HEAD_GID}"
|
| 598 |
+
gid_head=$(cat "$gid_path" 2>/dev/null | tr -d ':0' || true)
|
| 599 |
+
[ -n "$gid_head" ] || { warn "head GID index ${HEAD_GID} is EMPTY on ${ib}"; gid_ok=0; }
|
| 600 |
+
for r in 1 2 3; do
|
| 601 |
+
ib="$(_tp4_first_ib "$(_tp4_rank_cx7_ib "$r")")"
|
| 602 |
+
gid_path="/sys/class/infiniband/${ib}/ports/1/gids/$(_tp4_rank_gid "$r")"
|
| 603 |
+
gid_worker=$(worker_ssh_n "$r" "cat '$gid_path' 2>/dev/null" | tr -d ':0' || true)
|
| 604 |
+
if [ -z "$gid_worker" ]; then
|
| 605 |
+
warn "rank ${r} GID index $(_tp4_rank_gid "$r") is EMPTY on ${ib}"
|
| 606 |
+
gid_ok=0
|
| 607 |
+
fi
|
| 608 |
+
done
|
| 609 |
+
if [ "$gid_ok" != "1" ]; then
|
| 610 |
+
if [ -z "$gid_head" ]; then
|
| 611 |
+
warn "head GID index ${HEAD_GID} is EMPTY on $(_tp4_first_ib "$HEAD_CX7_IB")"
|
| 612 |
+
fi
|
| 613 |
+
warn "GID tables — pick each node's ::ffff:<ip> entry whose type is RoCE v2;"
|
| 614 |
+
warn "the two indices need not match, and a v1 entry at the same index will not work:"
|
| 615 |
+
for i in 0 1 2 3 4 5 6 7; do
|
| 616 |
+
printf ' head gid%s: %-40s %s\n' "$i" \
|
| 617 |
+
"$(cat "/sys/class/infiniband/${HEAD_CX7_IB}/ports/1/gids/$i" 2>/dev/null)" \
|
| 618 |
+
"$(cat "/sys/class/infiniband/${HEAD_CX7_IB}/ports/1/gid_attrs/types/$i" 2>/dev/null)" >&2
|
| 619 |
+
done
|
| 620 |
+
worker_ssh "for i in 0 1 2 3 4 5 6 7; do printf ' worker gid%s: %-40s %s\n' \"\$i\" \"\$(cat /sys/class/infiniband/${WORKER_CX7_IB}/ports/1/gids/\$i 2>/dev/null)\" \"\$(cat /sys/class/infiniband/${WORKER_CX7_IB}/ports/1/gid_attrs/types/\$i 2>/dev/null)\"; done" >&2 || true
|
| 621 |
+
die "set NCCL_IB_GID_INDEX (same index both ranks) or HEAD_GID/WORKER_GID (per rank) in .env to populated indices"
|
| 622 |
+
fi
|
| 623 |
+
|
| 624 |
+
[ "$TP" = "4" ] || warn "TP=${TP} — expected TP=4 for start-tp4.sh"
|
| 625 |
+
[ "$NNODES" = "4" ] || warn "NNODES=${NNODES} — expected 4"
|
| 626 |
+
|
| 627 |
+
local others cname
|
| 628 |
+
for r in 1 2 3; do
|
| 629 |
+
cname="$(_tp4_rank_container "$r")"
|
| 630 |
+
others=$(worker_ssh_n "$r" "docker ps --format ' {{.Names}} ({{.Image}})'" 2>/dev/null | grep -v "^ ${cname}" || true)
|
| 631 |
+
if [ -n "$others" ]; then
|
| 632 |
+
warn "other containers are running on rank ${r} ($(_tp4_ssh_target "$r")):"
|
| 633 |
+
echo "$others" >&2
|
| 634 |
+
warn "this model needs most of each GB10 — stop GPU containers on that Spark first"
|
| 635 |
+
fi
|
| 636 |
+
done
|
| 637 |
+
|
| 638 |
+
check_port_free "$PORT" PORT
|
| 639 |
+
check_port_free "$MASTER_PORT" MASTER_PORT
|
| 640 |
+
|
| 641 |
+
[ -f "$STOP_PATCH_HOST" ] || die "$STOP_PATCH_HOST missing"
|
| 642 |
+
[ -f "$SCHED_PATCH_HOST" ] || die "$SCHED_PATCH_HOST missing"
|
| 643 |
+
[ -f "$DRAFTER_PATCH_HOST" ] || die "$DRAFTER_PATCH_HOST missing"
|
| 644 |
+
[ -f "$APC_PATCH_HOST" ] || die "$APC_PATCH_HOST missing"
|
| 645 |
+
[ -f "$XGRAMMAR_PATCH_HOST" ] || die "$XGRAMMAR_PATCH_HOST missing"
|
| 646 |
+
[ -f "$KPOOL_TAIL_PATCH_HOST" ] || die "$KPOOL_TAIL_PATCH_HOST missing"
|
| 647 |
+
[ -f "$SPINWAIT_PATCH_HOST" ] || die "$SPINWAIT_PATCH_HOST missing"
|
| 648 |
+
[ -f "$SCRIPT_DIR/overlay/patch_ablit.py" ] || die "$SCRIPT_DIR/overlay/patch_ablit.py missing"
|
| 649 |
+
[ -f "$SCRIPT_DIR/overlay/ablit_runtime.py" ] || die "$SCRIPT_DIR/overlay/ablit_runtime.py missing"
|
| 650 |
+
[ -f "$SCRIPT_DIR/ablit/LAYER_MAP.json" ] || die "$SCRIPT_DIR/ablit/LAYER_MAP.json missing"
|
| 651 |
+
if [ "$ABLIT" = "1" ]; then
|
| 652 |
+
log "ablit: ON (method=${ABLIT_METHOD} direction=${ABLIT_DIRECTION} layers=${ABLIT_LAYERS} alpha=${ABLIT_ALPHA} mtp=${ABLIT_INCLUDE_MTP})"
|
| 653 |
+
fi
|
| 654 |
+
|
| 655 |
+
local need_kb=$((180 * 1024 * 1024)) avail
|
| 656 |
+
mkdir -p "$HF_CACHE_DIR"
|
| 657 |
+
avail=$(df -Pk "$HF_CACHE_DIR" 2>/dev/null | awk 'NR==2{print $4}' || true)
|
| 658 |
+
[ "${avail:-0}" -ge "$need_kb" ] || warn "only $((avail/1024/1024)) GiB free on head for a ~164 GiB model"
|
| 659 |
+
for r in 1 2 3; do
|
| 660 |
+
avail=$(worker_ssh_n "$r" "df -Pk '$(_tp4_rank_home "$r")' 2>/dev/null" | awk 'NR==2{print $4}' || true)
|
| 661 |
+
[ "${avail:-0}" -ge "$need_kb" ] || warn "only $((avail/1024/1024)) GiB free on rank ${r} for a ~164 GiB model"
|
| 662 |
+
done
|
| 663 |
+
|
| 664 |
+
# The worker HF cache must be writable by the SSH user before the ~164 GiB
|
| 665 |
+
# sync starts. A root-owned ~/.cache/huggingface (prior sudo/docker
|
| 666 |
+
# prepare on the worker) otherwise fails mid-sync with a bare mkdir
|
| 667 |
+
# permission error. mkdir -p is idempotent and is what sync does anyway.
|
| 668 |
+
for r in 1 2 3; do
|
| 669 |
+
if ! worker_ssh_n "$r" "mkdir -p '$(_tp4_rank_hf "$r")/hub' && test -w '$(_tp4_rank_hf "$r")/hub'"; then
|
| 670 |
+
die "rank ${r} cannot write $(_tp4_rank_hf "$r")/hub — fix ownership, e.g. ssh $(_tp4_ssh_target "$r") \"sudo chown -R \$USER: '$(_tp4_rank_hf "$r")'\""
|
| 671 |
+
fi
|
| 672 |
+
done
|
| 673 |
+
|
| 674 |
+
log "preflight OK (head=$(hostname) ${HEAD_IP}, workers=${WORKER_SSH} ${WORKER2_SSH} ${WORKER3_SSH})"
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
+
# ------------------------------ image --------------------------------------
|
| 678 |
+
image_from_registry() {
|
| 679 |
+
case "$IMAGE" in
|
| 680 |
+
*/*) return 0 ;;
|
| 681 |
+
*) return 1 ;;
|
| 682 |
+
esac
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
login_ghcr_if_token() {
|
| 686 |
+
[ -n "${GHCR_TOKEN:-}" ] || return 0
|
| 687 |
+
log "docker login ghcr.io as ${GHCR_USER} (GHCR_TOKEN)"
|
| 688 |
+
echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USER" --password-stdin >/dev/null
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
login_ghcr_if_token_worker() {
|
| 692 |
+
[ -n "${GHCR_TOKEN:-}" ] || return 0
|
| 693 |
+
local r
|
| 694 |
+
for r in 1 2 3; do
|
| 695 |
+
log "docker login ghcr.io on rank ${r} as ${GHCR_USER} (GHCR_TOKEN)"
|
| 696 |
+
echo "$GHCR_TOKEN" | worker_ssh_n "$r" "docker login ghcr.io -u '$GHCR_USER' --password-stdin" >/dev/null
|
| 697 |
+
done
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
# RepoDigest is stable across overlay2 vs containerd. Those snapshotters
|
| 701 |
+
# disagree on .Id (config digest vs index digest), so start.sh used to
|
| 702 |
+
# ship even after the worker had already pulled the same GHCR tag (issue #8).
|
| 703 |
+
# Local builds have no RepoDigest — use RootFS layer diffs, then .Id.
|
| 704 |
+
_IMAGE_KEY_FMT='{{if .RepoDigests}}{{index .RepoDigests 0}}{{else if .RootFS.Layers}}{{join .RootFS.Layers ","}}{{else}}{{.Id}}{{end}}'
|
| 705 |
+
|
| 706 |
+
parse_image_key() {
|
| 707 |
+
tr -d '\r' | sed -n 's/^GLM53KEY //p' | tail -n 1
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
local_image_key() {
|
| 711 |
+
docker image inspect -f "GLM53KEY ${_IMAGE_KEY_FMT}" "$IMAGE" 2>/dev/null | parse_image_key
|
| 712 |
+
}
|
| 713 |
+
|
| 714 |
+
worker_image_key() {
|
| 715 |
+
worker_ssh_n "${1:-1}" "docker image inspect -f 'GLM53KEY ${_IMAGE_KEY_FMT}' '$IMAGE' 2>/dev/null" | parse_image_key
|
| 716 |
+
}
|
| 717 |
+
|
| 718 |
+
images_match() {
|
| 719 |
+
[ -n "${1:-}" ] && [ -n "${2:-}" ] && [ "$1" = "$2" ]
|
| 720 |
+
}
|
| 721 |
+
|
| 722 |
+
image_platform() {
|
| 723 |
+
if [ -n "${IMAGE_PLATFORM:-}" ]; then
|
| 724 |
+
printf '%s' "$IMAGE_PLATFORM"
|
| 725 |
+
return
|
| 726 |
+
fi
|
| 727 |
+
local p
|
| 728 |
+
p="$(docker image inspect -f '{{.Os}}/{{.Architecture}}' "$IMAGE" 2>/dev/null || true)"
|
| 729 |
+
printf '%s' "${p:-linux/arm64}"
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
# Hash of Dockerfile + overlay/tests/files/ablit inputs that docker COPY.
|
| 733 |
+
# Compared to LABEL glm53.recipe.stamp so a git pull rebuilds once.
|
| 734 |
+
overlay_recipe_hash() {
|
| 735 |
+
{
|
| 736 |
+
printf '%s\n' "$SCRIPT_DIR/Dockerfile"
|
| 737 |
+
find "$SCRIPT_DIR/overlay" "$SCRIPT_DIR/files" "$SCRIPT_DIR/tests" \
|
| 738 |
+
"$SCRIPT_DIR/ablit" \
|
| 739 |
+
-type f \
|
| 740 |
+
! -path '*/__pycache__/*' \
|
| 741 |
+
! -path '*/ablit/transplant/*' \
|
| 742 |
+
! -name '*.pyc' \
|
| 743 |
+
2>/dev/null
|
| 744 |
+
} | LC_ALL=C sort | xargs -d '\n' -r sha256sum | sha256sum | awk '{print $1}'
|
| 745 |
+
}
|
| 746 |
+
|
| 747 |
+
image_recipe_stamp() {
|
| 748 |
+
local stamp
|
| 749 |
+
stamp="$(docker image inspect -f '{{ index .Config.Labels "glm53.recipe.stamp" }}' "$IMAGE" 2>/dev/null || true)"
|
| 750 |
+
case "$stamp" in
|
| 751 |
+
""|"<no value>"|"<nil>") printf '' ;;
|
| 752 |
+
*) printf '%s' "$stamp" ;;
|
| 753 |
+
esac
|
| 754 |
+
}
|
| 755 |
+
|
| 756 |
+
build_image() {
|
| 757 |
+
local stamp
|
| 758 |
+
stamp="$(overlay_recipe_hash)"
|
| 759 |
+
log "building ${IMAGE} from Dockerfile stamp=${stamp:0:12} (log: $LOGDIR/build-sm121.log) ..."
|
| 760 |
+
docker build --build-arg "GLM53_RECIPE_STAMP=$stamp" -t "$IMAGE" "$SCRIPT_DIR" \
|
| 761 |
+
>"$LOGDIR/build-sm121.log" 2>&1 \
|
| 762 |
+
|| { tail -n 40 "$LOGDIR/build-sm121.log" >&2; die "docker build of $IMAGE failed"; }
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
+
pull_image() {
|
| 766 |
+
login_ghcr_if_token
|
| 767 |
+
log "pulling ${IMAGE} ..."
|
| 768 |
+
docker pull "$IMAGE" && return 0
|
| 769 |
+
die "docker pull ${IMAGE} failed.
|
| 770 |
+
:exl3 is a public GHCR package — check network / disk.
|
| 771 |
+
If you still get 401/403: echo YOUR_PAT | docker login ghcr.io -u YOUR_GITHUB_USER --password-stdin
|
| 772 |
+
Overlay rebuild: BUILD=1 ./start.sh. Recipe-stamp drift also rebuilds; SKIP_BUILD=1 keeps GHCR."
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
pull_image_on_worker() {
|
| 776 |
+
local r="${1:-1}"
|
| 777 |
+
login_ghcr_if_token_worker
|
| 778 |
+
log "pulling ${IMAGE} on rank ${r} ..."
|
| 779 |
+
worker_ssh_n "$r" "docker pull '$IMAGE'"
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
ship_image_to_worker() {
|
| 783 |
+
local r="${1:-1}" platform
|
| 784 |
+
platform="$(image_platform)"
|
| 785 |
+
log "shipping ${IMAGE} (${platform}) to rank ${r} via docker save | ssh docker load ..."
|
| 786 |
+
if docker save --platform "$platform" "$IMAGE" | worker_ssh_n "$r" docker load; then
|
| 787 |
+
return 0
|
| 788 |
+
fi
|
| 789 |
+
warn "docker save --platform ${platform} failed — retrying without --platform"
|
| 790 |
+
docker save "$IMAGE" | worker_ssh_n "$r" docker load
|
| 791 |
+
}
|
| 792 |
+
|
| 793 |
+
ensure_image() {
|
| 794 |
+
mkdir -p "$LOGDIR"
|
| 795 |
+
local head_ok=0 worker_ok=0 head_key="" worker_key=""
|
| 796 |
+
if docker image inspect "$IMAGE" >/dev/null 2>&1; then
|
| 797 |
+
head_ok=1
|
| 798 |
+
head_key="$(local_image_key)"
|
| 799 |
+
fi
|
| 800 |
+
worker_ok=1
|
| 801 |
+
for r in 1 2 3; do
|
| 802 |
+
if worker_ssh_n "$r" "docker image inspect '$IMAGE' >/dev/null 2>&1"; then
|
| 803 |
+
worker_key="$(worker_image_key "$r")"
|
| 804 |
+
if images_match "$head_key" "$worker_key"; then
|
| 805 |
+
:
|
| 806 |
+
else
|
| 807 |
+
worker_ok=0
|
| 808 |
+
log "rank ${r} image differs (head=${head_key:-none} worker=${worker_key:-none}) — will refresh"
|
| 809 |
+
fi
|
| 810 |
+
else
|
| 811 |
+
worker_ok=0
|
| 812 |
+
fi
|
| 813 |
+
done
|
| 814 |
+
local skip_pull="${SKIP_PULL:-0}"
|
| 815 |
+
[ "${PULL:-0}" = "1" ] && skip_pull=0
|
| 816 |
+
local wanted_stamp have_stamp
|
| 817 |
+
wanted_stamp="$(overlay_recipe_hash)"
|
| 818 |
+
have_stamp=""
|
| 819 |
+
[ "$head_ok" = "1" ] && have_stamp="$(image_recipe_stamp)"
|
| 820 |
+
if [ "${BUILD:-0}" != "1" ] && [ "${SKIP_BUILD:-0}" != "1" ]; then
|
| 821 |
+
if [ "$head_ok" = "0" ] || [ "$have_stamp" != "$wanted_stamp" ]; then
|
| 822 |
+
log "image recipe ${have_stamp:-none} != repo ${wanted_stamp:0:12} — rebuilding (SKIP_BUILD=1 keeps GHCR)"
|
| 823 |
+
BUILD=1
|
| 824 |
+
fi
|
| 825 |
+
elif [ "${SKIP_BUILD:-0}" = "1" ] && [ "$have_stamp" != "$wanted_stamp" ]; then
|
| 826 |
+
warn "SKIP_BUILD=1 — not rebuilding; stamp ${have_stamp:-none} != repo ${wanted_stamp:0:12}"
|
| 827 |
+
fi
|
| 828 |
+
if [ "${BUILD:-0}" = "1" ]; then
|
| 829 |
+
build_image
|
| 830 |
+
head_key="$(local_image_key)"
|
| 831 |
+
head_ok=1
|
| 832 |
+
worker_ok=0
|
| 833 |
+
elif image_from_registry && [ "$skip_pull" != "1" ]; then
|
| 834 |
+
local before_key="$head_key"
|
| 835 |
+
pull_image
|
| 836 |
+
head_key="$(local_image_key)"
|
| 837 |
+
head_ok=1
|
| 838 |
+
if [ "$head_key" != "$before_key" ]; then
|
| 839 |
+
log "pulled ${IMAGE} (${before_key:-missing} -> ${head_key})"
|
| 840 |
+
else
|
| 841 |
+
log "${IMAGE} already current"
|
| 842 |
+
fi
|
| 843 |
+
if images_match "$worker_key" "$head_key"; then
|
| 844 |
+
worker_ok=1
|
| 845 |
+
else
|
| 846 |
+
worker_ok=0
|
| 847 |
+
fi
|
| 848 |
+
elif [ "$head_ok" = "0" ]; then
|
| 849 |
+
if image_from_registry && [ "$skip_pull" = "1" ]; then
|
| 850 |
+
die "SKIP_PULL=1 but ${IMAGE} is not on the head"
|
| 851 |
+
fi
|
| 852 |
+
build_image
|
| 853 |
+
head_key="$(local_image_key)"
|
| 854 |
+
head_ok=1
|
| 855 |
+
worker_ok=0
|
| 856 |
+
fi
|
| 857 |
+
if [ "${SKIP_SHIP:-0}" = "1" ]; then
|
| 858 |
+
[ "$worker_ok" = "1" ] || warn "SKIP_SHIP=1 — not copying ${IMAGE} to workers"
|
| 859 |
+
elif [ "$worker_ok" = "0" ]; then
|
| 860 |
+
for r in 1 2 3; do
|
| 861 |
+
local wok=0
|
| 862 |
+
worker_key="$(worker_image_key "$r")"
|
| 863 |
+
if images_match "$head_key" "$worker_key"; then
|
| 864 |
+
continue
|
| 865 |
+
fi
|
| 866 |
+
if image_from_registry && [ "$skip_pull" != "1" ] && [ "${BUILD:-0}" != "1" ]; then
|
| 867 |
+
if pull_image_on_worker "$r"; then
|
| 868 |
+
worker_key="$(worker_image_key "$r")"
|
| 869 |
+
if images_match "$head_key" "$worker_key"; then
|
| 870 |
+
wok=1
|
| 871 |
+
log "rank ${r} pulled ${IMAGE} — matches head"
|
| 872 |
+
else
|
| 873 |
+
warn "rank ${r} pull left a different image — shipping"
|
| 874 |
+
fi
|
| 875 |
+
else
|
| 876 |
+
warn "rank ${r} docker pull failed — shipping over SSH"
|
| 877 |
+
fi
|
| 878 |
+
fi
|
| 879 |
+
if [ "$wok" = "0" ]; then
|
| 880 |
+
ship_image_to_worker "$r"
|
| 881 |
+
worker_key="$(worker_image_key "$r")"
|
| 882 |
+
if images_match "$head_key" "$worker_key"; then
|
| 883 |
+
:
|
| 884 |
+
elif worker_ssh_n "$r" "docker image inspect '$IMAGE' >/dev/null 2>&1"; then
|
| 885 |
+
warn "rank ${r} has ${IMAGE} after ship but keys still differ — continuing"
|
| 886 |
+
else
|
| 887 |
+
die "rank ${r} still missing ${IMAGE} after ship"
|
| 888 |
+
fi
|
| 889 |
+
fi
|
| 890 |
+
done
|
| 891 |
+
fi
|
| 892 |
+
if [ "${SKIP_OVERLAY_VERIFY:-0}" != "1" ]; then
|
| 893 |
+
log "GPU EXL3 self-check on ${IMAGE} (log: $LOGDIR/overlay-verify.log) ..."
|
| 894 |
+
docker run --rm --gpus all \
|
| 895 |
+
-e EXL3_SELFCHECK_GPU=1 \
|
| 896 |
+
--entrypoint python3 "$IMAGE" /opt/glm53/test_exl3_overlay.py \
|
| 897 |
+
>"$LOGDIR/overlay-verify.log" 2>&1 \
|
| 898 |
+
|| { tail -n 80 "$LOGDIR/overlay-verify.log" >&2; die "EXL3 overlay GPU self-check failed"; }
|
| 899 |
+
log "overlay verify OK"
|
| 900 |
+
fi
|
| 901 |
+
log "image ready on all four nodes"
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
# ---------------------------- weight download ------------------------------
|
| 905 |
+
# Use an already-complete local tree (primary or upstream fallback). If the
|
| 906 |
+
# durable Mia-AiLab mirror is still filling / 404s, keep serving from the
|
| 907 |
+
# brandonmusic cache folder without a second 164 GiB pull.
|
| 908 |
+
adopt_complete_weights() {
|
| 909 |
+
local have
|
| 910 |
+
have="$(count_shards "$MODEL_PATH")"
|
| 911 |
+
if [ "${have:-0}" -ge "$EXPECTED_SHARDS" ]; then
|
| 912 |
+
ensure_refs_main
|
| 913 |
+
log "weights already present: $MODEL_PATH ($have shards)"
|
| 914 |
+
return 0
|
| 915 |
+
fi
|
| 916 |
+
have="$(count_shards "$FALLBACK_MODEL_PATH")"
|
| 917 |
+
if [ "${have:-0}" -ge "$EXPECTED_SHARDS" ]; then
|
| 918 |
+
log "primary cache incomplete — using fallback ${MODEL_FALLBACK} at $FALLBACK_MODEL_PATH ($have shards)"
|
| 919 |
+
MODEL_PATH="$FALLBACK_MODEL_PATH"
|
| 920 |
+
MODEL_CACHE_NAME="$MODEL_FALLBACK_CACHE_NAME"
|
| 921 |
+
ensure_refs_main
|
| 922 |
+
return 0
|
| 923 |
+
fi
|
| 924 |
+
return 1
|
| 925 |
+
}
|
| 926 |
+
|
| 927 |
+
# Resolve the HF CLI even when it lives outside PATH (venv installs), with a
|
| 928 |
+
# python huggingface_hub fallback when no binary exists (issue #22, item 1).
|
| 929 |
+
# Sets the global HF_BIN_CMD array. HF_BIN (may contain arguments) wins when
|
| 930 |
+
# its first word resolves. Returns 1 when nothing usable is found.
|
| 931 |
+
resolve_hf_bin() {
|
| 932 |
+
HF_BIN_CMD=()
|
| 933 |
+
if [ -n "${HF_BIN:-}" ]; then
|
| 934 |
+
read -ra HF_BIN_CMD <<< "$HF_BIN"
|
| 935 |
+
if command -v "${HF_BIN_CMD[0]}" >/dev/null 2>&1; then return 0; fi
|
| 936 |
+
HF_BIN_CMD=()
|
| 937 |
+
fi
|
| 938 |
+
local cand
|
| 939 |
+
for cand in hf huggingface-cli "$HOME/.local/bin/hf" "$HOME/.hf-cli/venv/bin/hf" /opt/hf-cli/venv/bin/hf; do
|
| 940 |
+
if command -v "$cand" >/dev/null 2>&1; then HF_BIN_CMD=("$cand"); return 0; fi
|
| 941 |
+
done
|
| 942 |
+
if command -v python3 >/dev/null 2>&1 && python3 -c 'import huggingface_hub' >/dev/null 2>&1; then
|
| 943 |
+
HF_BIN_CMD=(python3 -m huggingface_hub.commands.huggingface_cli)
|
| 944 |
+
return 0
|
| 945 |
+
fi
|
| 946 |
+
return 1
|
| 947 |
+
}
|
| 948 |
+
|
| 949 |
+
hf_download_repo() {
|
| 950 |
+
local repo="$1"
|
| 951 |
+
shift
|
| 952 |
+
local -a args=("$repo")
|
| 953 |
+
if [ -n "${MODEL_REVISION:-}" ] && [ "$repo" = "$MODEL" ]; then
|
| 954 |
+
args+=(--revision "$MODEL_REVISION")
|
| 955 |
+
fi
|
| 956 |
+
args+=("$@")
|
| 957 |
+
HF_HOME="$HF_CACHE_DIR" "${HF_BIN_CMD[@]}" download "${args[@]}"
|
| 958 |
+
}
|
| 959 |
+
|
| 960 |
+
download_weights() {
|
| 961 |
+
[ "${SKIP_DOWNLOAD:-0}" = "1" ] && { log "SKIP_DOWNLOAD=1 — skipping download check"; return; }
|
| 962 |
+
if [ "${REFRESH_WEIGHTS:-0}" != "1" ] && adopt_complete_weights; then
|
| 963 |
+
return
|
| 964 |
+
fi
|
| 965 |
+
|
| 966 |
+
resolve_hf_bin || die "no 'hf' / 'huggingface-cli' on PATH and no python huggingface_hub — pip install --user -U 'huggingface_hub[cli]' (or set HF_BIN=/path/to/hf)"
|
| 967 |
+
|
| 968 |
+
mkdir -p "$HF_CACHE_DIR"
|
| 969 |
+
local -a hf_excl=()
|
| 970 |
+
local pat
|
| 971 |
+
IFS=',' read -ra _excl_pats <<< "${HF_DOWNLOAD_EXCLUDE:-runtime-results/**,src/**,runtime/src/**,scripts/**,docs/**,results/**,.materialization/**,runtime/scripts/**}"
|
| 972 |
+
for pat in "${_excl_pats[@]}"; do
|
| 973 |
+
[ -n "$pat" ] && hf_excl+=(--exclude "$pat")
|
| 974 |
+
done
|
| 975 |
+
|
| 976 |
+
log "downloading ${MODEL} (~164 GiB / ${EXPECTED_SHARDS} shards) into ${HF_CACHE_DIR} ..."
|
| 977 |
+
hf_download_repo "$MODEL" "${hf_excl[@]}" || warn "download of ${MODEL} failed — will try ${MODEL_FALLBACK}"
|
| 978 |
+
if adopt_complete_weights; then
|
| 979 |
+
return
|
| 980 |
+
fi
|
| 981 |
+
|
| 982 |
+
if [ "$MODEL_FALLBACK" != "$MODEL" ]; then
|
| 983 |
+
log "falling back to ${MODEL_FALLBACK} ..."
|
| 984 |
+
hf_download_repo "$MODEL_FALLBACK" "${hf_excl[@]}" \
|
| 985 |
+
|| die "download of ${MODEL} and ${MODEL_FALLBACK} both failed"
|
| 986 |
+
fi
|
| 987 |
+
adopt_complete_weights \
|
| 988 |
+
|| die "download finished with $(count_shards "$MODEL_PATH") / $EXPECTED_SHARDS shards"
|
| 989 |
+
}
|
| 990 |
+
|
| 991 |
+
download_dflash() {
|
| 992 |
+
[ "$SPEC_METHOD" = "dflash" ] || return 0
|
| 993 |
+
[ "${SKIP_DOWNLOAD:-0}" = "1" ] && { log "SKIP_DOWNLOAD=1 — skipping DFlash2 download check"; return; }
|
| 994 |
+
local have
|
| 995 |
+
have="$(find "$DFLASH_PATH/snapshots" -name 'model.safetensors' 2>/dev/null | wc -l | tr -d '[:space:]' || true)"
|
| 996 |
+
if [ "${have:-0}" -ge 1 ] && [ "${REFRESH_WEIGHTS:-0}" != "1" ]; then
|
| 997 |
+
log "DFlash2 already present: $DFLASH_PATH"
|
| 998 |
+
ensure_dflash_refs_main
|
| 999 |
+
return
|
| 1000 |
+
fi
|
| 1001 |
+
resolve_hf_bin || die "no 'hf' / 'huggingface-cli' on PATH and no python huggingface_hub — pip install --user -U 'huggingface_hub[cli]' (or set HF_BIN=/path/to/hf)"
|
| 1002 |
+
mkdir -p "$HF_CACHE_DIR"
|
| 1003 |
+
log "downloading ${DFLASH_MODEL} (~2.3 GiB) into ${HF_CACHE_DIR} ..."
|
| 1004 |
+
HF_HOME="$HF_CACHE_DIR" "${HF_BIN_CMD[@]}" download "$DFLASH_MODEL"
|
| 1005 |
+
ensure_dflash_refs_main
|
| 1006 |
+
have="$(find "$DFLASH_PATH/snapshots" -name 'model.safetensors' 2>/dev/null | wc -l | tr -d '[:space:]' || true)"
|
| 1007 |
+
[ "${have:-0}" -ge 1 ] || die "DFlash2 download finished without model.safetensors"
|
| 1008 |
+
log "DFlash2 download complete"
|
| 1009 |
+
}
|
| 1010 |
+
|
| 1011 |
+
# Head-only Hub fetch. No docker, no SSH, no worker rsync.
|
| 1012 |
+
download_only() {
|
| 1013 |
+
local have
|
| 1014 |
+
resolve_hf_bin || die "no 'hf' / 'huggingface-cli' on PATH and no python huggingface_hub — pip install --user -U 'huggingface_hub[cli]' (or set HF_BIN=/path/to/hf)"
|
| 1015 |
+
mkdir -p "$HF_CACHE_DIR"
|
| 1016 |
+
local need_kb=$((180 * 1024 * 1024)) avail
|
| 1017 |
+
avail=$(df -Pk "$HF_CACHE_DIR" 2>/dev/null | awk 'NR==2{print $4}' || true)
|
| 1018 |
+
[ "${avail:-0}" -ge "$need_kb" ] || warn "only $((avail/1024/1024)) GiB free on this disk for a ~164 GiB model"
|
| 1019 |
+
|
| 1020 |
+
# Explicit download: do not honor SKIP_DOWNLOAD from .env.
|
| 1021 |
+
SKIP_DOWNLOAD=0
|
| 1022 |
+
download_weights
|
| 1023 |
+
download_dflash
|
| 1024 |
+
|
| 1025 |
+
have="$(count_shards "$MODEL_PATH")"
|
| 1026 |
+
log "======================================================================"
|
| 1027 |
+
log "head HF cache : ${HF_CACHE_DIR}"
|
| 1028 |
+
log " target : ${MODEL} (${have} / ${EXPECTED_SHARDS} shards)"
|
| 1029 |
+
log " snapshot : ${MODEL_PATH}"
|
| 1030 |
+
if [ "$SPEC_METHOD" = "dflash" ]; then
|
| 1031 |
+
log " DFlash2 : ${DFLASH_MODEL}"
|
| 1032 |
+
log " draft cache : ${DFLASH_PATH}"
|
| 1033 |
+
else
|
| 1034 |
+
log " DFlash2 : skipped (SPEC_METHOD=${SPEC_METHOD})"
|
| 1035 |
+
fi
|
| 1036 |
+
log "workers were not touched. ./start-tp4.sh will rsync on launch unless SKIP_SYNC=1."
|
| 1037 |
+
log "======================================================================"
|
| 1038 |
+
}
|
| 1039 |
+
|
| 1040 |
+
# ------------------------------ weight sync --------------------------------
|
| 1041 |
+
# Keyed on the snapshot commit (refs/main, with the same repair fallback as
|
| 1042 |
+
# ensure_refs_main), not on MODEL_REVISION: the marker lives inside each
|
| 1043 |
+
# synced repo folder, so a MODEL / revision switch re-syncs automatically.
|
| 1044 |
+
# Without it, every ./start.sh pays a full size+mtime re-verification walk
|
| 1045 |
+
# over ~164 GiB / 120 shards on both ends for zero bytes of difference
|
| 1046 |
+
# (issue #22, item 2). FORCE_SYNC=1 bypasses the marker; deleting the
|
| 1047 |
+
# marker file on the worker has the same effect.
|
| 1048 |
+
sync_repo_marker_rev() {
|
| 1049 |
+
local src="$1"
|
| 1050 |
+
local rev
|
| 1051 |
+
rev="$(cat "$src/refs/main" 2>/dev/null || true)"
|
| 1052 |
+
[ -n "$rev" ] || rev="$(ls -1t "$src/snapshots" 2>/dev/null | head -n 1 || true)"
|
| 1053 |
+
[ -n "$rev" ] || rev="unknown"
|
| 1054 |
+
printf '%s' "$rev"
|
| 1055 |
+
}
|
| 1056 |
+
|
| 1057 |
+
sync_repo_to_one_worker() {
|
| 1058 |
+
local r="$1" src="$2" cache_name="$3" label="$4"
|
| 1059 |
+
local marker rev hf ssh_t
|
| 1060 |
+
hf="$(_tp4_rank_hf "$r")"
|
| 1061 |
+
ssh_t="$(_tp4_ssh_target "$r")"
|
| 1062 |
+
marker="${hf}/hub/${cache_name}/.glm53-exl3-synced"
|
| 1063 |
+
rev="$(sync_repo_marker_rev "$src")"
|
| 1064 |
+
if [ "${FORCE_SYNC:-0}" != "1" ] \
|
| 1065 |
+
&& [ "$(worker_ssh_n "$r" "cat '$marker' 2>/dev/null" || true)" = "$rev" ]; then
|
| 1066 |
+
log "rank ${r} ${cache_name} already at ${rev} — rsync skipped"
|
| 1067 |
+
return 0
|
| 1068 |
+
fi
|
| 1069 |
+
log "syncing ${label} to rank ${r} (${ssh_t}) ..."
|
| 1070 |
+
worker_ssh_n "$r" "mkdir -p '${hf}/hub/${cache_name}'"
|
| 1071 |
+
rsync -a --partial --info=progress2 \
|
| 1072 |
+
"$src/" "${ssh_t}:${hf}/hub/${cache_name}/"
|
| 1073 |
+
worker_ssh_n "$r" "printf '%s' '$rev' > '$marker'"
|
| 1074 |
+
}
|
| 1075 |
+
|
| 1076 |
+
sync_weights() {
|
| 1077 |
+
[ "${SKIP_SYNC:-0}" = "1" ] && { log "SKIP_SYNC=1 — not syncing to workers"; return; }
|
| 1078 |
+
[ -d "$MODEL_PATH" ] || die "weights missing at $MODEL_PATH — run without SKIP_DOWNLOAD first"
|
| 1079 |
+
local r
|
| 1080 |
+
for r in 1 2 3; do
|
| 1081 |
+
sync_repo_to_one_worker "$r" "$MODEL_PATH" "$MODEL_CACHE_NAME" "weights"
|
| 1082 |
+
if [ "$SPEC_METHOD" = "dflash" ]; then
|
| 1083 |
+
[ -d "$DFLASH_PATH" ] || die "DFlash2 weights missing at $DFLASH_PATH"
|
| 1084 |
+
sync_repo_to_one_worker "$r" "$DFLASH_PATH" "$DFLASH_CACHE_NAME" "DFlash2 draft"
|
| 1085 |
+
fi
|
| 1086 |
+
done
|
| 1087 |
+
log "all worker weights in sync"
|
| 1088 |
+
}
|
| 1089 |
+
|
| 1090 |
+
# ------------------------ inner container scripts --------------------------
|
| 1091 |
+
write_inner_scripts() {
|
| 1092 |
+
cat > "$HEAD_SCRIPT" <<'EOF'
|
| 1093 |
+
#!/bin/bash
|
| 1094 |
+
set -euo pipefail
|
| 1095 |
+
say() { echo "[glm53-exl3-head] $*"; }
|
| 1096 |
+
|
| 1097 |
+
ARGS=(
|
| 1098 |
+
--served-model-name "${SERVED_MODEL_NAME}"
|
| 1099 |
+
--host 0.0.0.0
|
| 1100 |
+
--port "${PORT}"
|
| 1101 |
+
--tensor-parallel-size "${TP}"
|
| 1102 |
+
--nnodes "${NNODES}"
|
| 1103 |
+
--node-rank 0
|
| 1104 |
+
--master-addr "${HEAD_IP}"
|
| 1105 |
+
--master-port "${MASTER_PORT}"
|
| 1106 |
+
--distributed-executor-backend mp
|
| 1107 |
+
--tool-call-parser glm47
|
| 1108 |
+
--enable-auto-tool-choice
|
| 1109 |
+
--reasoning-parser glm45
|
| 1110 |
+
--enable-prefix-caching
|
| 1111 |
+
--no-enable-flashinfer-autotune
|
| 1112 |
+
)
|
| 1113 |
+
[ "${ENFORCE_EAGER:-1}" = "1" ] && ARGS+=(--enforce-eager)
|
| 1114 |
+
[ -n "${QUANTIZATION:-}" ] && [ "${QUANTIZATION}" != "none" ] && ARGS+=(--quantization "${QUANTIZATION}")
|
| 1115 |
+
[ -n "${MAX_MODEL_LEN:-}" ] && ARGS+=(--max-model-len "${MAX_MODEL_LEN}")
|
| 1116 |
+
[ -n "${GPU_MEM_UTIL:-}" ] && ARGS+=(--gpu-memory-utilization "${GPU_MEM_UTIL}")
|
| 1117 |
+
[ -n "${MAX_NUM_SEQS:-}" ] && ARGS+=(--max-num-seqs "${MAX_NUM_SEQS}")
|
| 1118 |
+
[ -n "${MAX_NUM_BATCHED_TOKENS:-}" ] && ARGS+=(--max-num-batched-tokens "${MAX_NUM_BATCHED_TOKENS}")
|
| 1119 |
+
[ -n "${KV_CACHE_DTYPE:-}" ] && ARGS+=(--kv-cache-dtype "${KV_CACHE_DTYPE}")
|
| 1120 |
+
if [ "${SPEC_METHOD:-mtp}" = "dflash" ]; then
|
| 1121 |
+
ARGS+=(--speculative-config "$(python3 -S -c 'import json,os
|
| 1122 |
+
spec={"method":"dflash","model":os.environ["DFLASH_MODEL_DIR"],"num_speculative_tokens":int(os.environ.get("DFLASH_TOKENS","7")),"kv_cache_dtype":"auto","draft_sample_method":"probabilistic","rejection_sample_method":"standard"}
|
| 1123 |
+
tp=os.environ.get("DFLASH_DRAFT_TP","").strip()
|
| 1124 |
+
if tp:
|
| 1125 |
+
spec["draft_tensor_parallel_size"]=int(tp)
|
| 1126 |
+
print(json.dumps(spec,separators=(",",":")))')")
|
| 1127 |
+
elif [ "${SPEC_METHOD:-mtp}" = "none" ]; then
|
| 1128 |
+
:
|
| 1129 |
+
elif [ "${MTP_TOKENS:-0}" != "0" ]; then
|
| 1130 |
+
ARGS+=(--speculative-config "{\"method\":\"mtp\",\"num_speculative_tokens\":${MTP_TOKENS}}")
|
| 1131 |
+
fi
|
| 1132 |
+
if [ -n "${CHAT_TEMPLATE:-}" ] && [ -f "${CHAT_TEMPLATE}" ]; then
|
| 1133 |
+
ARGS+=(--chat-template "${CHAT_TEMPLATE}")
|
| 1134 |
+
fi
|
| 1135 |
+
if [ "${LANGUAGE_MODEL_ONLY:-0}" = "1" ]; then
|
| 1136 |
+
ARGS+=(--language-model-only)
|
| 1137 |
+
say "language-model-only: no vision tower"
|
| 1138 |
+
else
|
| 1139 |
+
[ -n "${LIMIT_MM:-}" ] && ARGS+=(--limit-mm-per-prompt "${LIMIT_MM}")
|
| 1140 |
+
[ "${SKIP_MM_PROFILING:-1}" = "1" ] && ARGS+=(--skip-mm-profiling)
|
| 1141 |
+
say "vision on: limit-mm=${LIMIT_MM:-} skip-mm-profiling=${SKIP_MM_PROFILING:-1} chat-template=${CHAT_TEMPLATE:-}"
|
| 1142 |
+
fi
|
| 1143 |
+
if [ -n "${EXTRA_ARGS:-}" ]; then
|
| 1144 |
+
# shellcheck disable=SC2206
|
| 1145 |
+
EXTRA=(${EXTRA_ARGS})
|
| 1146 |
+
ARGS+=("${EXTRA[@]}")
|
| 1147 |
+
fi
|
| 1148 |
+
|
| 1149 |
+
[ -f "${MODEL_DIR}/config.json" ] || { say "FATAL: ${MODEL_DIR}/config.json missing"; ls -la "${MODEL_DIR}" | head; exit 1; }
|
| 1150 |
+
if [ -f /opt/glm53/patch_glm_video_placeholders.py ]; then
|
| 1151 |
+
python3 /opt/glm53/patch_glm_video_placeholders.py
|
| 1152 |
+
fi
|
| 1153 |
+
if [ -f /opt/glm53/patch_suppress_stops_in_reasoning.py ]; then
|
| 1154 |
+
python3 /opt/glm53/patch_suppress_stops_in_reasoning.py
|
| 1155 |
+
fi
|
| 1156 |
+
if [ -f /opt/glm53/patch_scheduler_decode_floor.py ]; then
|
| 1157 |
+
python3 /opt/glm53/patch_scheduler_decode_floor.py
|
| 1158 |
+
fi
|
| 1159 |
+
if [ -f /opt/glm53/patch_glm5_drafter_group.py ]; then
|
| 1160 |
+
python3 /opt/glm53/patch_glm5_drafter_group.py
|
| 1161 |
+
fi
|
| 1162 |
+
if [ -f /opt/glm53/patch_hybrid_prefix_hit.py ]; then
|
| 1163 |
+
python3 /opt/glm53/patch_hybrid_prefix_hit.py
|
| 1164 |
+
fi
|
| 1165 |
+
if [ -f /opt/glm53/patch_xgrammar_termination.py ]; then
|
| 1166 |
+
python3 /opt/glm53/patch_xgrammar_termination.py
|
| 1167 |
+
fi
|
| 1168 |
+
if [ -f /opt/glm53/patch_kpool_tail_slotmap.py ]; then
|
| 1169 |
+
python3 /opt/glm53/patch_kpool_tail_slotmap.py
|
| 1170 |
+
fi
|
| 1171 |
+
if [ -f /opt/glm53/patch_spinwait.py ]; then
|
| 1172 |
+
python3 /opt/glm53/patch_spinwait.py
|
| 1173 |
+
fi
|
| 1174 |
+
if [ -f /opt/glm53/patch_indexer_workspace.py ]; then
|
| 1175 |
+
python3 /opt/glm53/patch_indexer_workspace.py
|
| 1176 |
+
fi
|
| 1177 |
+
if [ -f /opt/glm53/patch_ablit.py ]; then
|
| 1178 |
+
python3 /opt/glm53/patch_ablit.py
|
| 1179 |
+
fi
|
| 1180 |
+
if [ "${ABLIT:-0}" = "1" ]; then
|
| 1181 |
+
say "ablit: o_proj orthogonalization ON (method=${ABLIT_METHOD:-auto} direction=${ABLIT_DIRECTION:-dealign} layers=${ABLIT_LAYERS:-15-45} alpha=${ABLIT_ALPHA:-3.0})"
|
| 1182 |
+
else
|
| 1183 |
+
say "ablit: off — stock o_proj weights"
|
| 1184 |
+
fi
|
| 1185 |
+
say "launching: vllm serve ${MODEL_DIR} ${ARGS[*]}"
|
| 1186 |
+
exec vllm serve "${MODEL_DIR}" "${ARGS[@]}"
|
| 1187 |
+
EOF
|
| 1188 |
+
|
| 1189 |
+
cat > "$WORKER_SCRIPT" <<'EOF'
|
| 1190 |
+
#!/bin/bash
|
| 1191 |
+
set -euo pipefail
|
| 1192 |
+
say() { echo "[glm53-exl3-worker] $*"; }
|
| 1193 |
+
|
| 1194 |
+
ARGS=(
|
| 1195 |
+
--served-model-name "${SERVED_MODEL_NAME}"
|
| 1196 |
+
--host 0.0.0.0
|
| 1197 |
+
--port "${PORT}"
|
| 1198 |
+
--tensor-parallel-size "${TP}"
|
| 1199 |
+
--nnodes "${NNODES}"
|
| 1200 |
+
--node-rank "${NODE_RANK}"
|
| 1201 |
+
--master-addr "${HEAD_IP}"
|
| 1202 |
+
--master-port "${MASTER_PORT}"
|
| 1203 |
+
--distributed-executor-backend mp
|
| 1204 |
+
--headless
|
| 1205 |
+
--tool-call-parser glm47
|
| 1206 |
+
--enable-auto-tool-choice
|
| 1207 |
+
--reasoning-parser glm45
|
| 1208 |
+
--enable-prefix-caching
|
| 1209 |
+
--no-enable-flashinfer-autotune
|
| 1210 |
+
)
|
| 1211 |
+
[ "${ENFORCE_EAGER:-1}" = "1" ] && ARGS+=(--enforce-eager)
|
| 1212 |
+
[ -n "${QUANTIZATION:-}" ] && [ "${QUANTIZATION}" != "none" ] && ARGS+=(--quantization "${QUANTIZATION}")
|
| 1213 |
+
[ -n "${MAX_MODEL_LEN:-}" ] && ARGS+=(--max-model-len "${MAX_MODEL_LEN}")
|
| 1214 |
+
[ -n "${GPU_MEM_UTIL:-}" ] && ARGS+=(--gpu-memory-utilization "${GPU_MEM_UTIL}")
|
| 1215 |
+
[ -n "${MAX_NUM_SEQS:-}" ] && ARGS+=(--max-num-seqs "${MAX_NUM_SEQS}")
|
| 1216 |
+
[ -n "${MAX_NUM_BATCHED_TOKENS:-}" ] && ARGS+=(--max-num-batched-tokens "${MAX_NUM_BATCHED_TOKENS}")
|
| 1217 |
+
[ -n "${KV_CACHE_DTYPE:-}" ] && ARGS+=(--kv-cache-dtype "${KV_CACHE_DTYPE}")
|
| 1218 |
+
if [ "${SPEC_METHOD:-mtp}" = "dflash" ]; then
|
| 1219 |
+
ARGS+=(--speculative-config "$(python3 -S -c 'import json,os
|
| 1220 |
+
spec={"method":"dflash","model":os.environ["DFLASH_MODEL_DIR"],"num_speculative_tokens":int(os.environ.get("DFLASH_TOKENS","7")),"kv_cache_dtype":"auto","draft_sample_method":"probabilistic","rejection_sample_method":"standard"}
|
| 1221 |
+
tp=os.environ.get("DFLASH_DRAFT_TP","").strip()
|
| 1222 |
+
if tp:
|
| 1223 |
+
spec["draft_tensor_parallel_size"]=int(tp)
|
| 1224 |
+
print(json.dumps(spec,separators=(",",":")))')")
|
| 1225 |
+
elif [ "${SPEC_METHOD:-mtp}" = "none" ]; then
|
| 1226 |
+
:
|
| 1227 |
+
elif [ "${MTP_TOKENS:-0}" != "0" ]; then
|
| 1228 |
+
ARGS+=(--speculative-config "{\"method\":\"mtp\",\"num_speculative_tokens\":${MTP_TOKENS}}")
|
| 1229 |
+
fi
|
| 1230 |
+
if [ -n "${CHAT_TEMPLATE:-}" ] && [ -f "${CHAT_TEMPLATE}" ]; then
|
| 1231 |
+
ARGS+=(--chat-template "${CHAT_TEMPLATE}")
|
| 1232 |
+
fi
|
| 1233 |
+
if [ "${LANGUAGE_MODEL_ONLY:-0}" = "1" ]; then
|
| 1234 |
+
ARGS+=(--language-model-only)
|
| 1235 |
+
else
|
| 1236 |
+
[ -n "${LIMIT_MM:-}" ] && ARGS+=(--limit-mm-per-prompt "${LIMIT_MM}")
|
| 1237 |
+
[ "${SKIP_MM_PROFILING:-1}" = "1" ] && ARGS+=(--skip-mm-profiling)
|
| 1238 |
+
fi
|
| 1239 |
+
if [ -n "${EXTRA_ARGS:-}" ]; then
|
| 1240 |
+
# shellcheck disable=SC2206
|
| 1241 |
+
EXTRA=(${EXTRA_ARGS})
|
| 1242 |
+
ARGS+=("${EXTRA[@]}")
|
| 1243 |
+
fi
|
| 1244 |
+
|
| 1245 |
+
[ -f "${MODEL_DIR}/config.json" ] || { say "FATAL: ${MODEL_DIR}/config.json missing"; ls -la "${MODEL_DIR}" | head; exit 1; }
|
| 1246 |
+
if [ -f /opt/glm53/patch_glm_video_placeholders.py ]; then
|
| 1247 |
+
python3 /opt/glm53/patch_glm_video_placeholders.py
|
| 1248 |
+
fi
|
| 1249 |
+
if [ -f /opt/glm53/patch_suppress_stops_in_reasoning.py ]; then
|
| 1250 |
+
python3 /opt/glm53/patch_suppress_stops_in_reasoning.py
|
| 1251 |
+
fi
|
| 1252 |
+
if [ -f /opt/glm53/patch_scheduler_decode_floor.py ]; then
|
| 1253 |
+
python3 /opt/glm53/patch_scheduler_decode_floor.py
|
| 1254 |
+
fi
|
| 1255 |
+
if [ -f /opt/glm53/patch_glm5_drafter_group.py ]; then
|
| 1256 |
+
python3 /opt/glm53/patch_glm5_drafter_group.py
|
| 1257 |
+
fi
|
| 1258 |
+
if [ -f /opt/glm53/patch_hybrid_prefix_hit.py ]; then
|
| 1259 |
+
python3 /opt/glm53/patch_hybrid_prefix_hit.py
|
| 1260 |
+
fi
|
| 1261 |
+
if [ -f /opt/glm53/patch_xgrammar_termination.py ]; then
|
| 1262 |
+
python3 /opt/glm53/patch_xgrammar_termination.py
|
| 1263 |
+
fi
|
| 1264 |
+
if [ -f /opt/glm53/patch_kpool_tail_slotmap.py ]; then
|
| 1265 |
+
python3 /opt/glm53/patch_kpool_tail_slotmap.py
|
| 1266 |
+
fi
|
| 1267 |
+
if [ -f /opt/glm53/patch_spinwait.py ]; then
|
| 1268 |
+
python3 /opt/glm53/patch_spinwait.py
|
| 1269 |
+
fi
|
| 1270 |
+
if [ -f /opt/glm53/patch_indexer_workspace.py ]; then
|
| 1271 |
+
python3 /opt/glm53/patch_indexer_workspace.py
|
| 1272 |
+
fi
|
| 1273 |
+
if [ -f /opt/glm53/patch_ablit.py ]; then
|
| 1274 |
+
python3 /opt/glm53/patch_ablit.py
|
| 1275 |
+
fi
|
| 1276 |
+
if [ "${ABLIT:-0}" = "1" ]; then
|
| 1277 |
+
say "ablit: o_proj orthogonalization ON (method=${ABLIT_METHOD:-auto} direction=${ABLIT_DIRECTION:-dealign} layers=${ABLIT_LAYERS:-15-45} alpha=${ABLIT_ALPHA:-3.0})"
|
| 1278 |
+
else
|
| 1279 |
+
say "ablit: off — stock o_proj weights"
|
| 1280 |
+
fi
|
| 1281 |
+
say "joining TP${TP} at ${HEAD_IP}:${MASTER_PORT} as rank ${NODE_RANK}"
|
| 1282 |
+
exec vllm serve "${MODEL_DIR}" "${ARGS[@]}"
|
| 1283 |
+
EOF
|
| 1284 |
+
chmod +x "$HEAD_SCRIPT" "$WORKER_SCRIPT"
|
| 1285 |
+
}
|
| 1286 |
+
|
| 1287 |
+
# ------------------------------- launch ------------------------------------
|
| 1288 |
+
_tp4_scp_runtime() {
|
| 1289 |
+
local r="$1" ssh_t cname
|
| 1290 |
+
ssh_t="$(_tp4_ssh_target "$r")"
|
| 1291 |
+
cname="$(_tp4_rank_container "$r")"
|
| 1292 |
+
scp -q -o BatchMode=yes "$WORKER_SCRIPT" "${ssh_t}:/tmp/${cname}.sh"
|
| 1293 |
+
scp -q -o BatchMode=yes "$CHAT_TEMPLATE_HOST" "${ssh_t}:/tmp/glm53-chat_template.jinja"
|
| 1294 |
+
scp -q -o BatchMode=yes "$VIDEO_PATCH_HOST" "${ssh_t}:/tmp/patch_glm_video_placeholders.py"
|
| 1295 |
+
scp -q -o BatchMode=yes "$STOP_PATCH_HOST" "${ssh_t}:/tmp/patch_suppress_stops_in_reasoning.py"
|
| 1296 |
+
scp -q -o BatchMode=yes "$SCHED_PATCH_HOST" "${ssh_t}:/tmp/patch_scheduler_decode_floor.py"
|
| 1297 |
+
scp -q -o BatchMode=yes "$DRAFTER_PATCH_HOST" "${ssh_t}:/tmp/patch_glm5_drafter_group.py"
|
| 1298 |
+
scp -q -o BatchMode=yes "$APC_PATCH_HOST" "${ssh_t}:/tmp/patch_hybrid_prefix_hit.py"
|
| 1299 |
+
scp -q -o BatchMode=yes "$XGRAMMAR_PATCH_HOST" "${ssh_t}:/tmp/patch_xgrammar_termination.py"
|
| 1300 |
+
scp -q -o BatchMode=yes "$KPOOL_TAIL_PATCH_HOST" "${ssh_t}:/tmp/patch_kpool_tail_slotmap.py"
|
| 1301 |
+
scp -q -o BatchMode=yes "$SPINWAIT_PATCH_HOST" "${ssh_t}:/tmp/patch_spinwait.py"
|
| 1302 |
+
worker_ssh_n "$r" "rm -rf /tmp/glm53-ablit"
|
| 1303 |
+
scp -q -r -o BatchMode=yes "$SCRIPT_DIR/ablit" "${ssh_t}:/tmp/glm53-ablit"
|
| 1304 |
+
scp -q -o BatchMode=yes "$SCRIPT_DIR/overlay/ablit_runtime.py" "${ssh_t}:/tmp/glm53-ablit_runtime.py"
|
| 1305 |
+
scp -q -o BatchMode=yes "$SCRIPT_DIR/overlay/patch_ablit.py" "${ssh_t}:/tmp/patch_ablit.py"
|
| 1306 |
+
}
|
| 1307 |
+
|
| 1308 |
+
launch_cluster() {
|
| 1309 |
+
local r
|
| 1310 |
+
docker rm -f "$CONTAINER_HEAD" >/dev/null 2>&1 || true
|
| 1311 |
+
for r in 1 2 3; do
|
| 1312 |
+
worker_ssh_n "$r" "docker rm -f '$(_tp4_rank_container "$r")'" >/dev/null 2>&1 || true
|
| 1313 |
+
done
|
| 1314 |
+
|
| 1315 |
+
mkdir -p "$CACHE_ROOT" "$TRITON_HOST_CACHE" "$TILELANG_HOST_CACHE"
|
| 1316 |
+
[ -f "$CHAT_TEMPLATE_HOST" ] || die "missing chat template: $CHAT_TEMPLATE_HOST"
|
| 1317 |
+
for r in 1 2 3; do
|
| 1318 |
+
worker_ssh_n "$r" "mkdir -p '$(_tp4_rank_vllm "$r")' '$(_tp4_rank_triton "$r")' '$(_tp4_rank_tilelang "$r")'"
|
| 1319 |
+
_tp4_scp_runtime "$r"
|
| 1320 |
+
done
|
| 1321 |
+
# skip the old single-worker scp block
|
| 1322 |
+
true
|
| 1323 |
+
: <<'TP4_SKIP_OLD_SCP'
|
| 1324 |
+
[ -f "$CHAT_TEMPLATE_HOST" ] || die "missing chat template: $CHAT_TEMPLATE_HOST"
|
| 1325 |
+
scp -q -o BatchMode=yes "$CHAT_TEMPLATE_HOST" "${WORKER_SSH}:/tmp/glm53-chat_template.jinja"
|
| 1326 |
+
[ -f "$VIDEO_PATCH_HOST" ] || die "missing $VIDEO_PATCH_HOST"
|
| 1327 |
+
scp -q -o BatchMode=yes "$VIDEO_PATCH_HOST" "${WORKER_SSH}:/tmp/patch_glm_video_placeholders.py"
|
| 1328 |
+
[ -f "$STOP_PATCH_HOST" ] || die "missing $STOP_PATCH_HOST"
|
| 1329 |
+
scp -q -o BatchMode=yes "$STOP_PATCH_HOST" "${WORKER_SSH}:/tmp/patch_suppress_stops_in_reasoning.py"
|
| 1330 |
+
[ -f "$SCHED_PATCH_HOST" ] || die "missing $SCHED_PATCH_HOST"
|
| 1331 |
+
scp -q -o BatchMode=yes "$SCHED_PATCH_HOST" "${WORKER_SSH}:/tmp/patch_scheduler_decode_floor.py"
|
| 1332 |
+
[ -f "$DRAFTER_PATCH_HOST" ] || die "missing $DRAFTER_PATCH_HOST"
|
| 1333 |
+
scp -q -o BatchMode=yes "$DRAFTER_PATCH_HOST" "${WORKER_SSH}:/tmp/patch_glm5_drafter_group.py"
|
| 1334 |
+
[ -f "$APC_PATCH_HOST" ] || die "missing $APC_PATCH_HOST"
|
| 1335 |
+
scp -q -o BatchMode=yes "$APC_PATCH_HOST" "${WORKER_SSH}:/tmp/patch_hybrid_prefix_hit.py"
|
| 1336 |
+
[ -f "$XGRAMMAR_PATCH_HOST" ] || die "missing $XGRAMMAR_PATCH_HOST"
|
| 1337 |
+
scp -q -o BatchMode=yes "$XGRAMMAR_PATCH_HOST" "${WORKER_SSH}:/tmp/patch_xgrammar_termination.py"
|
| 1338 |
+
[ -f "$KPOOL_TAIL_PATCH_HOST" ] || die "missing $KPOOL_TAIL_PATCH_HOST"
|
| 1339 |
+
scp -q -o BatchMode=yes "$KPOOL_TAIL_PATCH_HOST" "${WORKER_SSH}:/tmp/patch_kpool_tail_slotmap.py"
|
| 1340 |
+
[ -f "$SPINWAIT_PATCH_HOST" ] || die "missing $SPINWAIT_PATCH_HOST"
|
| 1341 |
+
scp -q -o BatchMode=yes "$SPINWAIT_PATCH_HOST" "${WORKER_SSH}:/tmp/patch_spinwait.py"
|
| 1342 |
+
|
| 1343 |
+
worker_ssh "rm -rf /tmp/glm53-ablit"
|
| 1344 |
+
scp -q -r -o BatchMode=yes "$SCRIPT_DIR/ablit" "${WORKER_SSH}:/tmp/glm53-ablit"
|
| 1345 |
+
scp -q -o BatchMode=yes "$SCRIPT_DIR/overlay/ablit_runtime.py" "${WORKER_SSH}:/tmp/glm53-ablit_runtime.py"
|
| 1346 |
+
scp -q -o BatchMode=yes "$SCRIPT_DIR/overlay/patch_ablit.py" "${WORKER_SSH}:/tmp/patch_ablit.py"
|
| 1347 |
+
TP4_SKIP_OLD_SCP
|
| 1348 |
+
|
| 1349 |
+
local -a nccl_common=(
|
| 1350 |
+
-e NCCL_IB_DISABLE=0
|
| 1351 |
+
-e NCCL_IB_ROCE_VERSION_NUM=2
|
| 1352 |
+
-e NCCL_NET=IB
|
| 1353 |
+
-e NCCL_NET_PLUGIN=none
|
| 1354 |
+
-e NCCL_NVLS_ENABLE=0
|
| 1355 |
+
-e NCCL_CUMEM_ENABLE=0
|
| 1356 |
+
-e NCCL_IB_MERGE_NICS=0
|
| 1357 |
+
-e "NCCL_CROSS_NIC=$NCCL_CROSS_NIC"
|
| 1358 |
+
-e NCCL_IGNORE_CPU_AFFINITY=1
|
| 1359 |
+
-e "NCCL_DEBUG=$NCCL_DEBUG"
|
| 1360 |
+
-e HF_HUB_OFFLINE=1
|
| 1361 |
+
-e TRANSFORMERS_OFFLINE=1
|
| 1362 |
+
-e HF_HOME=/root/.cache/huggingface
|
| 1363 |
+
-e VLLM_CACHE_ROOT=/root/.cache/vllm
|
| 1364 |
+
-e "GLM53_SUPPRESS_STOPS_IN_REASONING=$GLM53_SUPPRESS_STOPS_IN_REASONING"
|
| 1365 |
+
-e "GLM53_MIXED_PREFILL_CHUNK=$GLM53_MIXED_PREFILL_CHUNK"
|
| 1366 |
+
-e "GLM53_INDEXER_WORKSPACE=$GLM53_INDEXER_WORKSPACE"
|
| 1367 |
+
-e "GLM53_SPINWAIT_MS=$GLM53_SPINWAIT_MS"
|
| 1368 |
+
-e "TRITON_CACHE_DIR=$TRITON_CACHE_DIR"
|
| 1369 |
+
-e "TILELANG_CACHE_DIR=$TILELANG_CACHE_DIR"
|
| 1370 |
+
-e "VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=$VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS"
|
| 1371 |
+
-e "TORCH_CUDA_ARCH_LIST=$TORCH_CUDA_ARCH_LIST"
|
| 1372 |
+
-e "FLASHINFER_CUDA_ARCH_LIST=$FLASHINFER_CUDA_ARCH_LIST"
|
| 1373 |
+
-e FLASHINFER_DISABLE_VERSION_CHECK=1
|
| 1374 |
+
-e PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
|
| 1375 |
+
-e "VLLM_ENGINE_READY_TIMEOUT_S=$READY_TIMEOUT"
|
| 1376 |
+
# py-cpuinfo JSON-parses empty output on Grace/aarch64; the usage
|
| 1377 |
+
# thread then dumps JSONDecodeError. Stats are off on this private kit.
|
| 1378 |
+
-e VLLM_NO_USAGE_STATS=1
|
| 1379 |
+
-e DO_NOT_TRACK=1
|
| 1380 |
+
-e "VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS=$CG_ESTIMATE"
|
| 1381 |
+
)
|
| 1382 |
+
local worker_nccl="" e
|
| 1383 |
+
for e in "${nccl_common[@]}"; do
|
| 1384 |
+
[ "$e" = "-e" ] && continue
|
| 1385 |
+
worker_nccl+=" -e $e"
|
| 1386 |
+
done
|
| 1387 |
+
|
| 1388 |
+
local -a head_preload=()
|
| 1389 |
+
if [ "$USE_HOST_NCCL" = "1" ]; then
|
| 1390 |
+
if [ -f "$NCCL_HOST_DIR/$NCCL_SO_NAME" ]; then
|
| 1391 |
+
head_preload=(-v "$NCCL_HOST_DIR:/nccl:ro" -e "LD_PRELOAD=/nccl/$NCCL_SO_NAME")
|
| 1392 |
+
log "head: LD_PRELOAD $NCCL_SO_NAME"
|
| 1393 |
+
else
|
| 1394 |
+
warn "head: $NCCL_HOST_DIR/$NCCL_SO_NAME missing — using image NCCL"
|
| 1395 |
+
fi
|
| 1396 |
+
fi
|
| 1397 |
+
|
| 1398 |
+
local serve_env=""
|
| 1399 |
+
local v
|
| 1400 |
+
for v in SERVED_MODEL_NAME PORT TP NNODES HEAD_IP MASTER_PORT QUANTIZATION \
|
| 1401 |
+
MAX_MODEL_LEN GPU_MEM_UTIL MAX_NUM_SEQS MAX_NUM_BATCHED_TOKENS \
|
| 1402 |
+
KV_CACHE_DTYPE MTP_TOKENS SPEC_METHOD DFLASH_TOKENS DFLASH_MODEL_DIR \
|
| 1403 |
+
DFLASH_DRAFT_TP \
|
| 1404 |
+
LANGUAGE_MODEL_ONLY SKIP_MM_PROFILING \
|
| 1405 |
+
LIMIT_MM CHAT_TEMPLATE ENFORCE_EAGER EXL3_FUSED_MOE EXL3_MOE_ROW_TILE EXL3_TEMP_ROWS_FUSED EXL3_FAT_SORTED EXL3_FAT_BATCHED EXL3_FAT_KERNEL MODEL_DIR EXTRA_ARGS \
|
| 1406 |
+
ABLIT ABLIT_METHOD ABLIT_DIRECTION ABLIT_LAYERS ABLIT_ALPHA ABLIT_INCLUDE_MTP; do
|
| 1407 |
+
serve_env+=" -e $v='${!v:-}'"
|
| 1408 |
+
done
|
| 1409 |
+
# VLLM_API_KEY is read by the head (rank 0) API server for bearer auth; the
|
| 1410 |
+
# worker runs --headless so it only needs the var for argv-parity, and
|
| 1411 |
+
# start.sh below passes it explicitly on the head. Keep it out of the
|
| 1412 |
+
# generic loop so the key never shows in process listings of either node
|
| 1413 |
+
# beyond the container env (same as the DeepSeek deployment).
|
| 1414 |
+
serve_env+=" -e VLLM_API_KEY='${VLLM_API_KEY:-}'"
|
| 1415 |
+
|
| 1416 |
+
local worker_preload="" nccl_dir cname
|
| 1417 |
+
for r in 1 2 3; do
|
| 1418 |
+
worker_preload=""
|
| 1419 |
+
if [ "$USE_HOST_NCCL" = "1" ]; then
|
| 1420 |
+
nccl_dir="$(_tp4_rank_nccl_dir "$r")"
|
| 1421 |
+
if worker_ssh_n "$r" "test -f '$nccl_dir/$NCCL_SO_NAME'"; then
|
| 1422 |
+
worker_preload="-v '$nccl_dir:/nccl:ro' -e LD_PRELOAD='/nccl/$NCCL_SO_NAME'"
|
| 1423 |
+
log "rank ${r}: LD_PRELOAD $NCCL_SO_NAME"
|
| 1424 |
+
else
|
| 1425 |
+
warn "rank ${r}: $nccl_dir/$NCCL_SO_NAME missing — using image NCCL"
|
| 1426 |
+
fi
|
| 1427 |
+
fi
|
| 1428 |
+
cname="$(_tp4_rank_container "$r")"
|
| 1429 |
+
log "starting rank ${r} on $(_tp4_ssh_target "$r") (NCCL if=$(_tp4_rank_cx7_if "$r") hca=$(_tp4_rank_cx7_ib "$r")) ..."
|
| 1430 |
+
[ -n "${LOCAL_MODEL_DIR:-}" ] && worker_preload="$worker_preload -v '$LOCAL_MODEL_DIR:/model:ro'"
|
| 1431 |
+
[ -n "${LOCAL_DFLASH_DIR:-}" ] && worker_preload="$worker_preload -v '$LOCAL_DFLASH_DIR:/draft:ro'"
|
| 1432 |
+
worker_ssh_n "$r" "docker run -d --name '$cname' \
|
| 1433 |
+
--gpus all --network host --ipc=host --shm-size 32g --stop-timeout 60 \
|
| 1434 |
+
--device /dev/infiniband --cap-add IPC_LOCK \
|
| 1435 |
+
--ulimit memlock=-1 --ulimit stack=67108864 --ulimit nofile=1048576:1048576 \
|
| 1436 |
+
-v '$(_tp4_rank_hf "$r"):/root/.cache/huggingface' \
|
| 1437 |
+
-v '$(_tp4_rank_vllm "$r"):/root/.cache/vllm' \
|
| 1438 |
+
-v '$(_tp4_rank_triton "$r"):/root/.triton/cache' \
|
| 1439 |
+
-v '$(_tp4_rank_tilelang "$r"):/root/.tilelang/cache' \
|
| 1440 |
+
-v '/tmp/${cname}.sh:/start.sh:ro' \
|
| 1441 |
+
-v '/tmp/glm53-chat_template.jinja:${CHAT_TEMPLATE}:ro' \
|
| 1442 |
+
-v '/tmp/patch_glm_video_placeholders.py:/opt/glm53/patch_glm_video_placeholders.py:ro' \
|
| 1443 |
+
-v '/tmp/patch_suppress_stops_in_reasoning.py:/opt/glm53/patch_suppress_stops_in_reasoning.py:ro' \
|
| 1444 |
+
-v '/tmp/patch_scheduler_decode_floor.py:/opt/glm53/patch_scheduler_decode_floor.py:ro' \
|
| 1445 |
+
-v '/tmp/patch_glm5_drafter_group.py:/opt/glm53/patch_glm5_drafter_group.py:ro' \
|
| 1446 |
+
-v '/tmp/patch_hybrid_prefix_hit.py:/opt/glm53/patch_hybrid_prefix_hit.py:ro' \
|
| 1447 |
+
-v '/tmp/patch_xgrammar_termination.py:/opt/glm53/patch_xgrammar_termination.py:ro' \
|
| 1448 |
+
-v '/tmp/patch_kpool_tail_slotmap.py:/opt/glm53/patch_kpool_tail_slotmap.py:ro' \
|
| 1449 |
+
-v '/tmp/patch_spinwait.py:/opt/glm53/patch_spinwait.py:ro' \
|
| 1450 |
+
-v '${ABLIT_HOST_DIR:-/tmp/glm53-ablit}:/opt/glm53/ablit:ro' \
|
| 1451 |
+
-v '/tmp/glm53-ablit_runtime.py:/opt/glm53/ablit_runtime.py:ro' \
|
| 1452 |
+
-v '/tmp/patch_ablit.py:/opt/glm53/patch_ablit.py:ro' \
|
| 1453 |
+
${worker_preload} \
|
| 1454 |
+
${worker_nccl} \
|
| 1455 |
+
-e NCCL_SOCKET_IFNAME='$(_tp4_rank_cx7_if "$r")' \
|
| 1456 |
+
-e GLOO_SOCKET_IFNAME='$(_tp4_rank_cx7_if "$r")' \
|
| 1457 |
+
-e NCCL_IB_HCA='$(_tp4_rank_cx7_ib "$r")' \
|
| 1458 |
+
-e NCCL_IB_GID_INDEX='$(_tp4_rank_gid "$r")' \
|
| 1459 |
+
-e VLLM_HOST_IP='$(_tp4_rank_ip "$r")' \
|
| 1460 |
+
-e NODE_RANK='$r' \
|
| 1461 |
+
${serve_env} \
|
| 1462 |
+
--entrypoint bash '$IMAGE' /start.sh" >/dev/null
|
| 1463 |
+
done
|
| 1464 |
+
|
| 1465 |
+
log "starting head (vLLM API :${PORT}; NCCL if=${HEAD_CX7_IF} hca=${HEAD_CX7_IB}) ..."
|
| 1466 |
+
[ -n "${LOCAL_MODEL_DIR:-}" ] && head_preload+=(-v "$LOCAL_MODEL_DIR:/model:ro")
|
| 1467 |
+
[ -n "${LOCAL_DFLASH_DIR:-}" ] && head_preload+=(-v "$LOCAL_DFLASH_DIR:/draft:ro")
|
| 1468 |
+
docker run -d --name "$CONTAINER_HEAD" \
|
| 1469 |
+
--gpus all --network host --ipc=host --shm-size 32g --stop-timeout 60 \
|
| 1470 |
+
--device /dev/infiniband --cap-add IPC_LOCK \
|
| 1471 |
+
--ulimit memlock=-1 --ulimit stack=67108864 --ulimit nofile=1048576:1048576 \
|
| 1472 |
+
-v "$HF_CACHE_DIR:/root/.cache/huggingface" \
|
| 1473 |
+
-v "$CACHE_ROOT:/root/.cache/vllm" \
|
| 1474 |
+
-v "$TRITON_HOST_CACHE:/root/.triton/cache" \
|
| 1475 |
+
-v "$TILELANG_HOST_CACHE:/root/.tilelang/cache" \
|
| 1476 |
+
-v "$HEAD_SCRIPT:/start.sh:ro" \
|
| 1477 |
+
-v "$CHAT_TEMPLATE_HOST:$CHAT_TEMPLATE:ro" \
|
| 1478 |
+
-v "$VIDEO_PATCH_HOST:/opt/glm53/patch_glm_video_placeholders.py:ro" \
|
| 1479 |
+
-v "$STOP_PATCH_HOST:/opt/glm53/patch_suppress_stops_in_reasoning.py:ro" \
|
| 1480 |
+
-v "$SCHED_PATCH_HOST:/opt/glm53/patch_scheduler_decode_floor.py:ro" \
|
| 1481 |
+
-v "$DRAFTER_PATCH_HOST:/opt/glm53/patch_glm5_drafter_group.py:ro" \
|
| 1482 |
+
-v "$APC_PATCH_HOST:/opt/glm53/patch_hybrid_prefix_hit.py:ro" \
|
| 1483 |
+
-v "$XGRAMMAR_PATCH_HOST:/opt/glm53/patch_xgrammar_termination.py:ro" \
|
| 1484 |
+
-v "$KPOOL_TAIL_PATCH_HOST:/opt/glm53/patch_kpool_tail_slotmap.py:ro" \
|
| 1485 |
+
-v "$SPINWAIT_PATCH_HOST:/opt/glm53/patch_spinwait.py:ro" \
|
| 1486 |
+
-v "${ABLIT_HOST_DIR:-$SCRIPT_DIR/ablit}:/opt/glm53/ablit:ro" \
|
| 1487 |
+
-v "$SCRIPT_DIR/overlay/ablit_runtime.py:/opt/glm53/ablit_runtime.py:ro" \
|
| 1488 |
+
-v "$SCRIPT_DIR/overlay/patch_ablit.py:/opt/glm53/patch_ablit.py:ro" \
|
| 1489 |
+
"${head_preload[@]}" \
|
| 1490 |
+
"${nccl_common[@]}" \
|
| 1491 |
+
-e NCCL_SOCKET_IFNAME="$HEAD_CX7_IF" \
|
| 1492 |
+
-e GLOO_SOCKET_IFNAME="$HEAD_CX7_IF" \
|
| 1493 |
+
-e NCCL_IB_HCA="$HEAD_CX7_IB" \
|
| 1494 |
+
-e NCCL_IB_GID_INDEX="$HEAD_GID" \
|
| 1495 |
+
-e VLLM_HOST_IP="$HEAD_IP" \
|
| 1496 |
+
-e SERVED_MODEL_NAME="$SERVED_MODEL_NAME" \
|
| 1497 |
+
-e PORT="$PORT" -e TP="$TP" -e NNODES="$NNODES" \
|
| 1498 |
+
-e HEAD_IP="$HEAD_IP" -e MASTER_PORT="$MASTER_PORT" \
|
| 1499 |
+
-e QUANTIZATION="$QUANTIZATION" \
|
| 1500 |
+
-e MAX_MODEL_LEN="$MAX_MODEL_LEN" -e GPU_MEM_UTIL="$GPU_MEM_UTIL" \
|
| 1501 |
+
-e MAX_NUM_SEQS="$MAX_NUM_SEQS" \
|
| 1502 |
+
-e MAX_NUM_BATCHED_TOKENS="$MAX_NUM_BATCHED_TOKENS" \
|
| 1503 |
+
-e KV_CACHE_DTYPE="$KV_CACHE_DTYPE" -e MTP_TOKENS="$MTP_TOKENS" \
|
| 1504 |
+
-e SPEC_METHOD="$SPEC_METHOD" \
|
| 1505 |
+
-e DFLASH_TOKENS="${DFLASH_TOKENS:-7}" \
|
| 1506 |
+
-e DFLASH_MODEL_DIR="${DFLASH_MODEL_DIR:-}" \
|
| 1507 |
+
-e DFLASH_DRAFT_TP="${DFLASH_DRAFT_TP:-}" \
|
| 1508 |
+
-e LANGUAGE_MODEL_ONLY="$LANGUAGE_MODEL_ONLY" \
|
| 1509 |
+
-e SKIP_MM_PROFILING="$SKIP_MM_PROFILING" \
|
| 1510 |
+
-e LIMIT_MM="$LIMIT_MM" \
|
| 1511 |
+
-e CHAT_TEMPLATE="$CHAT_TEMPLATE" \
|
| 1512 |
+
-e ENFORCE_EAGER="$ENFORCE_EAGER" \
|
| 1513 |
+
-e EXL3_FUSED_MOE="$EXL3_FUSED_MOE" \
|
| 1514 |
+
-e EXL3_MOE_ROW_TILE="$EXL3_MOE_ROW_TILE" \
|
| 1515 |
+
-e EXL3_TEMP_ROWS_FUSED="$EXL3_TEMP_ROWS_FUSED" \
|
| 1516 |
+
-e EXL3_FAT_SORTED="$EXL3_FAT_SORTED" \
|
| 1517 |
+
-e EXL3_FAT_BATCHED="$EXL3_FAT_BATCHED" \
|
| 1518 |
+
-e EXL3_FAT_KERNEL="$EXL3_FAT_KERNEL" \
|
| 1519 |
+
-e ABLIT="$ABLIT" \
|
| 1520 |
+
-e ABLIT_METHOD="$ABLIT_METHOD" \
|
| 1521 |
+
-e ABLIT_DIRECTION="$ABLIT_DIRECTION" \
|
| 1522 |
+
-e ABLIT_LAYERS="$ABLIT_LAYERS" \
|
| 1523 |
+
-e ABLIT_ALPHA="$ABLIT_ALPHA" \
|
| 1524 |
+
-e ABLIT_INCLUDE_MTP="$ABLIT_INCLUDE_MTP" \
|
| 1525 |
+
-e MODEL_DIR="$MODEL_DIR" \
|
| 1526 |
+
-e VLLM_API_KEY="$VLLM_API_KEY" \
|
| 1527 |
+
-e EXTRA_ARGS="${EXTRA_ARGS:-}" \
|
| 1528 |
+
--entrypoint bash "$IMAGE" /start.sh >/dev/null
|
| 1529 |
+
|
| 1530 |
+
log "containers up — head=${CONTAINER_HEAD}, workers=${CONTAINER_WORKER} ${CONTAINER_WORKER2} ${CONTAINER_WORKER3}"
|
| 1531 |
+
}
|
| 1532 |
+
|
| 1533 |
+
# ---------------------------- health wait ----------------------------------
|
| 1534 |
+
wait_for_health() {
|
| 1535 |
+
local url="http://127.0.0.1:${PORT}/health"
|
| 1536 |
+
log "waiting for ${url} (weight load + warmup on a 320B MoE is slow; timeout ${READY_TIMEOUT}s) ..."
|
| 1537 |
+
log "streaming head logs live — Ctrl-C detaches, the server keeps running"
|
| 1538 |
+
|
| 1539 |
+
local logpid=""
|
| 1540 |
+
_stop_logtail() {
|
| 1541 |
+
[ -n "$logpid" ] && kill "$logpid" 2>/dev/null || true
|
| 1542 |
+
wait "$logpid" 2>/dev/null || true
|
| 1543 |
+
logpid=""
|
| 1544 |
+
}
|
| 1545 |
+
trap '_stop_logtail; warn "interrupted — containers keep running ('"'"'./start-tp4.sh logs'"'"' / '"'"'./start-tp4.sh stop'"'"')"; exit 130' INT
|
| 1546 |
+
docker logs -f --tail 0 "$CONTAINER_HEAD" 2>&1 &
|
| 1547 |
+
logpid=$!
|
| 1548 |
+
|
| 1549 |
+
local elapsed=0 healthy=0 exited=0 dead_side="" worker_fail=0
|
| 1550 |
+
while [ "$elapsed" -lt "$READY_TIMEOUT" ]; do
|
| 1551 |
+
if curl -fsS -m 5 "$url" >/dev/null 2>&1; then healthy=1; break; fi
|
| 1552 |
+
if ! docker inspect -f '{{.State.Running}}' "$CONTAINER_HEAD" 2>/dev/null | grep -q true; then
|
| 1553 |
+
log "head container exited during startup"
|
| 1554 |
+
exited=1; dead_side="head"; break
|
| 1555 |
+
fi
|
| 1556 |
+
# A dead worker rank can never make the head healthy — fail fast with
|
| 1557 |
+
# the log dump instead of polling for the full READY_TIMEOUT (issue
|
| 1558 |
+
# #22, item 4). Transient ssh/docker hiccups are tolerated; only
|
| 1559 |
+
# three consecutive non-running answers (~30 s) count as a dead
|
| 1560 |
+
# worker.
|
| 1561 |
+
local all_up=1 wr
|
| 1562 |
+
for wr in 1 2 3; do
|
| 1563 |
+
if worker_ssh_n "$wr" "docker inspect -f '{{.State.Running}}' '$(_tp4_rank_container "$wr")' 2>/dev/null" | grep -q true; then
|
| 1564 |
+
:
|
| 1565 |
+
else
|
| 1566 |
+
all_up=0
|
| 1567 |
+
dead_side="rank${wr}"
|
| 1568 |
+
fi
|
| 1569 |
+
done
|
| 1570 |
+
if [ "$all_up" = "1" ]; then
|
| 1571 |
+
worker_fail=0
|
| 1572 |
+
else
|
| 1573 |
+
worker_fail=$((worker_fail + 1))
|
| 1574 |
+
if [ "$worker_fail" -ge 3 ]; then
|
| 1575 |
+
log "a worker container is not running (${dead_side}, 3 consecutive checks)"
|
| 1576 |
+
exited=1; break
|
| 1577 |
+
fi
|
| 1578 |
+
fi
|
| 1579 |
+
sleep 10; elapsed=$((elapsed + 10))
|
| 1580 |
+
done
|
| 1581 |
+
|
| 1582 |
+
_stop_logtail
|
| 1583 |
+
trap 'warn "interrupted — containers keep running ('"'"'./start-tp4.sh logs'"'"' / '"'"'./start-tp4.sh stop'"'"')"; exit 130' INT
|
| 1584 |
+
|
| 1585 |
+
if [ "$healthy" = "1" ]; then
|
| 1586 |
+
log "health check passed after ${elapsed}s — server is up"
|
| 1587 |
+
elif [ "$exited" = "1" ]; then
|
| 1588 |
+
warn "${dead_side:-head} container exited/stopped after ${elapsed}s"
|
| 1589 |
+
else
|
| 1590 |
+
warn "timed out after ${elapsed}s without becoming healthy"
|
| 1591 |
+
fi
|
| 1592 |
+
[ "$healthy" = "1" ]
|
| 1593 |
+
}
|
| 1594 |
+
|
| 1595 |
+
post_ready_warmup() {
|
| 1596 |
+
if [ "${GLM53_BOOT_SHAPE_WARMUP:-1}" = "0" ]; then
|
| 1597 |
+
log "boot shape warmup skipped (GLM53_BOOT_SHAPE_WARMUP=0)"
|
| 1598 |
+
return 0
|
| 1599 |
+
fi
|
| 1600 |
+
[ -f "$SCRIPT_DIR/scripts/boot-shape-warmup.sh" ] \
|
| 1601 |
+
|| { warn "boot-shape-warmup.sh missing — skipping"; return 0; }
|
| 1602 |
+
log "post-ready DFlash2/sampler warmup (nonfatal; timeout ${GLM53_WARMUP_REQ_TIMEOUT}s/req) ..."
|
| 1603 |
+
GLM53_WARMUP_MAX_CONCURRENCY="$MAX_NUM_SEQS" \
|
| 1604 |
+
GLM53_WARMUP_REQ_TIMEOUT="$GLM53_WARMUP_REQ_TIMEOUT" \
|
| 1605 |
+
GLM53_WARMUP_DFLASH_K="${DFLASH_TOKENS:-7}" \
|
| 1606 |
+
GLM53_WARMUP_TRITON_CACHE_DIR="$TRITON_HOST_CACHE" \
|
| 1607 |
+
GLM53_WARMUP_BEARER="${VLLM_API_KEY:-}" \
|
| 1608 |
+
bash "$SCRIPT_DIR/scripts/boot-shape-warmup.sh" \
|
| 1609 |
+
"http://127.0.0.1:${PORT}" "$SERVED_MODEL_NAME" \
|
| 1610 |
+
|| warn "boot shape warmup incomplete — uncovered shapes may JIT mid-serve on TP=4"
|
| 1611 |
+
}
|
| 1612 |
+
|
| 1613 |
+
collect_failure_logs() {
|
| 1614 |
+
mkdir -p "$LOGDIR"
|
| 1615 |
+
docker logs "$CONTAINER_HEAD" >"$LOGDIR/head.log" 2>&1 || true
|
| 1616 |
+
local r
|
| 1617 |
+
for r in 1 2 3; do
|
| 1618 |
+
worker_ssh_n "$r" "docker logs '$(_tp4_rank_container "$r")' 2>&1" >"$LOGDIR/worker${r}.log" 2>&1 || true
|
| 1619 |
+
done
|
| 1620 |
+
}
|
| 1621 |
+
|
| 1622 |
+
on_ready() {
|
| 1623 |
+
log "======================================================================"
|
| 1624 |
+
log "GLM-5.3-Flash EXL3 is UP (TP=${TP}, nnodes=${NNODES})"
|
| 1625 |
+
log " endpoints : http://127.0.0.1:${PORT}/v1 (LAN: ${HEAD_IP}:${PORT})"
|
| 1626 |
+
log " model name : ${SERVED_MODEL_NAME}"
|
| 1627 |
+
log " weights : ${MODEL} quant=${QUANTIZATION} kv=${KV_CACHE_DTYPE}"
|
| 1628 |
+
local vision=on
|
| 1629 |
+
[ "${LANGUAGE_MODEL_ONLY}" = "1" ] && vision=off
|
| 1630 |
+
local spec="MTP k=${MTP_TOKENS}"
|
| 1631 |
+
[ "$SPEC_METHOD" = "dflash" ] && spec="DFlash2 k=${DFLASH_TOKENS} (${DFLASH_MODEL})"
|
| 1632 |
+
[ "$SPEC_METHOD" = "none" ] && spec=off
|
| 1633 |
+
local ablit="off (stock weights)"
|
| 1634 |
+
[ "$ABLIT" = "1" ] && ablit="ON method=${ABLIT_METHOD} direction=${ABLIT_DIRECTION} layers=${ABLIT_LAYERS} alpha=${ABLIT_ALPHA}"
|
| 1635 |
+
log " features : tools=glm47+auto, reasoning=glm45, spec=${spec}, vision=${vision}, ablit=${ablit}"
|
| 1636 |
+
local auth_line="none (VLLM_API_KEY empty)"
|
| 1637 |
+
if [ -n "${VLLM_API_KEY:-}" ]; then
|
| 1638 |
+
auth_line="bearer token set (VLLM_API_KEY) — send Authorization: Bearer <key> on /v1 requests"
|
| 1639 |
+
fi
|
| 1640 |
+
log " auth : ${auth_line}"
|
| 1641 |
+
log " quick test :"
|
| 1642 |
+
log " curl -s http://127.0.0.1:${PORT}/v1/chat/completions \\"
|
| 1643 |
+
if [ -n "${VLLM_API_KEY:-}" ]; then
|
| 1644 |
+
log " -H 'Authorization: Bearer <KEY>' \\"
|
| 1645 |
+
fi
|
| 1646 |
+
log " -H 'Content-Type: application/json' \\"
|
| 1647 |
+
log " -d '{\"model\": \"${SERVED_MODEL_NAME}\", \"messages\": [{\"role\": \"user\", \"content\": \"hello!\"}]}'"
|
| 1648 |
+
log " manage : ./start-tp4.sh status | ./start-tp4.sh logs | ./start-tp4.sh logs 1 | ./start-tp4.sh stop"
|
| 1649 |
+
log "======================================================================"
|
| 1650 |
+
if [ "${TAIL:-0}" = "1" ]; then
|
| 1651 |
+
log "tailing head logs — Ctrl-C just detaches, the server keeps running"
|
| 1652 |
+
trap '' INT
|
| 1653 |
+
docker logs -f --tail 20 "$CONTAINER_HEAD" || true
|
| 1654 |
+
trap 'warn "interrupted — containers keep running"; exit 130' INT
|
| 1655 |
+
fi
|
| 1656 |
+
}
|
| 1657 |
+
|
| 1658 |
+
# ------------------------------- start -------------------------------------
|
| 1659 |
+
start() {
|
| 1660 |
+
preflight
|
| 1661 |
+
ensure_image
|
| 1662 |
+
download_weights
|
| 1663 |
+
download_dflash
|
| 1664 |
+
sync_weights
|
| 1665 |
+
write_inner_scripts
|
| 1666 |
+
|
| 1667 |
+
if [ -n "${LOCAL_MODEL_DIR:-}" ]; then
|
| 1668 |
+
MODEL_DIR="/model"
|
| 1669 |
+
log "LOCAL_MODEL_DIR mode: $LOCAL_MODEL_DIR -> /model on all ranks"
|
| 1670 |
+
else
|
| 1671 |
+
MODEL_DIR="$(resolve_model_dir)"
|
| 1672 |
+
fi
|
| 1673 |
+
DFLASH_MODEL_DIR=""
|
| 1674 |
+
if [ "$SPEC_METHOD" = "dflash" ]; then
|
| 1675 |
+
if [ -n "${LOCAL_DFLASH_DIR:-}" ]; then
|
| 1676 |
+
DFLASH_MODEL_DIR="/draft"
|
| 1677 |
+
else
|
| 1678 |
+
DFLASH_MODEL_DIR="$(resolve_dflash_dir)"
|
| 1679 |
+
fi
|
| 1680 |
+
log "DFlash2 load path (in-container): ${DFLASH_MODEL_DIR}"
|
| 1681 |
+
fi
|
| 1682 |
+
log "model load path (in-container): ${MODEL_DIR}"
|
| 1683 |
+
log "config: image=${IMAGE} tp=${TP} nnodes=${NNODES} quant=${QUANTIZATION} spec=${SPEC_METHOD} mtp=${MTP_TOKENS} dflash_k=${DFLASH_TOKENS} max-len=${MAX_MODEL_LEN} gpu-util=${GPU_MEM_UTIL} kv=${KV_CACHE_DTYPE} lm-only=${LANGUAGE_MODEL_ONLY} port=${PORT}"
|
| 1684 |
+
|
| 1685 |
+
launch_cluster
|
| 1686 |
+
if wait_for_health; then
|
| 1687 |
+
post_ready_warmup
|
| 1688 |
+
on_ready
|
| 1689 |
+
return
|
| 1690 |
+
fi
|
| 1691 |
+
collect_failure_logs
|
| 1692 |
+
echo "---- last 60 lines of head log ($LOGDIR/head.log) ----"
|
| 1693 |
+
tail -n 60 "$LOGDIR/head.log" || true
|
| 1694 |
+
echo "---- last 40 lines of worker1 log ($LOGDIR/worker1.log) ----"
|
| 1695 |
+
tail -n 40 "$LOGDIR/worker1.log" || true
|
| 1696 |
+
echo "---- last 40 lines of worker2 log ($LOGDIR/worker2.log) ----"
|
| 1697 |
+
tail -n 40 "$LOGDIR/worker2.log" || true
|
| 1698 |
+
echo "---- last 40 lines of worker3 log ($LOGDIR/worker3.log) ----"
|
| 1699 |
+
tail -n 40 "$LOGDIR/worker3.log" || true
|
| 1700 |
+
die "server did not become healthy — full logs in $LOGDIR/"
|
| 1701 |
+
}
|
| 1702 |
+
|
| 1703 |
+
# ------------------------------- stop --------------------------------------
|
| 1704 |
+
stop() {
|
| 1705 |
+
local r
|
| 1706 |
+
log "stopping head container ..."
|
| 1707 |
+
docker rm -f "$CONTAINER_HEAD" >/dev/null 2>&1 || log " (no head container was running)"
|
| 1708 |
+
for r in 1 2 3; do
|
| 1709 |
+
log "stopping rank ${r} on $(_tp4_ssh_target "$r") ..."
|
| 1710 |
+
worker_ssh_n "$r" "docker rm -f '$(_tp4_rank_container "$r")'" >/dev/null 2>&1 \
|
| 1711 |
+
|| log " (no rank ${r} container was running)"
|
| 1712 |
+
done
|
| 1713 |
+
log "stopped."
|
| 1714 |
+
}
|
| 1715 |
+
|
| 1716 |
+
# ------------------------------ status -------------------------------------
|
| 1717 |
+
status() {
|
| 1718 |
+
log "head (${CONTAINER_HEAD} on $(hostname)):"
|
| 1719 |
+
docker ps -a --filter "name=${CONTAINER_HEAD}" --format ' {{.Names}} {{.Status}}' || true
|
| 1720 |
+
if curl -fsS -m 5 "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then
|
| 1721 |
+
log " API: healthy — http://127.0.0.1:${PORT}/v1"
|
| 1722 |
+
else
|
| 1723 |
+
log " API: not responding"
|
| 1724 |
+
fi
|
| 1725 |
+
local r
|
| 1726 |
+
for r in 1 2 3; do
|
| 1727 |
+
log "rank ${r} ($(_tp4_rank_container "$r") on $(_tp4_ssh_target "$r")):"
|
| 1728 |
+
worker_ssh_n "$r" "docker ps -a --filter name=$(_tp4_rank_container "$r") --format ' {{.Names}} {{.Status}}'" 2>/dev/null \
|
| 1729 |
+
|| log " (rank ${r} unreachable)"
|
| 1730 |
+
done
|
| 1731 |
+
}
|
| 1732 |
+
|
| 1733 |
+
# ------------------------------- logs --------------------------------------
|
| 1734 |
+
logs() {
|
| 1735 |
+
case "${1:-head}" in
|
| 1736 |
+
1|2|3|worker|worker1)
|
| 1737 |
+
local r="${1}"
|
| 1738 |
+
if [ "$r" = "worker" ] || [ "$r" = "worker1" ]; then r=1; fi
|
| 1739 |
+
log "following rank ${r} logs on $(_tp4_ssh_target "$r") ..."
|
| 1740 |
+
trap '' INT
|
| 1741 |
+
worker_ssh_n "$r" "docker logs -f --tail 100 '$(_tp4_rank_container "$r")'" || true
|
| 1742 |
+
trap 'warn "interrupted"; exit 130' INT
|
| 1743 |
+
;;
|
| 1744 |
+
head|*)
|
| 1745 |
+
log "following head logs (driver + API server) ..."
|
| 1746 |
+
trap '' INT
|
| 1747 |
+
docker logs -f --tail 100 "$CONTAINER_HEAD" || true
|
| 1748 |
+
trap 'warn "interrupted"; exit 130' INT
|
| 1749 |
+
;;
|
| 1750 |
+
esac
|
| 1751 |
+
}
|
| 1752 |
+
|
| 1753 |
+
# ------------------------------- main --------------------------------------
|
| 1754 |
+
main() {
|
| 1755 |
+
local cmd="${1:-start}"
|
| 1756 |
+
case "$cmd" in
|
| 1757 |
+
start|restart) validate_numeric_config ;;
|
| 1758 |
+
esac
|
| 1759 |
+
case "$cmd" in
|
| 1760 |
+
stop) banner stop.sh ;;
|
| 1761 |
+
download) banner download.sh ;;
|
| 1762 |
+
*) banner start-tp4.sh ;;
|
| 1763 |
+
esac
|
| 1764 |
+
case "$cmd" in
|
| 1765 |
+
start) shift || true; start ;;
|
| 1766 |
+
download) download_only ;;
|
| 1767 |
+
stop) stop ;;
|
| 1768 |
+
restart) stop; start ;;
|
| 1769 |
+
status) status ;;
|
| 1770 |
+
logs) shift || true; logs "$@" ;;
|
| 1771 |
+
-h|--help|help) usage ;;
|
| 1772 |
+
*) usage; exit 1 ;;
|
| 1773 |
+
esac
|
| 1774 |
+
}
|
| 1775 |
+
|
| 1776 |
+
main "$@"
|
serving/taskbench.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Task-class x concurrency decode benchmark for the AEON serve (OpenAI chat API, vLLM metrics).
|
| 3 |
+
gen tok/s = completion tokens / (wall - TTFT) per stream; aggregate = sum tokens / max wall (decode window).
|
| 4 |
+
tok/pass = delta(spec accepted)/delta(drafts) + 1 from /metrics."""
|
| 5 |
+
import json, os, sys, time, threading, urllib.request, statistics, random
|
| 6 |
+
BASE=os.environ.get("BASE","http://localhost:8000"); MODEL=os.environ.get("MODEL","aeon"); OUT=int(os.environ.get("OUT","512"))
|
| 7 |
+
THINK=os.environ.get("THINK","0")=="1"
|
| 8 |
+
TOPICS=["a lighthouse keeper in 1890s Norway","a courier robot in a flooded Bangkok","a violinist who loses her hearing","a mountain village's last winter","two brothers repairing a boat","a night-shift nurse in Lagos","a chess prodigy in exile","a beekeeper during a drought"]
|
| 9 |
+
TASKS={
|
| 10 |
+
"prose": lambda i: f"Write a vivid literary short story (at least 700 words) about {TOPICS[i%8]}. No headings, flowing prose only.",
|
| 11 |
+
"code": lambda i: f"Write a complete, well-documented Python module implementing an LRU cache with TTL expiry, thread safety, statistics, and a small CLI demo. Variant #{i}: name the class Cache{i}. Code only, no prose.",
|
| 12 |
+
"list": lambda i: f"List 120 distinct {['programming concepts','world cities','chemical compounds','historical events','musical instruments','mathematical theorems','plant species','cooking techniques'][i%8]}, numbered, each with a one-line description.",
|
| 13 |
+
"essay": lambda i: f"Write a structured argumentative essay (about 800 words, with an introduction, three sections with headers, and a conclusion) on: '{['Should cities ban private cars?','Is remote work good for innovation?','Should AI systems have a right to refuse tasks?','Is nuclear power essential for decarbonization?','Should voting be mandatory?','Do social networks make us lonelier?','Should space exploration be privatized?','Is a four-day work week viable?'][i%8]}'",
|
| 14 |
+
}
|
| 15 |
+
def metrics():
|
| 16 |
+
t=urllib.request.urlopen(BASE+"/metrics",timeout=10).read().decode(); d={}
|
| 17 |
+
for l in t.splitlines():
|
| 18 |
+
for k in ("spec_decode_num_drafts_total","spec_decode_num_accepted_tokens_total"):
|
| 19 |
+
if l.startswith("vllm:"+k+"{"): d[k]=float(l.rsplit(" ",1)[1])
|
| 20 |
+
return d
|
| 21 |
+
def one(prompt, res, idx):
|
| 22 |
+
body={"model":MODEL,"messages":[{"role":"user","content":prompt}],"max_tokens":OUT,"stream":True,
|
| 23 |
+
"stream_options":{"include_usage":True},"chat_template_kwargs":{"enable_thinking":THINK}}
|
| 24 |
+
body.update(json.loads(os.environ.get("SAMPLING","{}")))
|
| 25 |
+
req=urllib.request.Request(BASE+"/v1/chat/completions",data=json.dumps(body).encode(),headers={"Content-Type":"application/json"})
|
| 26 |
+
t0=time.time(); ttft=None; ntok=0
|
| 27 |
+
with urllib.request.urlopen(req,timeout=1800) as r:
|
| 28 |
+
for line in r:
|
| 29 |
+
line=line.decode().strip()
|
| 30 |
+
if not line.startswith("data:") or line=="data: [DONE]": continue
|
| 31 |
+
ev=json.loads(line[5:])
|
| 32 |
+
if ev.get("usage"): ntok=ev["usage"]["completion_tokens"]
|
| 33 |
+
ch=ev.get("choices")
|
| 34 |
+
d=(ch[0].get("delta") or {}) if ch else {}
|
| 35 |
+
if ttft is None and (d.get("content") or d.get("reasoning_content") or d.get("reasoning")): ttft=time.time()-t0
|
| 36 |
+
wall=time.time()-t0; res[idx]=(ntok,ttft or wall,wall)
|
| 37 |
+
def run(task,conc):
|
| 38 |
+
m0=metrics(); res=[None]*conc; th=[threading.Thread(target=one,args=(TASKS[task](i+conc*7),res,i)) for i in range(conc)]
|
| 39 |
+
T=time.time(); [t.start() for t in th]; [t.join() for t in th]; W=time.time()-T; m1=metrics()
|
| 40 |
+
toks=sum(r[0] for r in res); per=[r[0]/(r[2]-r[1]) for r in res if r[2]>r[1]] or [float("nan")]
|
| 41 |
+
g=lambda m,k: m.get(k,0.0)
|
| 42 |
+
dr=g(m1,"spec_decode_num_drafts_total")-g(m0,"spec_decode_num_drafts_total"); ac=g(m1,"spec_decode_num_accepted_tokens_total")-g(m0,"spec_decode_num_accepted_tokens_total")
|
| 43 |
+
tpp=(ac/dr+1) if dr else float("nan"); ttft=statistics.median(r[1] for r in res)
|
| 44 |
+
return dict(task=task,conc=conc,agg=toks/W,per=statistics.mean(per),ttft=ttft,tpp=tpp,tok=toks/conc)
|
| 45 |
+
if __name__=="__main__":
|
| 46 |
+
tasks=sys.argv[1].split(",") if len(sys.argv)>1 else list(TASKS); concs=[int(c) for c in (sys.argv[2] if len(sys.argv)>2 else "1,2,4,8").split(",")]
|
| 47 |
+
run("prose",1) # warm
|
| 48 |
+
print(f"thinking={'on' if THINK else 'off'} out={OUT} sampling=server default (temp 0.6/top_p .95/top_k 20/rep 1.05)")
|
| 49 |
+
print(f"{'task':6} {'C':>2} {'agg t/s':>8} {'per-stream':>10} {'TTFT':>6} {'tok/pass':>8} {'tok/req':>7}")
|
| 50 |
+
for task in tasks:
|
| 51 |
+
for c in concs:
|
| 52 |
+
r=run(task,c); print(f"{r['task']:6} {r['conc']:>2} {r['agg']:8.1f} {r['per']:10.1f} {r['ttft']:6.2f} {r['tpp']:8.2f} {r['tok']:7.0f}",flush=True)
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"backend": "tokenizers",
|
| 3 |
+
"clean_up_tokenization_spaces": false,
|
| 4 |
+
"do_lower_case": false,
|
| 5 |
+
"eos_token": "<|endoftext|>",
|
| 6 |
+
"extra_special_tokens": [
|
| 7 |
+
"<|endoftext|>",
|
| 8 |
+
"[MASK]",
|
| 9 |
+
"[gMASK]",
|
| 10 |
+
"[sMASK]",
|
| 11 |
+
"<sop>",
|
| 12 |
+
"<eop>",
|
| 13 |
+
"<|system|>",
|
| 14 |
+
"<|user|>",
|
| 15 |
+
"<|assistant|>",
|
| 16 |
+
"<|observation|>",
|
| 17 |
+
"<|begin_of_image|>",
|
| 18 |
+
"<|end_of_image|>",
|
| 19 |
+
"<|begin_of_video|>",
|
| 20 |
+
"<|end_of_video|>",
|
| 21 |
+
"<|begin_of_audio|>",
|
| 22 |
+
"<|end_of_audio|>",
|
| 23 |
+
"<|begin_of_transcription|>",
|
| 24 |
+
"<|end_of_transcription|>"
|
| 25 |
+
],
|
| 26 |
+
"is_local": true,
|
| 27 |
+
"model_max_length": 1048576,
|
| 28 |
+
"model_specific_special_tokens": {},
|
| 29 |
+
"pad_token": "<|endoftext|>",
|
| 30 |
+
"padding_side": "left",
|
| 31 |
+
"remove_space": false,
|
| 32 |
+
"tokenizer_class": "TokenizersBackend"
|
| 33 |
+
}
|