feat(pro6000/GLM-5.3): 双场景压测标准入库(bench_corpus 真实语料工具链 + run-id 窗口纪律 + 质量门禁 + 语料构建链)

- 场景一:128k/64k 输入、o512、cc1-4、90% 前缀命中(run-id 9301-9308)
- 场景二:16k 全独立输入、o512、cc8/16/32(run-id 9311-9313)
- 口径:输出吞吐取服务端 completion_tokens;命中率仅从 TP0 Prefill 日志核验;
  配置对比用 TPOT×accept;噪声带 ±8%;重跑必须 --pool-override 换新鲜窗口
- 基线(2026-09-07 真实语料)与完整纪律见实验目录 README
This commit is contained in:
yy-fighting 2026-09-08 11:37:54 +08:00
parent 9cdbc1fd22
commit f0ab17c561
14 changed files with 1090 additions and 0 deletions

View File

@ -60,6 +60,7 @@
| `experiments/h200/dsv4_h200_vllm_tp{2,4,8}_custom_bench/` | 自定义压测客户端(多服务负载均衡) |
| `experiments/h200/dsv4_h200_max_context_length/` `…/dsv4_h200_long_context_matrix/` `…/dsv4_h200_256k_4k_probe/` `…/dsv4_h200_64k_sglang_vs_vllm/` | 长上下文专项 |
| `experiments/p800/dsv4_p800_sglang/` `…/dsv4_p800_max_context_length/` `…/dsv4_p800_long_context_matrix/` `…/dsv4_p800_256k_4k_probe/` | P800 baseline 与长上下文专项 |
| `experiments/pro6000/glm53_nvfp4_pro6000d_sglang_dual_scenario_bench/` | RTX 6000D + SGLangGLM-5.3-NVFP4 双场景压测标准16k/512 高并发 + 128k/64k 90% 命中低并发;真实语料 run-id 窗口纪律 + 三套部署配置 A/B/CAR基线与口径见目录 README部署 profile 见 `deploy/profiles/pro6000/glm53_nvfp4_*` |
| `experiments/TEMPLATE/` | 老式固定场景实验模板 |
## 快速复现

View File

@ -0,0 +1,223 @@
# GLM-5.3-NVFP4 (Pro6000D×8) SGLang 双场景压测标准
- 日期2026-09-04 ~ 09-0809-07 真实语料修正版定稿 机器174.1.60.5(生产)/ 174.1.60.7(实验)
- 硬件8×RTX 6000D 96GBSM120PCIe Gen5无 NVLink 模型`/data/hf_models/GLM-5.3-NVFP4`
- 推理栈SGLang `lmsysorg/sglang:nightly-dev-20260828-daf63171`(容器入口必须 `python3 -m sglang.launch_server`,镜像 entrypoint 无 shebang
- 完整报告:飞书 wiki https://gcn673xpgdxn.feishu.cn/wiki/NzbMwzmKviYidRkZYRrc8GYQnrf revision 20真实语料版本地副本 `D:\sskj\reports\GLM53_NVFP4_双场景压测报告_2026-09-07.md`
## 目的
把 09-04~09-08 多轮压测沉淀为**可复用的双场景测试标准**:任何同事按本目录脚本与纪律执行,
即可得到与基线可比的数字。压测核心是自研 `bench_corpus.py`(真实 PG19 语料input_ids 直发
原生 `/generate`配套固定的语料窗口分配run-id、命中率日志核验、质量门禁与重跑纪律。
## 两场景定义
| | 场景一:长上下文低并发 | 场景二16k 独立输入高并发 |
|---|---|---|
| 输入 | 131072 (128k) / 65536 (64k) token | 16384 (16k) token全独立 |
| 输出 | 512 token`ignore_eos` | 同左 |
| 并发 | cc 1/2/3/4 | cc 8/16/32扩展点 cc40/64 |
| 每点请求数 | 8 | cc8→16、cc16→32、cc32→32 |
| 前缀命中 | `--shared-frac 0.9`90% 共享前缀 + 10% 唯一后缀,页 16 对齐) | `--shared-frac 0`(命中率必须为 0 |
| 实测命中率 | 128k 89.99% / 64k 89.94%(日志核验) | 0.0 |
| 负载形态 | prefill 占 10%decodeEAGLE主导 | 100% 新 token串行 chunk prefill 主导 |
| 推荐配置 | **配置 A**TP8+EAGLE 生产标准) | **配置 B**TP4PP2+IndexCache |
标准命令(= `run_s1_corpus.sh` / `run_s2_corpus.sh` 的内容):
```bash
# 场景一8 点run-id 9301-9308 与语料窗口一一绑定)
python3 scripts/bench_corpus.py --corpus /root/corpus_ids.json \
--input-len 131072 --concurrency 1 --num-requests 8 --run-id 9301 \
--shared-frac 0.9 --output-len 512 # 128k cc1其余点换 cc 与 run-id
# 场景二3 点run-id 9311-9313
python3 scripts/bench_corpus.py --corpus /root/corpus_ids.json \
--input-len 16384 --concurrency 8 --num-requests 16 --run-id 9311 \
--shared-frac 0 --output-len 512
```
`bench_hit90.py` / `bench_report.sh` / `bench_matrix.sh` 是合成随机 id 的快速对照工具:
随机 id 语义不通顺会使 EAGLE accept 虚高3.46/3.88 vs 自然文本 2.29**decode 类指标只作
参考,正式口径一律用真实语料版**。
## 测量口径(必须遵守,否则数字不可比)
1. **输出吞吐 = 服务端 `meta_info.completion_tokens` 总和 / 墙钟**。EAGLE 投机解码下客户端
流式 chunk 计数会低估一律以服务端计数为准bench_corpus.py 已按此实现)。
2. **TTFT = 客户端发出到首个流式响应**,并发 >1 时含排队;**TPOT = (首响应→末响应)/(completion_tokens1)**
并发下含交错停转,非纯 decode 步时。
3. **命中率只能从容器日志 TP0 "Prefill batch" 行核验**:正则 `#new-token: (\d+).*?#cached-token: (\d+)`
按运行窗口 `docker logs --since/--until`RFC3339结束 +2s 边距)截取,剔除每分钟 64-token
监控心跳(特征 `#new-token: 64`)。响应内 `cached_tokens` 字段恒为 0不可用。
4. **配置对比用 TPOT×accept步时**,抵消 accept 长度的内容运气(真实语料 accept 范围 2.13-2.92
吞吐/e2e 跨语料窗口噪声带 ±8%,同窗口背靠背 A/B交替顺序用于终局对比。
5. 单点验收:`ok=nreq, failed=0, retractions=0`SUMMARY 字段完整命中率偏离预期、retractions>0、
OOM 或机器有外部负载(见下)→ 停下排查,数据作废。
## 语料corpus_ids.json不入库 ~97MB
真实语料 = PG19 英文长书GCS `deepmind-gutenberg`,路径 `train/{id}.txt`)用**服役模型自己的
tokenizer** 分词得到的平坦 token 序列:`{"ids": [...21,296,780 tokens...], "books": [[start,end),...]}`
两种获取方式:
```bash
# 方式一(推荐):从 60.7 拷贝现成语料(与基线逐字节一致)
scp root@174.1.60.7:/root/corpus_ids.json /root/ # md5 a8f3909211adb92501cf007ab71d3b29
# 方式二:重建(换 tokenizer / 语料损坏时)
python3 scripts/fetch_pg19_v2.py # 下载最长 12 本书 ~85MB 到 /root/bench_corpus/books/
docker cp /root/bench_corpus glm53-nvfp4:/tmp/ # 需一个运行中的 GLM-5.3-NVFP4 容器
docker exec -i glm53-nvfp4 python3 - < scripts/tokenize_corpus.py \
> /root/corpus_ids.json 2> /root/tokenize_progress.log
```
注意:`bench_corpus.py` 的窗口布局是固定 token 偏移(见下表),语料长度必须 ≥ 2,071,040 + 余量;
换模型/分词器重建后长度不同,超界会报错(此时用 `--pool-override` 指定窗口起点)。
## run-id 窗口与重跑纪律
| 窗口 | token 区间 | 绑定 run-id | 用途 |
|---|---|---|---|
| 共享前缀 | [0, 117968) / [0, 58976) | — | 128k / 64k 场景一的 90% 共享前缀 |
| pool A | [131072, 550400) | 9301-9304 | 128k 唯一后缀(每点 8×13104 |
| pool B | [550400, 760320) | 9305-9308 | 64k 唯一后缀(每点 8×6560 |
| pool C | [760320, 2071040) | 9311-9313 | 16k 全独立输入16/32/32×16384 |
| spare | [2071040, 语料尾) | — | warmup 切片 + 重跑余量 |
**纪律**:每个 run-id 绑定唯一不重叠窗口,**复用窗口会虚高命中率 → 数据无效**。重跑某点取新鲜
文本时用 `--pool-override <起始偏移>`run-id 退化为标签),惯例起点 = 2200000 起步、每轮 +700000。
bench tag 每轮必换、绝不覆盖旧日志(日志目录默认 `/root/bench_logs`,可用 `LOG_DIR` 覆盖)。
## 部署配置(三套正式 + 一个实验补丁)
| 配置 | 脚本 | 要点 | 适用 |
|---|---|---|---|
| **ATP8+EAGLE 生产标准** | `scripts/deploy_glm53_605.sh` | TP8EAGLE 4/1/5KV fp8_e4m3 + hicache 3mem 0.90(池 276,864mrr 16chunk 8192max-prefill 16384decode 图 bs{1,2,3,4,6,8}ctx 270336parser glm45/glm47 | 场景一两类负载混跑的折中60.5 在役) |
| **A-s2场景二高并发变体** | 同上脚本 + `EXTRA` | 仅两处不同mrr 16→32、decode 图 bs{4,8,12,16}(池 276,864÷16,896=16.4 驻留上限decode 批自然 ≤16图覆盖到 bs16 即可bs24/32 图纯耗显存且会运行时 OOM | 场景二用配置 A 跑时 |
| **BTP4PP2+IndexCache** | `scripts/deploy_glm53_optimal.sh` | TP4 PP2mem 0.85(池 569,600=2.06×mrr 48chunk 16384`index_topk_freq=4`;禁 radix禁投机PP2 与投机框架不兼容);`--disable-custom-all-reduce` | **场景二最优**(吞吐 +41~79% |
| **B-s1B 的场景一形态** | `scripts/deploy_glm53_optimal_s1.sh` | 与 B 唯一差异:启用 radix cache90% 命中前提) | 场景一 A/B 对比时 |
| **CAR 补丁(实验性)** | `scripts/deploy_glm53_607_exp.sh` + `scripts/car_patch/` | E7b custom-AR 1stage小 AR≤8MBdecode AR ≤1MB走 one-shot 自定义核而非 NCCL RING_LLcc1 decode 每步 14~16%cc3-4 中性;**仅 cc1-2 验证过,未上生产** | 低并发场景一的实验优化 |
SM120 必需三项(所有配置一致):`--disable-shared-experts-fusion --moe-runner-backend
flashinfer_cutlass --disable-flashinfer-autotune`。EAGLE draft 模型自动从主权重加载,无需额外参数。
配置 B 已知缺口:脚本未带 `--tool-call-parser glm47 --reasoning-parser glm45`,质量门 6/7
tool call 失败纯属参数缺失,非模型问题);**上生产必须补 parser**。
deploy 脚本内置集群踩坑防护:`docker rm -f` 异步滞留 → 轮询等容器对象消失 + 等显存排空
<2000 MiB最长 15 分钟**不等显存归零就重部署会把新容器 KV 池压小**脚本支持环境变量
调参(`MEMFRAC/STEPS/TOPK/DRAFT/CTXLEN/CHUNK/MAXPRE/EXTRA/RESTART`),用法见脚本头注释。
## 基线数字2026-09-07真实语料60.7 干净机器)
**场景一 · 配置 A**run-id 9301-9308命中率 89.99%/89.94%0 失败 0 回撤):
| 场景 | cc | 输入 tok/s | 输出 tok/s | 单请求 decode tok/s | TTFT mean/p50 s | TPOT ms | accept |
|---|---|---|---|---|---|---|---|
| 128k | 1 | 12,581 | 49.1 | 89.8 | 4.51 / 4.51 | 11.6 | 2.84 |
| 128k | 2 | 14,622 | 57.1 | 48.7 | 5.68 / 4.54 | 22.8 | 2.45 |
| 128k | 3 | 16,972 | 66.3 | 40.4 | 7.14 / 4.52 | 29.3 | 2.55 |
| 128k | 4 | 17,625 | 68.9 | 28.8 | 9.12 / 7.64 | 37.8 | 2.32 |
| 64k | 1 | 7,090 | 55.4 | 72.3 | 1.96 / 1.96 | 14.2 | 2.22 |
| 64k | 2 | 10,236 | 80.0 | 50.1 | 2.41 / 2.01 | 20.3 | 2.13 |
| 64k | 3 | 13,803 | 107.8 | 54.3 | 3.11 / 2.03 | 19.4 | 2.92 |
| 64k | 4 | 13,927 | 108.8 | 38.2 | 4.11 / 3.76 | 27.0 | 2.31 |
**场景二 · 配置 A真实语料vs 配置 B**Arun-id 9311-9313B 无投机解码,合成/真实语料等价):
| cc | A 输出 tok/s | B 输出 tok/s | B 增益 | A 输入 tok/s | B 输入 tok/s | A TTFT mean s | B TTFT mean s | A/B TPOT ms |
|---|---|---|---|---|---|---|---|---|
| 8 | 77.1 | 108.6 | +41% | 2,468 | 3,474 | 18.8 | 12.3 | 64.5 / 49.6 |
| 16 | 95.3 | 146.2 | +53% | 3,050 | 4,678 | 20.1 | 18.9 | 123.5 / 72.7 |
| 32 | 96.2 | 171.9 | +79% | 3,078 | 5,501 | 75.6 | 35.0 | 123.7 / 117.7 |
**场景一 · A vs B 输出吞吐tok/s**A 为真实语料B 无投机解码口径等价——A 7/8 占优,
唯一例外 128k cc4B 75.9 vs A 68.9B 略优 10%cc4 聚合被四路交错 prefill 主导,换窗口复测稳定):
| 场景 | cc1 | cc2 | cc3 | cc4 |
|---|---|---|---|---|
| 128k A / B | 49.1 / 24.2 | 57.1 / 48.5 | 66.3 / 58.7 | **68.9 / 75.9** |
| 64k A / B | 55.4 / 27.8 | 80.0 / 59.4 | 107.8 / 74.0 | 108.8 / 95.2 |
**选型结论**:场景一用配置 A、场景二用配置 B混跑负载用 A 折中。共同瓶颈是 8192-chunk 串行
prefill 有效速率 ~2.8-2.9k tok/sDSA 计算 ~65% + TP8 AR over PCIe ~30%),场景一 90% 命中把
prefill 降到 10% 所以 TTFT 只有 2-4.6s;场景二该墙被 BTP4 AR + IndexCache推到 ~5.5k tok/s。
CAR 补丁E7b单独基线cc1 decode 每步 14~16%步时口径e2e 6~14%cc3-4 中性。
## 质量门禁与运维纪律
- **每次部署后、压测前**跑 `bash scripts/quality_gate_605.sh`GSM8K×5 + 中文推理鸡兔同笼 + tool
call**PASS=7 FAIL=0 方可用/可压**;实验结束后恢复生产配置并再次 7/7。
- 压测机必须干净60.2 曾因他人训练任务混跑出现 prefill 双峰,数据污染弃用——压测前确认
`nvidia-smi` 无外部进程。
- 实验后收尾:恢复生产配置 + 质量门 7/7 + (可选)清空 GPU`docker rm` 后等显存 0 MiB
- 结果日志:`/root/bench_logs/r1_run*.log`(场景一)、`r2_run*.log`(场景二);汇总表用
`python3 scripts/summarize_bench.py '/root/bench_logs/r1_run*.log'`,字段核对用 `bench_fields.py`
## 复现步骤
```bash
# 0) 语料(见上节)+ 脚本就位(本目录 = 服务器上的实验目录,或整体拷到 /root/
# 1) 部署生产配置 A 并过质量门
bash scripts/deploy_glm53_605.sh && bash scripts/quality_gate_605.sh
# 2) 场景一8 点(约 25 分钟)
bash scripts/run_s1_corpus.sh
# 3) 场景二:换高并发变体 → 3 点 → 恢复生产
MEMFRAC=0.90 EXTRA='--max-running-requests 32 --cuda-graph-max-bs-decode 16 --cuda-graph-bs-decode 4 8 12 16' \
RESTART=yes bash scripts/deploy_glm53_605.sh
bash scripts/run_s2_corpus.sh
RESTART=yes bash scripts/deploy_glm53_605.sh && bash scripts/quality_gate_605.sh
# 4) 命中率核验(以 9301 为例,窗口 = 该点起止时间 +2s 边距)
docker logs glm53-nvfp4 --since '<start RFC3339>' --until '<end RFC3339>' 2>&1 \
| grep 'Prefill batch' | grep -oP '#new-token: \d+.*?#cached-token: \d+'
# 期望:场景一 89.99%/89.94%,场景二 0.0;心跳行(#new-token: 64不计
# 5) 配置 B场景二专用
bash scripts/deploy_glm53_optimal.sh && bash scripts/quality_gate_605.sh # 预期 6/7parser 缺口)
bash scripts/run_s2_corpus.sh
```
CAR 补丁(实验性)用法:先在**运行中的原生容器**上生成补丁 `bash scripts/car_patch/prep_car_patch.sh`
docker cp 原件 + sed 注入 + py_compile 校验,产物默认 `/root/patches/`),再用
`bash scripts/deploy_glm53_607_exp.sh` 部署(默认 `CAR_PATCH=1` 自动注入后重启容器;`CAR_PATCH=0`
部署原生)。验证:容器日志出现 `SSKJ_CAR_PATCH_ACTIVE ws=8 full_nvlink=False`。补丁随容器消亡,
镜像不受影响。
## 目录与文件对照(原版 md5 = 服务器实测,仓库版即标准)
| 文件 | 60.7/集群原版 md5 | 仓库版处理 |
|---|---|---|
| `scripts/bench_corpus.py` | c6d92126ab518c5b1936841f869e4b35 | 逐字保留(压测核心) |
| `scripts/bench_hit90.py` | fd1903969fa101ca43347e6d91ba05e0 | 逐字保留 |
| `scripts/summarize_bench.py` | 1a265978538dec9eefc6cfb0e6fad281 | 逐字保留 |
| `scripts/bench_fields.py` | aba7adcd21448b8e21ba8974e5326cf6 | 逐字保留 |
| `scripts/fetch_pg19_v2.py` | 3dccd45d7fa3b556094d55f3141d7249 | 逐字保留 |
| `scripts/tokenize_corpus.py` | 266097bfa08d02b9d7d7e87e77d77b97 | 逐字保留 |
| `scripts/quality_gate_605.sh` | 0bada531dda12cb982d9b2b36695b809 | 逐字保留 |
| `scripts/deploy_glm53_605.sh` | fcd9109b2121c6aea2e913ce612fa63f全 8 台一致) | 逐字保留 |
| `scripts/deploy_glm53_optimal.sh` | 22685f56b28a54216accbe479fba86b4 | 逐字保留 |
| `scripts/deploy_glm53_optimal_s1.sh` | 6f1ee9ae5d404914f914320dd218bea4 | 逐字保留 |
| `scripts/deploy_glm53_607_exp.sh` | 21db641f1d997e26fcf1ddc97163b87e | 逐字保留 |
| `scripts/car_patch/custom_all_reduce.py` | a8fc9a504c041acc40831a848b4985d8 | 逐字保留60.7:/root/patches |
| `scripts/car_patch/custom_all_reduce_utils.py` | 65a4d22bd87ae343e7d68c4879c14f98 | 逐字保留 |
| `scripts/car_patch/prep_car_patch.sh` | 9578fe4b1f2443ccecd2cc3e0b660fb0 | 逐字保留 |
| `scripts/run_s1_corpus.sh` | 5b2534f4c2e2f5760c453abb00dc32a2 | 仅路径适配¹ |
| `scripts/run_s2_corpus.sh` | 8965a71edef6b1cf406496f0362a5d05 | 仅路径适配¹ |
| `scripts/bench_report.sh` | 282f8cdf5873d3a3eb8a950a1564acae | 仅路径适配¹ |
| `scripts/bench_matrix.sh` | b961a56bc7c8f26c519fdc7f5f627ba1 | 仅路径适配¹ |
¹ 仓库规范README 注意事项要求脚本不写绝对路径bench 脚本改按脚本所在目录解析,
语料/日志目录可用 `CORPUS`/`LOG_DIR` 环境变量覆盖,默认仍为 `/root`(服务器原布局,行为不变)。
语料 `corpus_ids.json`97MB不入库历史原始日志不入库`.gitignore` 排除 `*.log`
基线以本 README 表格 + 飞书报告为准。
## 关联
- 部署 profile`python -m sskj.deploy` 消费):`deploy/profiles/pro6000/glm53_nvfp4_pro6000_sglang_tp8eagle.env`
`deploy/profiles/pro6000/glm53_nvfp4_pro6000_sglang_tp4pp2_indexcache.env`
- 前序实验:`experiments/pro6000/glm53_nvfp4_pro6000d_sglang_tp4pp2_profile/`profile 与配置级证伪)、
`experiments/pro6000/glm53_nvfp4_pro6000d_sglang_ppmtp_deepdive/`PP+MTP 深挖,均在 hzy 分支)
- 场景一深度优化报告E7b CAR 补丁出处):`D:\sskj\reports\GLM53_NVFP4_60.7_场景一优化实验报告_2026-09-07.md`

View File

@ -0,0 +1,26 @@
# GLM-5.3-NVFP4 双场景压测标准实验配置174.1.60.5/60.7, 定稿 2026-09-07 真实语料版)
HOST=174.1.60.5 # 生产在役机(实验用 60.7,压测前须确认机器干净)
MODEL=/data/hf_models/GLM-5.3-NVFP4
IMAGE=lmsysorg/sglang:nightly-dev-20260828-daf63171
CONTAINER=glm53-nvfp4
PORT=30000
# ---- 部署配置(详见 README「部署配置」脚本支持环境变量调参----
DEPLOY_A="bash scripts/deploy_glm53_605.sh" # 生产标准TP8 EAGLE 4/1/5 fp8KV hicache3 mem0.90 mrr16 chunk8192
DEPLOY_A_S2="MEMFRAC=0.90 EXTRA='--max-running-requests 32 --cuda-graph-max-bs-decode 16 --cuda-graph-bs-decode 4 8 12 16' RESTART=yes bash scripts/deploy_glm53_605.sh"
DEPLOY_B="bash scripts/deploy_glm53_optimal.sh" # TP4PP2+IndexCache freq4 mem0.85 mrr48 chunk16384 radix-off 无投机
DEPLOY_B_S1="bash scripts/deploy_glm53_optimal_s1.sh" # B 的场景一形态:唯一差异 radix-on
DEPLOY_CAR="bash scripts/deploy_glm53_607_exp.sh" # A + CAR 1stage 补丁注入CAR_PATCH=0 关闭)— 实验性,仅 cc1-2 验证
# ---- 场景矩阵run-id 与语料窗口一一绑定,严禁复用)----
SCENARIO_1="{128k,64k} x cc{1,2,3,4}, nreq=8, shared-frac 0.9, run-id 9301-9308runner: run_s1_corpus.sh"
SCENARIO_2="16k 全独立, cc{8,16,32}, nreq{16,32,32}, shared-frac 0, run-id 9311-9313runner: run_s2_corpus.sh"
SCENARIO_EXT="cc40/cc64 扩展点nreq=cc须用 --pool-override 取新鲜窗口≥2200000每轮 +700000"
# ---- 语料 ----
CORPUS=/root/corpus_ids.json # PG19 21,296,780 token60.7 现成 md5 a8f3909211adb92501cf007ab71d3b29重建见 README
CORPUS_BUILD="python3 scripts/fetch_pg19_v2.py && docker exec -i glm53-nvfp4 python3 - < scripts/tokenize_corpus.py > /root/corpus_ids.json"
# ---- 口径与门禁(摘要,全文见 README「测量口径」----
METRICS="输出吞吐=服务端 completion_tokens/墙钟;命中率仅从 TP0 Prefill 日志核验;配置对比用 TPOT×accept噪声带 ±8%"
QUALITY_GATE="bash scripts/quality_gate_605.sh → PASS=7 FAIL=0配置 B 预期 6/7缺 --tool-call-parser glm47"

View File

@ -0,0 +1,298 @@
#!/usr/bin/env python3
"""Real-corpus (PG19) benchmark for sglang GLM-5.3-NVFP4.
Same methodology as bench_hit90.py (input_ids direct to /generate, temp 0,
ignore_eos, stream, server-side completion_tokens counting, hit-rate verified
from scheduler logs), but prompts are token slices of REAL book text tokenized
with the served model's own tokenizer, replacing random ids.
Corpus file: JSON {"ids": [flat token ids], "books": [[start, end), ...]}
Fixed token-offset layout into the flat id array:
[0, 117968) shared prefix for 128k points (90% of 131072)
[0, 58976) shared prefix for 64k points (same region, shorter cut)
pool A [131072, +4*8*13104) 128k unique suffixes, run-ids 9301-9304
pool B [550400, +4*8*6560) 64k unique suffixes, run-ids 9305-9308
pool C [760320, 262144+524288+524288) 16k fully-unique prompts, run-ids 9311-9313
spare [2071040, end) warmup slices / re-run margin
Each run-id maps to one non-overlapping window (one window = one bench point);
re-running a point with fresh text = bump --pool-override past the spare base.
Usage:
python3 bench_corpus.py --corpus /root/corpus_ids.json --input-len 131072 \
--concurrency 1 --num-requests 8 --run-id 9301 --shared-frac 0.9
"""
import argparse
import datetime
import json
import re
import statistics
import subprocess
import time
from concurrent.futures import ThreadPoolExecutor
import requests
OUTPUT_LEN_DEFAULT = 512
CORPUS_DEFAULT = "/root/corpus_ids.json"
# fixed pool layout (see docstring)
S1_128K_RID0, S1_64K_RID0, S2_RID0 = 9301, 9305, 9311
POOL_A_BASE, POOL_A_PER = 131072, 8 * 13104 # 128k suffix windows
POOL_B_BASE = POOL_A_BASE + 4 * POOL_A_PER # 550400
POOL_B_PER = 8 * 6560 # 64k suffix windows
POOL_C_BASE = POOL_B_BASE + 4 * POOL_B_PER # 760320
POOL_C_SIZES = [16 * 16384, 32 * 16384, 32 * 16384] # cc8 / cc16 / cc32
SPARE_BASE = POOL_C_BASE + sum(POOL_C_SIZES) # 2071040
sess = requests.Session()
sess.trust_env = False # bypass any proxy env on the host
def split_lens(input_len, shared_frac):
# unique suffix = (1 - shared_frac) of the prompt, page-16 aligned
# (128k @0.9 -> 13104 unique; 16k @0.0 -> fully unique prompts)
unique = round(input_len * (1.0 - shared_frac) / 16) * 16
return input_len - unique, unique
def pool_start_for(input_len, shared_frac, run_id, override):
if override is not None:
return override
if shared_frac > 0:
if input_len == 131072:
idx = run_id - S1_128K_RID0
if not 0 <= idx < 4:
sys_exit_bad_runid(run_id, "128k points use run-ids 9301-9304")
return POOL_A_BASE + idx * POOL_A_PER
if input_len == 65536:
idx = run_id - S1_64K_RID0
if not 0 <= idx < 4:
sys_exit_bad_runid(run_id, "64k points use run-ids 9305-9308")
return POOL_B_BASE + idx * POOL_B_PER
sys_exit_bad_runid(run_id, "shared-frac>0 supports 131072/65536 only")
idx = run_id - S2_RID0
if not 0 <= idx < 3:
sys_exit_bad_runid(run_id, "16k unique points use run-ids 9311-9313")
return POOL_C_BASE + sum(POOL_C_SIZES[:idx])
def sys_exit_bad_runid(run_id, msg):
raise SystemExit(f"[pool] run-id {run_id} outside expected set: {msg}")
def build_prompts(ids, shared_len, unique_len, num_requests, pool_start):
if shared_len:
shared = ids[0:shared_len]
else:
shared = []
end = pool_start + num_requests * unique_len
if end > len(ids):
raise SystemExit(
f"[pool] window [{pool_start}, {end}) exceeds corpus ({len(ids)} ids); "
f"use --pool-override or a larger corpus")
prompts = []
for i in range(num_requests):
s = pool_start + i * unique_len
prompts.append(shared + ids[s:s + unique_len])
return shared, prompts, (pool_start, end)
def warmup(url, ids, shared_len):
# primes the radix cache with the shared prefix (same role as in bench_hit90);
# warm slice comes from the spare region so it never collides with a pool window
if len(ids) >= SPARE_BASE + 64:
warm_slice = ids[SPARE_BASE:SPARE_BASE + 64]
else:
warm_slice = ids[-64:]
payload = {
"input_ids": ids[0:shared_len] + warm_slice if shared_len else warm_slice,
"sampling_params": {"max_new_tokens": 8, "temperature": 0.0, "ignore_eos": True},
}
t0 = time.perf_counter()
r = sess.post(url, json=payload, timeout=1800)
dt = time.perf_counter() - t0
print(f"[warmup] http={r.status_code} wall={dt:.2f}s", flush=True)
def bench_one(url, prompt, output_len, idx, results):
payload = {
"input_ids": prompt,
"sampling_params": {"max_new_tokens": output_len, "temperature": 0.0, "ignore_eos": True},
"stream": True,
}
rec = {"idx": idx}
t0 = time.perf_counter()
first = last = None
first_ct = None
final_meta = None
max_ct = 0
try:
with sess.post(url, json=payload, stream=True, timeout=3600) as resp:
for raw in resp.iter_lines():
if not raw or not raw.startswith(b"data:"):
continue
body = raw[5:].strip()
if body == b"[DONE]":
continue
now = time.perf_counter()
try:
d = json.loads(body)
except Exception:
continue
mi = d.get("meta_info") or {}
ct = mi.get("completion_tokens") or 0
if ct:
max_ct = max(max_ct, ct)
if first is None:
first = now
first_ct = ct
last = now
if mi.get("finish_reason"):
final_meta = mi
t_end = time.perf_counter()
n_out = max_ct or ((final_meta or {}).get("completion_tokens") or 0)
decode_span = (last - first) if (first and last and last > first) else 0.0
rec.update(
ok=n_out > 0,
ttft=(first - t0) if first else None,
e2e=t_end - t0,
n_out=n_out,
first_chunk_tokens=first_ct,
decode_span=decode_span,
tpot=(decode_span / (n_out - 1)) if n_out > 1 else None,
per_req_decode_tok_s=(n_out / decode_span) if decode_span > 0 else None,
retractions=(final_meta or {}).get("num_retractions"),
spec_accept_len=(final_meta or {}).get("spec_accept_length"),
)
except Exception as e:
rec.update(ok=False, error=repr(e))
results[idx] = rec
def verify_hit_rate(container, t_start, t_end):
def rfc3339(epoch):
return (datetime.datetime.fromtimestamp(epoch, tz=datetime.timezone.utc)
.isoformat().replace("+00:00", "Z"))
try:
# No margin before t_start: warmup's prefill lines end strictly before it,
# and catching them would deflate the measured hit rate.
p = subprocess.run(
["docker", "logs", container, "--since", rfc3339(t_start), "--until", rfc3339(t_end + 2)],
capture_output=True, text=True, timeout=120)
text = p.stdout + p.stderr
except Exception as e:
return {"error": repr(e)}
pat = re.compile(r"#new-token: (\d+).*?#cached-token: (\d+)")
n_batches = new_tok = cached_tok = 0
for line in text.splitlines():
if "TP0]" not in line or "Prefill batch" not in line:
continue
m = pat.search(line)
if m:
n_batches += 1
new_tok += int(m.group(1))
cached_tok += int(m.group(2))
total = new_tok + cached_tok
return {
"prefill_batches": n_batches,
"new_tokens": new_tok,
"cached_tokens": cached_tok,
"hit_rate": round(cached_tok / total, 4) if total else None,
}
def stats(vals):
vals = [v for v in vals if v is not None]
if not vals:
return {"mean": None, "p50": None, "max": None, "min": None}
s = sorted(vals)
return {
"mean": round(statistics.fmean(vals), 4),
"p50": round(s[len(s) // 2], 4),
"max": round(s[-1], 4),
"min": round(s[0], 4),
}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--concurrency", type=int, required=True)
ap.add_argument("--num-requests", type=int, required=True)
ap.add_argument("--run-id", type=int, required=True)
ap.add_argument("--input-len", type=int, required=True, help="16384 / 65536 / 131072")
ap.add_argument("--output-len", type=int, default=OUTPUT_LEN_DEFAULT)
ap.add_argument("--shared-frac", type=float, default=0.9)
ap.add_argument("--corpus", default=CORPUS_DEFAULT)
ap.add_argument("--pool-override", type=int, default=None,
help="explicit corpus offset for the unique-suffix window (re-runs)")
ap.add_argument("--url", default="http://127.0.0.1:30000/generate")
ap.add_argument("--container", default="glm53-nvfp4")
args = ap.parse_args()
with open(args.corpus) as f:
corpus = json.load(f)
ids = corpus["ids"]
shared_len, unique_len = split_lens(args.input_len, args.shared_frac)
pool_start = pool_start_for(args.input_len, args.shared_frac, args.run_id, args.pool_override)
shared, prompts, window = build_prompts(ids, shared_len, unique_len, args.num_requests, pool_start)
print(f"[pool] window={window} shared_len={shared_len} unique_len={unique_len} "
f"corpus_total={len(ids)}", flush=True)
warmup(args.url, ids, shared_len)
results = {}
t_start = time.time()
t0 = time.perf_counter()
with ThreadPoolExecutor(max_workers=args.concurrency) as ex:
futs = [ex.submit(bench_one, args.url, p, args.output_len, i, results)
for i, p in enumerate(prompts)]
for f in futs:
f.result()
wall = time.perf_counter() - t0
t_end = time.time()
hit = verify_hit_rate(args.container, t_start, t_end)
ok = [r for r in results.values() if r.get("ok")]
n_out_total = sum(r["n_out"] for r in ok)
out_tps = [r["n_out"] / r["e2e"] for r in ok if r.get("e2e")]
ttft = stats([r.get("ttft") for r in ok])
tpot = stats([r.get("tpot") for r in ok])
e2e = stats([r.get("e2e") for r in ok])
dec = stats([r.get("per_req_decode_tok_s") for r in ok])
spec = [v for v in (r.get("spec_accept_len") for r in ok) if v is not None]
retr = sum(r.get("retractions") or 0 for r in ok)
summary = {
"concurrency": args.concurrency,
"num_requests": args.num_requests,
"run_id": args.run_id,
"corpus_window": {"start": window[0], "end": window[1]},
"ok": len(ok),
"failed": args.num_requests - len(ok),
"wall_s": round(wall, 2),
"input_len": args.input_len,
"shared_len": shared_len,
"unique_len": unique_len,
"output_len": args.output_len,
"output_tokens_total": n_out_total,
"output_throughput_tok_s": round(n_out_total / wall, 2) if wall else None,
"input_throughput_tok_s": round(args.input_len * len(ok) / wall, 2) if wall else None,
"ttft_s": ttft,
"tpot_s": tpot,
"e2e_s": e2e,
"per_req_out_tok_s_e2e": stats(out_tps),
"per_req_decode_tok_s": dec,
"spec_accept_length_mean": round(statistics.fmean(spec), 3) if spec else None,
"retractions_total": retr,
"cache_hit_from_logs": hit,
}
print("\n===== SUMMARY =====")
print(json.dumps(summary, indent=2), flush=True)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
"""Print full stats fields from bench logs to pin down conventions."""
import glob
import json
import sys
pat = sys.argv[1]
for f in sorted(glob.glob(pat)):
txt = open(f).read()
if "===== SUMMARY =====" not in txt:
continue
d = json.loads(txt.split("===== SUMMARY =====")[1])
print(
f"{f.split('/')[-1]} run={d['run_id']} il={d['input_len']} cc={d['concurrency']} "
f"dec_mean={d['per_req_decode_tok_s']['mean']} dec_p50={d['per_req_decode_tok_s']['p50']} "
f"tpot_mean={d['tpot_s']['mean']} tpot_p50={d['tpot_s']['p50']} "
f"ttft_mean={d['ttft_s']['mean']} ttft_p50={d['ttft_s']['p50']} ttft_max={d['ttft_s']['max']} "
f"e2e_mean={d['e2e_s']['mean']} accept={d['spec_accept_length_mean']}"
)

View File

@ -0,0 +1,236 @@
#!/usr/bin/env python3
"""Parameterized cache-hit benchmark for sglang GLM-5.3-NVFP4.
Scenario: N-token prompts (default 128k), --shared-frac of each prompt is a
shared prefix (radix/hicache hit, default 0.9), the rest is a unique
per-request suffix. --shared-frac 0 = fully unique prompts (no prefix reuse).
Measures TTFT / TPOT / output throughput at a given client concurrency.
Hit rate is verified from scheduler logs (docker) for the exact run window
("#new-token / #cached-token" per prefill batch).
Usage:
python3 bench_hit90.py --concurrency 1 --num-requests 4 --run-id 101
python3 bench_hit90.py --input-len 65536 --concurrency 2 --num-requests 4 --run-id 102
python3 bench_hit90.py --input-len 16384 --shared-frac 0 --concurrency 16 --num-requests 32 --run-id 103
"""
import argparse
import datetime
import json
import random
import re
import statistics
import subprocess
import time
from concurrent.futures import ThreadPoolExecutor
import requests
SEED = 20260904
OUTPUT_LEN_DEFAULT = 512
VOCAB_LO, VOCAB_HI = 1000, 100000
sess = requests.Session()
sess.trust_env = False # bypass any proxy env on the host
def split_lens(input_len, shared_frac):
# unique suffix = (1 - shared_frac) of the prompt, page-16 aligned
# (128k @0.9 -> 13104 unique; 16k @0.0 -> fully unique prompts)
unique = round(input_len * (1.0 - shared_frac) / 16) * 16
return input_len - unique, unique
def build_prompts(shared_len, unique_len, num_requests, run_id):
rng = random.Random(SEED)
shared = [rng.randint(VOCAB_LO, VOCAB_HI) for _ in range(shared_len)]
prompts = []
for i in range(num_requests):
# Suffix seed includes run_id so each run generates fresh suffixes;
# reusing suffixes from an earlier run would inflate the cache hit rate.
r = random.Random(SEED * 100003 + run_id * 1000003 + i)
prompts.append(shared + [r.randint(VOCAB_LO, VOCAB_HI) for _ in range(unique_len)])
return shared, prompts
def warmup(url, shared):
suffix = [random.Random(777).randint(VOCAB_LO, VOCAB_HI) for _ in range(64)]
payload = {
"input_ids": shared + suffix,
"sampling_params": {"max_new_tokens": 8, "temperature": 0.0, "ignore_eos": True},
}
t0 = time.perf_counter()
r = sess.post(url, json=payload, timeout=1800)
dt = time.perf_counter() - t0
print(f"[warmup] http={r.status_code} wall={dt:.2f}s", flush=True)
def bench_one(url, prompt, output_len, idx, results):
payload = {
"input_ids": prompt,
"sampling_params": {"max_new_tokens": output_len, "temperature": 0.0, "ignore_eos": True},
"stream": True,
}
rec = {"idx": idx}
t0 = time.perf_counter()
first = last = None
first_ct = None
final_meta = None
max_ct = 0
try:
with sess.post(url, json=payload, stream=True, timeout=3600) as resp:
for raw in resp.iter_lines():
if not raw or not raw.startswith(b"data:"):
continue
body = raw[5:].strip()
if body == b"[DONE]":
continue
now = time.perf_counter()
try:
d = json.loads(body)
except Exception:
continue
mi = d.get("meta_info") or {}
ct = mi.get("completion_tokens") or 0
if ct:
max_ct = max(max_ct, ct)
if first is None:
first = now
first_ct = ct
last = now
if mi.get("finish_reason"):
final_meta = mi
t_end = time.perf_counter()
n_out = max_ct or ((final_meta or {}).get("completion_tokens") or 0)
decode_span = (last - first) if (first and last and last > first) else 0.0
rec.update(
ok=n_out > 0,
ttft=(first - t0) if first else None,
e2e=t_end - t0,
n_out=n_out,
first_chunk_tokens=first_ct,
decode_span=decode_span,
tpot=(decode_span / (n_out - 1)) if n_out > 1 else None,
per_req_decode_tok_s=(n_out / decode_span) if decode_span > 0 else None,
retractions=(final_meta or {}).get("num_retractions"),
spec_accept_len=(final_meta or {}).get("spec_accept_length"),
)
except Exception as e:
rec.update(ok=False, error=repr(e))
results[idx] = rec
def verify_hit_rate(container, t_start, t_end):
def rfc3339(epoch):
return (datetime.datetime.fromtimestamp(epoch, tz=datetime.timezone.utc)
.isoformat().replace("+00:00", "Z"))
try:
# No margin before t_start: warmup's prefill lines end strictly before it,
# and catching them would deflate the measured hit rate.
p = subprocess.run(
["docker", "logs", container, "--since", rfc3339(t_start), "--until", rfc3339(t_end + 2)],
capture_output=True, text=True, timeout=120)
text = p.stdout + p.stderr
except Exception as e:
return {"error": repr(e)}
pat = re.compile(r"#new-token: (\d+).*?#cached-token: (\d+)")
n_batches = new_tok = cached_tok = 0
for line in text.splitlines():
if "TP0]" not in line or "Prefill batch" not in line:
continue
m = pat.search(line)
if m:
n_batches += 1
new_tok += int(m.group(1))
cached_tok += int(m.group(2))
total = new_tok + cached_tok
return {
"prefill_batches": n_batches,
"new_tokens": new_tok,
"cached_tokens": cached_tok,
"hit_rate": round(cached_tok / total, 4) if total else None,
}
def stats(vals):
vals = [v for v in vals if v is not None]
if not vals:
return {"mean": None, "p50": None, "max": None, "min": None}
s = sorted(vals)
return {
"mean": round(statistics.fmean(vals), 4),
"p50": round(s[len(s) // 2], 4),
"max": round(s[-1], 4),
"min": round(s[0], 4),
}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--concurrency", type=int, required=True)
ap.add_argument("--num-requests", type=int, required=True)
ap.add_argument("--run-id", type=int, default=0,
help="unique id per run so suffix prompts are never reused across runs")
ap.add_argument("--input-len", type=int, default=131072, help="total prompt tokens (16k=16384, 64k=65536, 128k=131072)")
ap.add_argument("--output-len", type=int, default=OUTPUT_LEN_DEFAULT)
ap.add_argument("--shared-frac", type=float, default=0.9,
help="fraction of each prompt that is a shared prefix (radix hit); 0 = fully unique prompts")
ap.add_argument("--url", default="http://127.0.0.1:30000/generate")
ap.add_argument("--container", default="glm53-nvfp4")
args = ap.parse_args()
shared_len, unique_len = split_lens(args.input_len, args.shared_frac)
shared, prompts = build_prompts(shared_len, unique_len, args.num_requests, args.run_id)
warmup(args.url, shared)
results = {}
t_start = time.time()
t0 = time.perf_counter()
with ThreadPoolExecutor(max_workers=args.concurrency) as ex:
futs = [ex.submit(bench_one, args.url, p, args.output_len, i, results) for i, p in enumerate(prompts)]
for f in futs:
f.result()
wall = time.perf_counter() - t0
t_end = time.time()
hit = verify_hit_rate(args.container, t_start, t_end)
ok = [r for r in results.values() if r.get("ok")]
n_out_total = sum(r["n_out"] for r in ok)
out_tps = [r["n_out"] / r["e2e"] for r in ok if r.get("e2e")]
ttft = stats([r.get("ttft") for r in ok])
tpot = stats([r.get("tpot") for r in ok])
e2e = stats([r.get("e2e") for r in ok])
dec = stats([r.get("per_req_decode_tok_s") for r in ok])
spec = [v for v in (r.get("spec_accept_len") for r in ok) if v is not None]
retr = sum(r.get("retractions") or 0 for r in ok)
summary = {
"concurrency": args.concurrency,
"num_requests": args.num_requests,
"run_id": args.run_id,
"ok": len(ok),
"failed": args.num_requests - len(ok),
"wall_s": round(wall, 2),
"input_len": args.input_len,
"shared_len": shared_len,
"unique_len": unique_len,
"output_len": args.output_len,
"output_tokens_total": n_out_total,
"output_throughput_tok_s": round(n_out_total / wall, 2) if wall else None,
"input_throughput_tok_s": round(args.input_len * len(ok) / wall, 2) if wall else None,
"ttft_s": ttft,
"tpot_s": tpot,
"e2e_s": e2e,
"per_req_out_tok_s_e2e": stats(out_tps),
"per_req_decode_tok_s": dec,
"spec_accept_length_mean": round(statistics.fmean(spec), 3) if spec else None,
"retractions_total": retr,
"cache_hit_from_logs": hit,
}
print("\n===== SUMMARY =====")
print(json.dumps(summary, indent=2), flush=True)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,26 @@
#!/bin/bash
# Usage: bench_matrix.sh <tag> <runid_base> [url]
# Runs the 4-scenario matrix serially in background-safe fashion:
# 128k/90%hit x cc1, cc2 ; 64k/90%hit x cc1, cc2 (NUMREQ requests each, default 4)
#
# Repo 版与 60.7 原版md5 b961a56bc7c8f26c519fdc7f5f627ba1唯一差异
# bench 脚本按本目录解析,日志目录可用 LOG_DIR 覆盖(默认 /root/bench_logs
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="${LOG_DIR:-/root/bench_logs}"
TAG=$1
RID=$2
URL=${3:-http://127.0.0.1:30000/generate}
NUMREQ=${NUMREQ:-4}
mkdir -p "$LOG_DIR"
for spec in "131072 1" "131072 2" "65536 1" "65536 2"; do
set -- $spec
IL=$1; CC=$2
if [ "$IL" -eq 131072 ]; then K=128k; else K=64k; fi
LOG=$LOG_DIR/${TAG}_${K}_cc${CC}.log
echo "[$(date +%T)] START ${TAG} ${K} cc${CC} run-id ${RID} numreq ${NUMREQ}"
python3 "${SCRIPT_DIR}/bench_hit90.py" --input-len $IL --concurrency $CC --num-requests $NUMREQ \
--run-id $RID --url $URL > "$LOG" 2>&1
RID=$((RID+1))
echo "[$(date +%T)] DONE ${TAG} ${K} cc${CC} (exit $?)"
done
echo ALL_DONE

View File

@ -0,0 +1,20 @@
#!/bin/bash
# One bench point for the two-scenario report (synthetic random-id variant,
# bench_hit90.py — 仅作快速对照用,正式口径以 bench_corpus.py 真实语料为准).
# Usage: bench_report.sh <tag> <input_len> <cc> <numreq> <run_id> [shared_frac]
# Log: ${LOG_DIR}/<tag>_i<LEN>k_cc<CC>.log
#
# Repo 版与 60.7 原版md5 282f8cdf5873d3a3eb8a950a1564acae唯一差异
# bench 脚本按本目录解析,日志目录可用 LOG_DIR 覆盖(默认 /root/bench_logs
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="${LOG_DIR:-/root/bench_logs}"
TAG=$1; ILEN=$2; CC=$3; NREQ=$4; RID=$5; SF=${6:-0.9}
mkdir -p "$LOG_DIR"
LOG=$LOG_DIR/${TAG}_i$((ILEN/1024))k_cc${CC}.log
echo "[$(date +%H:%M:%S)] START $TAG i$ILEN cc$CC nreq$NREQ run$RID sf$SF"
python3 "${SCRIPT_DIR}/bench_hit90.py" --concurrency "$CC" --num-requests "$NREQ" \
--run-id "$RID" --input-len "$ILEN" --output-len 512 --shared-frac "$SF" \
> "$LOG" 2>&1
RC=$?
echo "[$(date +%H:%M:%S)] DONE $TAG i$ILEN cc$CC run$RID exit=$RC"
exit $RC

View File

@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""PG19 corpus fetcher v2: list the GCS bucket (correct path = train/{id}.txt),
pick the largest books (skipping >20MB multi-volume tomes) until ~80MB of text,
download in parallel."""
import os
import re
import sys
from concurrent.futures import ThreadPoolExecutor
import requests
BASE = "https://storage.googleapis.com/deepmind-gutenberg"
D = "/root/bench_corpus"
TARGET_TOTAL = 80_000_000
MAX_BOOK = 20_000_000
MAX_BOOKS = 80
s = requests.Session()
entries = []
token = None
while True:
params = {"list-type": "2", "prefix": "train/", "max-keys": "1000"}
if token:
params["continuation-token"] = token
r = s.get(BASE, params=params, timeout=30)
txt = r.text
for m in re.finditer(r"<Contents><Key>([^<]+)</Key>.*?<Size>(\d+)</Size>", txt, re.DOTALL):
entries.append((m.group(1), int(m.group(2)))
)
if "<IsTruncated>true</IsTruncated>" not in txt:
break
mt = re.search(r"<NextContinuationToken>([^<]+)</NextContinuationToken>", txt)
if not mt:
break
token = mt.group(1)
print(f"[list] {len(entries)} objects so far", flush=True)
entries.sort(key=lambda x: -x[1])
selected = []
total = 0
for key, size in entries:
if size > MAX_BOOK or size < 500_000:
continue
selected.append((key, size))
total += size
if total >= TARGET_TOTAL or len(selected) >= MAX_BOOKS:
break
print(f"[select] {len(selected)} books, {total/1e6:.1f} MB", flush=True)
os.makedirs(f"{D}/books", exist_ok=True)
def dl(ks):
key, size = ks
fn = f"{D}/books/{key.split('/')[-1]}"
r = requests.get(f"{BASE}/{key}", timeout=300)
with open(fn, "wb") as f:
f.write(r.content)
return key, len(r.content)
with ThreadPoolExecutor(max_workers=8) as ex:
for key, got in ex.map(dl, selected):
print(f"[dl] {key} {got}", flush=True)
real = sum(os.path.getsize(f"{D}/books/{f}") for f in os.listdir(f"{D}/books"))
print(f"[done] files={len(os.listdir(f'{D}/books'))} total_bytes={real}", flush=True)

View File

@ -0,0 +1,71 @@
#!/bin/bash
# /data/hf_models/GLM-5.3-NVFP4 质量门: GSM8K 5题 + 中文推理 + tool call
# 用法: bash quality_gate_flash.sh (需服务已在 127.0.0.1:30000 就绪)
PORT=30000
BASE=http://127.0.0.1:$PORT
PASS=0; FAIL=0
ask() { # $1=question $2=expect_number(optional) $3=label
local q="$1" expect="$2" label="$3"
local out
out=$(curl -s -m 300 $BASE/v1/chat/completions -H 'Content-Type: application/json' -d "{
\"model\": \"/data/hf_models/GLM-5.3-NVFP4\",
\"messages\": [{\"role\": \"user\", \"content\": $(python3 -c "import json,sys; print(json.dumps(sys.argv[1]))" "$q")}],
\"temperature\": 0, \"max_tokens\": 2048
}")
local content finish
content=$(echo "$out" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['choices'][0]['message'].get('content') or '')" 2>/dev/null)
finish=$(echo "$out" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['choices'][0].get('finish_reason'))" 2>/dev/null)
if [ -z "$content" ] && [ "$finish" != "tool_calls" ]; then
echo "[FAIL] $label: 无输出 finish=$finish"; FAIL=$((FAIL+1)); return
fi
if [ -n "$expect" ]; then
if echo "$content" | grep -qE "(^|[^0-9])$expect([^0-9]|$)"; then
echo "[PASS] $label (含 $expect)"; PASS=$((PASS+1))
else
echo "[FAIL] $label: 期望含 $expect, 实际输出尾部: $(echo "$content" | tail -c 300)"; FAIL=$((FAIL+1))
fi
else
echo "[INFO] $label: $(echo "$content" | head -c 200)"
fi
}
echo "===== GSM8K x5 (temperature=0) ====="
ask "Natalia sold clips to 48 of her friends in April, and then she sold half as many clips in May. How many clips did Natalia sell altogether in April and May?" "72" "GSM8K-1"
ask "A robe takes 2 bolts of blue fiber and half that much white fiber. How many bolts in total does it take?" "3" "GSM8K-2"
ask "Betty has half the money that Linda has. If Linda has 120 dollars, how many dollars does Betty have?" "60" "GSM8K-3"
ask "A waiter had 9 tables he was waiting on, with 7 women and 3 men at each table. After an hour, all the men left. How many customers remained?" "63" "GSM8K-4"
ask "Weng earns 12 dollars per hour babysitting. Yesterday she babysat for 50 minutes. How much did she earn?" "10" "GSM8K-5"
echo "===== 中文推理 (鸡兔同笼) ====="
ask "一个笼子里有鸡和兔从上面数有35个头从下面数有94只脚。请问鸡和兔各有几只请给出推理过程。" "23" "中文推理-鸡数(兔应为12)"
echo "(兔数检查: 上面输出应同时含 12)"
echo "===== Tool Call ====="
tc_out=$(curl -s -m 120 $BASE/v1/chat/completions -H 'Content-Type: application/json' -d '{
"model": "/data/hf_models/GLM-5.3-NVFP4",
"messages": [{"role": "user", "content": "请帮我查一下北京今天的天气"}],
"tools": [{"type": "function", "function": {
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}}],
"tool_choice": "auto", "temperature": 0, "max_tokens": 512
}')
tc_calls=$(echo "$tc_out" | python3 -c "
import json,sys
d=json.load(sys.stdin)
msg=d['choices'][0]['message']
calls=msg.get('tool_calls') or []
if calls:
c=calls[0]
print(c['function']['name'], c['function']['arguments'])
else:
print('NO_TOOL_CALL', (msg.get('content') or '')[:150])
" 2>/dev/null)
if echo "$tc_calls" | grep -q "get_weather.*北京"; then
echo "[PASS] tool call: $tc_calls"; PASS=$((PASS+1))
else
echo "[FAIL] tool call: $tc_calls"; FAIL=$((FAIL+1))
fi
echo "===== 结果: PASS=$PASS FAIL=$FAIL ====="

View File

@ -0,0 +1,23 @@
#!/bin/bash
# Scenario 1, real-corpus (PG19) re-test of method A (TP8+EAGLE, live prod config).
# 8 points: {128k, 64k} x cc{1,2,3,4}, nreq 8, shared-frac 0.9, run-ids 9301-9308.
# Each run-id maps to a fixed non-overlapping corpus window (see bench_corpus.py).
#
# Repo 版与 60.7 原版md5 5b2534f4c2e2f5760c453abb00dc32a2唯一差异
# bench 脚本按本目录解析,语料/日志目录可用 CORPUS / LOG_DIR 覆盖(默认 /root
# 即服务器原布局,行为不变)。详见实验 README「文件对照」。
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CORPUS="${CORPUS:-/root/corpus_ids.json}"
LOG_DIR="${LOG_DIR:-/root/bench_logs}"
mkdir -p "$LOG_DIR"
for spec in "131072 1 9301" "131072 2 9302" "131072 3 9303" "131072 4 9304" \
"65536 1 9305" "65536 2 9306" "65536 3 9307" "65536 4 9308"; do
set -- $spec
IL=$1; CC=$2; RID=$3
echo "=== input=$IL cc=$CC run=$RID start $(date +%T) ===" >> "$LOG_DIR/r1_runner.log"
python3 "${SCRIPT_DIR}/bench_corpus.py" --corpus "$CORPUS" \
--input-len $IL --concurrency $CC --num-requests 8 --run-id $RID \
--shared-frac 0.9 --output-len 512 > "$LOG_DIR/r1_run${RID}.log" 2>&1
echo "=== run=$RID done rc=$? $(date +%T) ===" >> "$LOG_DIR/r1_runner.log"
done
echo "ALL_DONE $(date +%T)" >> "$LOG_DIR/r1_runner.log"

View File

@ -0,0 +1,22 @@
#!/bin/bash
# Scenario 2, real-corpus (PG19) re-test of method A (TP8+EAGLE, high-cc variant:
# max-running 32, decode graphs bs 4/8/12/16). 3 points, 16k fully-unique prompts.
# run-ids map to fixed corpus windows (see bench_corpus.py pool C).
#
# Repo 版与 60.7 原版md5 8965a71edef6b1cf406496f0362a5d05唯一差异
# bench 脚本按本目录解析,语料/日志目录可用 CORPUS / LOG_DIR 覆盖(默认 /root
# 即服务器原布局,行为不变)。详见实验 README「文件对照」。
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CORPUS="${CORPUS:-/root/corpus_ids.json}"
LOG_DIR="${LOG_DIR:-/root/bench_logs}"
mkdir -p "$LOG_DIR"
for spec in "8 16 9311" "16 32 9312" "32 32 9313"; do
set -- $spec
CC=$1; NR=$2; RID=$3
echo "=== cc=$CC nreq=$NR run=$RID start $(date +%T) ===" >> "$LOG_DIR/r2_runner.log"
python3 "${SCRIPT_DIR}/bench_corpus.py" --corpus "$CORPUS" \
--input-len 16384 --concurrency $CC --num-requests $NR --run-id $RID \
--shared-frac 0 --output-len 512 > "$LOG_DIR/r2_run${RID}.log" 2>&1
echo "=== run=$RID done rc=$? $(date +%T) ===" >> "$LOG_DIR/r2_runner.log"
done
echo "ALL_DONE $(date +%T)" >> "$LOG_DIR/r2_runner.log"

View File

@ -0,0 +1,26 @@
#!/usr/bin/env python3
"""Compact table from bench_corpus.py r1/r2 logs."""
import glob
import json
import sys
pat = sys.argv[1] if len(sys.argv) > 1 else "/root/bench_logs/r1_run*.log"
rows = []
for f in sorted(glob.glob(pat)):
txt = open(f).read()
if "===== SUMMARY =====" not in txt:
rows.append((f, "NO_SUMMARY"))
continue
d = json.loads(txt.split("===== SUMMARY =====")[1])
rows.append((f, (
f"run={d['run_id']} il={d['input_len']} cc={d['concurrency']} ok={d['ok']}/{d['num_requests']} "
f"wall={d['wall_s']} out_tps={d['output_throughput_tok_s']} "
f"in_tps={d['input_throughput_tok_s']} "
f"ttft_p50={d['ttft_s']['p50']} ttft_mean={d['ttft_s']['mean']} "
f"tpot_p50={d['tpot_s']['p50']} tpot_mean={d['tpot_s']['mean']} "
f"e2e_p50={d['e2e_s']['p50']} dec_p50={d['per_req_decode_tok_s']['p50']} "
f"accept={d['spec_accept_length_mean']} retr={d['retractions_total']} "
f"hit={d['cache_hit_from_logs'].get('hit_rate')}"
)))
for f, r in rows:
print(f.split("/")[-1], r)

View File

@ -0,0 +1,31 @@
#!/usr/bin/env python3
"""Tokenize PG19 books with the served model's tokenizer (run INSIDE container).
Reads /tmp/bench_corpus/books/*.txt (docker cp'd in), writes compact JSON to
stdout: {"ids": [flat token ids], "books": [[start, end), ...]}
Progress goes to stderr. Host-side usage:
docker exec -i glm53-nvfp4 python3 - < tokenize_corpus.py \
> /root/corpus_ids.json 2> /root/tokenize_progress.log
"""
import glob
import json
import sys
from transformers import AutoTokenizer
MODEL = "/data/hf_models/GLM-5.3-NVFP4"
tok = AutoTokenizer.from_pretrained(MODEL)
ids_all = []
books = []
files = sorted(glob.glob("/tmp/bench_corpus/books/*.txt"))
for f in files:
with open(f, encoding="utf-8", errors="ignore") as fh:
text = fh.read()
enc = tok(text, add_special_tokens=False)["input_ids"]
books.append([len(ids_all), len(ids_all) + len(enc)])
ids_all.extend(enc)
print(f"[tokenize] {f} -> {len(enc)} tokens", file=sys.stderr)
print(f"[tokenize] total files={len(files)} total_tokens={len(ids_all)}", file=sys.stderr)
json.dump({"ids": ids_all, "books": books}, sys.stdout, separators=(",", ":"))