Keep MTP opt-in: default-off build, isolated experimental package and measurements
Browse filesAdd explicit experimental MTP compile gating and resident probe tools/results. Normal terminal/API inference does not initialize MTP. Keep final v6 model and portable binary unchanged; exclude MTP build artifacts.
- .gitignore +33 -0
- CMakeLists.txt +2 -0
- README.md +5 -0
- docs/MTP_LATEST_20261002.md +168 -0
- docs/README.md +1 -0
- docs/measurements/mtp-latest-20261002/baseline-32k/metadata.json +64 -0
- docs/measurements/mtp-latest-20261002/combined-32k/metadata.json +64 -0
- docs/measurements/mtp-latest-20261002/ling3-tiny-v6-mtp-experimental.manifest.json +13 -0
- docs/measurements/mtp-latest-20261002/summary.json +200 -0
- include/ling3/decoder.h +8 -0
- include/ling3/precision_policy.h +11 -1
- models/local-experimental/mtp/README.md +15 -0
- models/local-experimental/mtp/ling3-tiny-v6-mtp-experimental.manifest.json +13 -0
- src/decoder.cpp +131 -1
- src/main.cpp +134 -0
- tests/data/mtp_probe_questions.txt +3 -0
- tests/precision_policy_test.cpp +15 -0
- tools/append_mtp_package.py +133 -0
- tools/mtp_resident_benchmark.py +134 -0
.gitignore
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Build and local caches
|
| 2 |
+
build/
|
| 3 |
+
build-mtp*/
|
| 4 |
+
engine/source/build/
|
| 5 |
+
**/__pycache__/
|
| 6 |
+
*.pyc
|
| 7 |
+
|
| 8 |
+
# Runtime and benchmark output
|
| 9 |
+
*.log
|
| 10 |
+
*.jsonl
|
| 11 |
+
board-test-result.json
|
| 12 |
+
*-result.json
|
| 13 |
+
artifacts/benchmarks/
|
| 14 |
+
docs/measurements/**/*.f32
|
| 15 |
+
|
| 16 |
+
# Large model/checkpoint/release files; publish through Release or model hosting
|
| 17 |
+
models/**/*.safetensors
|
| 18 |
+
models/**/*.l3r
|
| 19 |
+
models/**/*.gguf
|
| 20 |
+
models/local-final/**/*.l3r
|
| 21 |
+
models/local-final/**/mindnano-infer
|
| 22 |
+
models/local-experimental/**/ling3-rknn
|
| 23 |
+
releases/
|
| 24 |
+
|
| 25 |
+
# The final model is published separately through Hugging Face / Git LFS.
|
| 26 |
+
!models/local-final/
|
| 27 |
+
!models/local-final/v6-board-test/
|
| 28 |
+
!models/local-final/v6-board-test/ling3-tiny-w4.l3r
|
| 29 |
+
models/local-final/v6-board-test/mindnano-infer
|
| 30 |
+
|
| 31 |
+
# Generated quantization payloads
|
| 32 |
+
calibration/assets/quantized-assets/
|
| 33 |
+
work/
|
CMakeLists.txt
CHANGED
|
@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.20)
|
|
| 3 |
project(ling3_rknn VERSION 0.1.0 LANGUAGES CXX)
|
| 4 |
|
| 5 |
option(LING3_WITH_RKNN "Build the direct RKNN backend" OFF)
|
|
|
|
| 6 |
option(LING3_TUNE_A76 "Tune release builds for Cortex-A76" ON)
|
| 7 |
option(LING3_WITH_ICU "Use ICU for exact tokenizer normalization and splitting" ON)
|
| 8 |
set(RKNN_ROOT "" CACHE PATH "Local RKNPU2 root from https://github.com/airockchip/rknpu2 (contains include/ and lib/)")
|
|
@@ -28,6 +29,7 @@ target_include_directories(ling3_core PUBLIC include)
|
|
| 28 |
target_compile_features(ling3_core PUBLIC cxx_std_20)
|
| 29 |
target_compile_options(ling3_core PRIVATE -Wall -Wextra -Wpedantic)
|
| 30 |
target_compile_definitions(ling3_core PUBLIC LING3_WITH_RKNN=$<BOOL:${LING3_WITH_RKNN}>)
|
|
|
|
| 31 |
|
| 32 |
if(LING3_WITH_ICU)
|
| 33 |
find_package(ICU COMPONENTS uc i18n QUIET)
|
|
|
|
| 3 |
project(ling3_rknn VERSION 0.1.0 LANGUAGES CXX)
|
| 4 |
|
| 5 |
option(LING3_WITH_RKNN "Build the direct RKNN backend" OFF)
|
| 6 |
+
option(LING3_EXPERIMENTAL_MTP "Build experimental NEXTN/MTP probes (not production decoding)" OFF)
|
| 7 |
option(LING3_TUNE_A76 "Tune release builds for Cortex-A76" ON)
|
| 8 |
option(LING3_WITH_ICU "Use ICU for exact tokenizer normalization and splitting" ON)
|
| 9 |
set(RKNN_ROOT "" CACHE PATH "Local RKNPU2 root from https://github.com/airockchip/rknpu2 (contains include/ and lib/)")
|
|
|
|
| 29 |
target_compile_features(ling3_core PUBLIC cxx_std_20)
|
| 30 |
target_compile_options(ling3_core PRIVATE -Wall -Wextra -Wpedantic)
|
| 31 |
target_compile_definitions(ling3_core PUBLIC LING3_WITH_RKNN=$<BOOL:${LING3_WITH_RKNN}>)
|
| 32 |
+
target_compile_definitions(ling3_core PUBLIC LING3_EXPERIMENTAL_MTP=$<BOOL:${LING3_EXPERIMENTAL_MTP}>)
|
| 33 |
|
| 34 |
if(LING3_WITH_ICU)
|
| 35 |
find_package(ICU COMPONENTS uc i18n QUIET)
|
README.md
CHANGED
|
@@ -70,6 +70,8 @@ Ling-3.0-rknn 是在 RK3588/RK3588S 上的专用 C++ 推理工程。
|
|
| 70 |
|
| 71 |
当前限制:一次只执行一个推理请求,忙时可能返回 429;接口没有用户鉴权和 TLS;KV 缓存是进程内资源,不是跨重启的持久聊天记录;128K/256K 只提供容量选项,尚未完成完整板端质量和性能认证;MTP 未启用。
|
| 72 |
|
|
|
|
|
|
|
| 73 |
## 板端性能
|
| 74 |
|
| 75 |
下表来自 NanoPi M6(RK3588、16GB)2026-09-22 实测。CPU、NPU、DMC 测试期间固定最高频率;模型为 `ling3-tiny-w4.l3r`,BF16 KV,32K 初始化,关闭 thinking,temperature=0,每次冷启动会话输入后生成 64 tokens,未命中 KV 缓存。TTFT 不包括服务启动时间。
|
|
@@ -86,6 +88,8 @@ Ling-3.0-rknn 是在 RK3588/RK3588S 上的专用 C++ 推理工程。
|
|
| 86 |
|
| 87 |
TTFT 随输入长度增加,解码速度也会因每个新 token 需要访问更长的历史 KV 而下降;因此 128/64 的结果不能代表长上下文表现。完整原始结果和测试环境见 [BOARD_BENCHMARK_AND_INT4_20260922.md](docs/BOARD_BENCHMARK_AND_INT4_20260922.md)。频率、散热和后台负载改变时应重新测量。
|
| 88 |
|
|
|
|
|
|
|
| 89 |
## 量化与数值损失
|
| 90 |
|
| 91 |
当前发布模型不是全 W4:部分线性层和专家使用 W4A8,其他指定投影使用 W8A8。默认 KV 为 BF16,KDA 状态保留 FP32。量化存在损失,以下是固定 teacher-forcing 序列、4,322 个预测位置、完整 157,184 词表,以 BF16 为参考的 logits 分布指标:
|
|
@@ -137,6 +141,7 @@ python3 tools/pack_model.py \
|
|
| 137 |
cmake -S . -B build \
|
| 138 |
-DCMAKE_BUILD_TYPE=Release \
|
| 139 |
-DLING3_WITH_RKNN=ON \
|
|
|
|
| 140 |
-DRKNN_ROOT=./vendor/rknpu2
|
| 141 |
cmake --build build -j4 --target mindnano-infer ling3-rknn
|
| 142 |
```
|
|
|
|
| 70 |
|
| 71 |
当前限制:一次只执行一个推理请求,忙时可能返回 429;接口没有用户鉴权和 TLS;KV 缓存是进程内资源,不是跨重启的持久聊天记录;128K/256K 只提供容量选项,尚未完成完整板端质量和性能认证;MTP 未启用。
|
| 72 |
|
| 73 |
+
MTP 仅作为实验保留,编译开关为 `LING3_EXPERIMENTAL_MTP`,默认 `OFF`。默认构建不包含 MTP 前向实现、实验 API 和 `mtp-benchmark` 命令,不初始化 MTP 权重或 KV。即使显式编译为 `ON`,终端对话和 OpenAI API 也不自动启用 MTP;只有实验探针调用 `EnableMtp()` 后才初始化。实验模型不加入正常部署流程,启用方法见 [MTP 实验说明](docs/MTP_LATEST_20261002.md)。
|
| 74 |
+
|
| 75 |
## 板端性能
|
| 76 |
|
| 77 |
下表来自 NanoPi M6(RK3588、16GB)2026-09-22 实测。CPU、NPU、DMC 测试期间固定最高频率;模型为 `ling3-tiny-w4.l3r`,BF16 KV,32K 初始化,关闭 thinking,temperature=0,每次冷启动会话输入后生成 64 tokens,未命中 KV 缓存。TTFT 不包括服务启动时间。
|
|
|
|
| 88 |
|
| 89 |
TTFT 随输入长度增加,解码速度也会因每个新 token 需要访问更长的历史 KV 而下降;因此 128/64 的结果不能代表长上下文表现。完整原始结果和测试环境见 [BOARD_BENCHMARK_AND_INT4_20260922.md](docs/BOARD_BENCHMARK_AND_INT4_20260922.md)。频率、散热和后台负载改变时应重新测量。
|
| 90 |
|
| 91 |
+
2026-10-02 保留最新混合模型+MTP 的[实验驻留前向测试](docs/MTP_LATEST_20261002.md):统一 32K 初始化,主模型约 18.69 token/s;MTP 每步追加约 10.54 ms,驻留 RSS 增加约 497 MiB。四条不同序列的下一 token Top1 命中为 17/252,尚未证明投机加速。这些 MTP 指标只适用于显式启用的实验探针,不是默认引擎的开销。实验模型位于 `models/local-experimental/mtp/`,正式 v6 模型、便携发布二进制和默认服务未替换。
|
| 92 |
+
|
| 93 |
## 量化与数值损失
|
| 94 |
|
| 95 |
当前发布模型不是全 W4:部分线性层和专家使用 W4A8,其他指定投影使用 W8A8。默认 KV 为 BF16,KDA 状态保留 FP32。量化存在损失,以下是固定 teacher-forcing 序列、4,322 个预测位置、完整 157,184 词表,以 BF16 为参考的 logits 分布指标:
|
|
|
|
| 141 |
cmake -S . -B build \
|
| 142 |
-DCMAKE_BUILD_TYPE=Release \
|
| 143 |
-DLING3_WITH_RKNN=ON \
|
| 144 |
+
-DLING3_EXPERIMENTAL_MTP=OFF \
|
| 145 |
-DRKNN_ROOT=./vendor/rknpu2
|
| 146 |
cmake --build build -j4 --target mindnano-infer ling3-rknn
|
| 147 |
```
|
docs/MTP_LATEST_20261002.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 最新混合量化模型与 MTP 组合测试
|
| 2 |
+
|
| 3 |
+
2026-10-02,在 NanoPi M6 / RK3588 / 16GB(`10.1.20.33`)测试。
|
| 4 |
+
结论:最新主模型与 MTP 可以一起加载并连续前向,未改变本组主模型生成
|
| 5 |
+
token;但预测命中偏低,尚未证明投机解码收益。
|
| 6 |
+
|
| 7 |
+
## 实验开关(默认关闭)
|
| 8 |
+
|
| 9 |
+
`LING3_EXPERIMENTAL_MTP=OFF` 是 CMake 默认值。关闭时,MTP 层、状态、
|
| 10 |
+
前向函数、实验 API 和 `mtp-benchmark` 命令均不编译;正常推理只运行原主干,
|
| 11 |
+
不初始化 MTP 权重和 KV。追加了 MTP 的模型文件不会自动激活它。
|
| 12 |
+
|
| 13 |
+
开启编译也不会把 MTP 加入终端或 OpenAI API 对话流程。`Decoder` 构造函数
|
| 14 |
+
不再自动加载 sidecar,只有实验探针显式调用 `EnableMtp()` 才会初始化。
|
| 15 |
+
现有正常生成、KV 复用和服务流程没有调用此接口。
|
| 16 |
+
|
| 17 |
+
以下性能和内存数据为之前显式运行实验探针的记录,不代表默认构建开销。
|
| 18 |
+
实验模型保留在 `models/local-experimental/mtp/`,不进入正式 v6 发布包。
|
| 19 |
+
本地及板端的独立 MTP 编译目录、旧实验二进制已清理;源码、模型和测试记录
|
| 20 |
+
保留,可按下方命令重新构建。当前源码复现实验时必须单独开启编译开关;默认二进制的
|
| 21 |
+
帮助信息不包含 `mtp-benchmark`,测试脚本会在锁频前提示编译开关未开启。
|
| 22 |
+
|
| 23 |
+
## 版本与口径
|
| 24 |
+
|
| 25 |
+
主干使用本工作区 `models/local-final/v6-board-test/ling3-tiny-w4.l3r`,
|
| 26 |
+
SHA256 为 `80f0086247328fe77b2f939df460b4955287b6f75f6aaada85512c56e652f435`。
|
| 27 |
+
它是混合 W4A8/W8A8 包,包含 91 个 BF16 源线性权重张量,在运行时走 W8A8;
|
| 28 |
+
不是上一轮 `minimind-o/Ling3RKNN` 的旧 W4 实验主干。
|
| 29 |
+
|
| 30 |
+
MTP 来自 `inclusionAI/Ling-3.0-tiny-base` 的 layer 24,使用此前已转换的
|
| 31 |
+
W4A8 资产。仅追加 798 个 MTP 张量,原主干 18,029 个张量保留原数据并逐项
|
| 32 |
+
验证 SHA256;不重新量化主模型。MTP 使用主模型的共享 W8A8 输出头。
|
| 33 |
+
|
| 34 |
+
组合包:`models/local-experimental/mtp/ling3-tiny-v6-mtp-experimental.l3r`,
|
| 35 |
+
5,188,395,008 bytes,约 4.832 GiB。
|
| 36 |
+
SHA256 为 `edfc670a0d27d9e5db1a49e18584adb1f47af1120b2060cfa5042ec9332ce8a9`。
|
| 37 |
+
正式 v6 模型和便携发布二进制未替换。
|
| 38 |
+
|
| 39 |
+
使用本工作区根目录源码的实验 `HasMtp` / `EvalMtp` 与 `mtp-benchmark`,
|
| 40 |
+
在板端重新编译。该源码与 README 中 2026-09-22 的 `engine/source/`
|
| 41 |
+
历史性能测试版本不同,不把新旧结果视为同成本优化对比。
|
| 42 |
+
|
| 43 |
+
两个模型均初始化 **32K 容量**,实际输入为 128、44、42、39 token,
|
| 44 |
+
输出均为 64 token;这是短请求测试,不是 32K 实际输入测试。每题在同一个
|
| 45 |
+
驻留进程中预热一次,再测三轮;切换题目时重置对话状态,保留权重和 RKNN
|
| 46 |
+
context。固定中文性能题、故事题、办公题、解释题均关闭 thinking,贪心解码。
|
| 47 |
+
|
| 48 |
+
CPU 大核 2.304/2.352 GHz、NPU 1 GHz、DMC 2.4 GHz,固定最高频率。
|
| 49 |
+
启用专家预加载、三核平衡调度、共享输入、SIMD/vector math。
|
| 50 |
+
MLA attention 使用 CPU,GDN 使用 FP32 CPU 路径;线性层和专家矩阵使用 NPU。
|
| 51 |
+
采样未发现降频,最高温度 58.23°C;推理进程未使用 swap,测试后频率已恢复。
|
| 52 |
+
|
| 53 |
+
## 连续速度与内存
|
| 54 |
+
|
| 55 |
+
速度取预热后三轮合计解码时间,均计入首 token 后的 63 次计算。
|
| 56 |
+
|
| 57 |
+
| 项目 | 最新主模型 | 加载 MTP、只运行主模型 | 主模型逐步追加 MTP |
|
| 58 |
+
|---|---:|---:|---:|
|
| 59 |
+
| 四题合计解码速度 | 18.69 token/s | 18.71 token/s | 15.54 token/s |
|
| 60 |
+
| 固定 128/64 题速度 | 18.43 token/s | 18.56 token/s | 15.34 token/s |
|
| 61 |
+
| 主模型每步平均 | 53.35 ms | 53.32 ms | 53.54 ms |
|
| 62 |
+
| MTP 每步平均 | — | — | 10.54 ms |
|
| 63 |
+
| MTP 层 / 输出头 | — | — | 2.03 / 8.51 ms |
|
| 64 |
+
| 预热后进程 RSS | 6,516~6,524 MiB | 7,012~7,021 MiB | 7,012~7,021 MiB |
|
| 65 |
+
| 进程峰值 RSS | 6,952 MiB | 7,021 MiB | 7,021 MiB |
|
| 66 |
+
|
| 67 |
+
RSS 约从 **6.37 GiB 增至 6.86 GiB**,同题稳定态增量约 **497 MiB**。
|
| 68 |
+
MTP 增加一个 MLA 层,32K BF16 KV 本体增加 320 MiB;其他增量包括专家与
|
| 69 |
+
投影权重、RKNN context 和工作内存。共享输出头没有另复制一整套权重。
|
| 70 |
+
峰值还包含构造期间临时映射,不能直接用两次冷启动峰值差计算 MTP 的内存增量。
|
| 71 |
+
RSS 包含 NPU 共享映射,是进程统计,不等于整个系统内存差值。
|
| 72 |
+
|
| 73 |
+
输出头约占 MTP 前向时间的 **81%**。上一轮旧 W4 主干的 MTP 为 6.85 ms、
|
| 74 |
+
输出头 4.76 ms;最新模型使用共享 W8A8 输出头,不能沿用旧包的成本估算。
|
| 75 |
+
|
| 76 |
+
本探针逐 token 填满 MTP 缓存:固定 128-token 题的主模型填充约 6.8 秒,
|
| 77 |
+
追加 MTP 后约 8.1 秒。**这不是正式批量 prefill 的 TTFT**,不能与 README
|
| 78 |
+
的 753 ms 直接比较。
|
| 79 |
+
|
| 80 |
+
## 预测与主模型一致性
|
| 81 |
+
|
| 82 |
+
| 问题 | 输入 token | MTP 下一 token Top1 命中 |
|
| 83 |
+
|---|---:|---:|
|
| 84 |
+
| 固定工作安排性能题 | 128 | 3/63,4.76% |
|
| 85 |
+
| 中文故事 | 44 | 6/63,9.52% |
|
| 86 |
+
| 项目会议议程 | 42 | 3/63,4.76% |
|
| 87 |
+
| 长上下文原理解释 | 39 | 5/63,7.94% |
|
| 88 |
+
| 四条不同序列合计 | — | **17/252,6.75%** |
|
| 89 |
+
|
| 90 |
+
每题三轮相同,重复轮次用于测速度,不能当作更多独立质量样本。
|
| 91 |
+
加入 MTP 后每轮主模型 Top1 均为 64/64 一致;四题的生成 token hash
|
| 92 |
+
也与不含 MTP 的原模型一致。这只证明本组生成序列一致,没有完整 logits
|
| 93 |
+
数值等价认证,也不是通用问答正确率。
|
| 94 |
+
|
| 95 |
+
目前测的是使用真实主干 hidden 和正确历史的单步 NEXTN;**不是连续预测
|
| 96 |
+
四个候选后的实际投机接受率**。主模型批量验证、接受前缀提交、拒绝回滚及
|
| 97 |
+
MTP 会话状态恢复尚未实现。直接顺序追加 MTP 会降低速度,当前命中表现
|
| 98 |
+
也不足以支持“预测四个、接受两个”的假设。
|
| 99 |
+
|
| 100 |
+
下一步应先对照桌面参考定位预测偏差,包括 MTP 量化损失、主干与 sidecar
|
| 101 |
+
兼容性、位置和缓存对齐。已核对上游 HF 实现确实传入主模型最终归一化后的
|
| 102 |
+
hidden;不应在没有数值对照时擅自去掉这一归一化。先修预测质量,再做完整
|
| 103 |
+
投机验证和输出头优化。
|
| 104 |
+
|
| 105 |
+
## 使用与复现
|
| 106 |
+
|
| 107 |
+
在 RK3588 上显式开启实验编译,使用独立构建目录:
|
| 108 |
+
|
| 109 |
+
```bash
|
| 110 |
+
cmake -S . -B build-mtp -DCMAKE_BUILD_TYPE=Release \
|
| 111 |
+
-DLING3_WITH_RKNN=ON -DRKNN_ROOT=./vendor/rknpu2 \
|
| 112 |
+
-DLING3_EXPERIMENTAL_MTP=ON
|
| 113 |
+
cmake --build build-mtp -j4 --target ling3-rknn
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
普通发布使用 README 中的默认 `OFF` 配置。CLI 参数示例如下,依赖匹配的
|
| 117 |
+
RKNN/ICU 用户态运行库;定量复现请使用后面的脚本设置优化环境。正式便携
|
| 118 |
+
二进制尚未加入这个实验 CLI。模型头中的 4096 是原包默认容量,本测试显式
|
| 119 |
+
传入 32768 初始化,不代表实际输入被限制在 4096。
|
| 120 |
+
|
| 121 |
+
```bash
|
| 122 |
+
./build-mtp/ling3-rknn mtp-benchmark \
|
| 123 |
+
models/local-experimental/mtp/ling3-tiny-v6-mtp-experimental.l3r \
|
| 124 |
+
128 64 3 32768 tests/data/mtp_probe_questions.txt
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
`tools/mtp_resident_benchmark.py` 会设置优化环境、临时锁频、采样 CPU/NPU、
|
| 128 |
+
内存及温度,正常结束和 SIGINT/SIGTERM 时恢复原设置:
|
| 129 |
+
|
| 130 |
+
```bash
|
| 131 |
+
sudo python3 tools/mtp_resident_benchmark.py \
|
| 132 |
+
./build-mtp/ling3-rknn \
|
| 133 |
+
models/local-experimental/mtp/ling3-tiny-v6-mtp-experimental.l3r \
|
| 134 |
+
--runtime /path/to/rknn/lib --output ./results/mtp-combined-new \
|
| 135 |
+
--context 32768 --seqlen 128 --tokens 64 --rounds 3 \
|
| 136 |
+
--cases tests/data/mtp_probe_questions.txt
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
对照时将模型路径换成 `models/local-final/v6-board-test/ling3-tiny-w4.l3r`,
|
| 140 |
+
使用新的输出目录。脚本拒绝覆盖旧结果,一次只运行一个模型进程。
|
| 141 |
+
|
| 142 |
+
`tools/append_mtp_package.py` 可从含量化 MTP 张量的 `.l3r` 追加 layer 24,
|
| 143 |
+
不要求原始主模型或重新量化主干:
|
| 144 |
+
|
| 145 |
+
```bash
|
| 146 |
+
python3 tools/append_mtp_package.py \
|
| 147 |
+
--base models/local-final/v6-board-test/ling3-tiny-w4.l3r \
|
| 148 |
+
--mtp-package /path/to/package-containing-layer24.l3r \
|
| 149 |
+
--output ./work/ling3-tiny-v6-mtp-new.l3r
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
MTP 缓存目前要求连续填满。跳过前缀、重复提交同一个位置、只恢复主模型
|
| 153 |
+
缓存后直接调用 MTP 都会被接口检查拒绝;不能把主模型状态复用直接视为
|
| 154 |
+
MTP 状态复用。
|
| 155 |
+
|
| 156 |
+
主机侧 8 项机制测试通过,包含新增的 MTP 精度族分类检查。板端两组测试
|
| 157 |
+
均 exit 0,本地生成的组合模型 SHA256 与板端一致。
|
| 158 |
+
|
| 159 |
+
整理实验开关后,主机侧默认 `OFF` 与显式 `ON` 两种构建分别通过全部
|
| 160 |
+
8 项测试;检查默认静态库不含 `EnableMtp`、`HasMtp`、`EvalMtp` 导出符号,
|
| 161 |
+
默认 CLI 不含 `mtp-benchmark`,禁用的实验命令不会打开模型文件。此轮没有
|
| 162 |
+
重跑板端性能测试,上面的数值仍是此前保存的实测记录。
|
| 163 |
+
|
| 164 |
+
[结果摘要](measurements/mtp-latest-20261002/summary.json)与
|
| 165 |
+
[合包审计](measurements/mtp-latest-20261002/ling3-tiny-v6-mtp-experimental.manifest.json)
|
| 166 |
+
保存在工作区。完整前向日志、频率元数据和占用采样保留在同目录的
|
| 167 |
+
`baseline-32k/`、`combined-32k/`,以及板端
|
| 168 |
+
`/home/serial/ling3-latest-mtp-20261002/results/`。
|
docs/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
| 4 |
|
| 5 |
## 核心报告
|
| 6 |
|
|
|
|
| 7 |
- [BOARD_BENCHMARK_AND_INT4_20260922.md](BOARD_BENCHMARK_AND_INT4_20260922.md):当前板端 128/64 至 16K/64 性能与官方 INT4 数值对比。
|
| 8 |
- [INT4_BOARD_ACCURACY_20260922.md](INT4_BOARD_ACCURACY_20260922.md):固定 teacher-forcing 条件下的 logits、概率分布和 Top1 一致率。
|
| 9 |
- [KV_CACHE_BENCHMARK_20260922.md](KV_CACHE_BENCHMARK_20260922.md):BF16/INT8 KV 的容量、内存和长输入性能。
|
|
|
|
| 4 |
|
| 5 |
## 核心报告
|
| 6 |
|
| 7 |
+
- [MTP_LATEST_20261002.md](MTP_LATEST_20261002.md):默认不编译的 MTP 实验开关,以及统一 32K 容量的驻留速度、内存和预测命中测试。
|
| 8 |
- [BOARD_BENCHMARK_AND_INT4_20260922.md](BOARD_BENCHMARK_AND_INT4_20260922.md):当前板端 128/64 至 16K/64 性能与官方 INT4 数值对比。
|
| 9 |
- [INT4_BOARD_ACCURACY_20260922.md](INT4_BOARD_ACCURACY_20260922.md):固定 teacher-forcing 条件下的 logits、概率分布和 Top1 一致率。
|
| 10 |
- [KV_CACHE_BENCHMARK_20260922.md](KV_CACHE_BENCHMARK_20260922.md):BF16/INT8 KV 的容量、内存和长输入性能。
|
docs/measurements/mtp-latest-20261002/baseline-32k/metadata.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"started_utc": "2026-10-02T14:32:07.142908+00:00",
|
| 3 |
+
"command": [
|
| 4 |
+
"/home/serial/ling3-latest-mtp-20261002/build/ling3-rknn",
|
| 5 |
+
"mtp-benchmark",
|
| 6 |
+
"/home/serial/ling3-v6-workspace-test/ling3-tiny-w4.l3r",
|
| 7 |
+
"128",
|
| 8 |
+
"64",
|
| 9 |
+
"3",
|
| 10 |
+
"32768",
|
| 11 |
+
"/home/serial/ling3-latest-mtp-20261002/source/tests/data/mtp_probe_questions.txt"
|
| 12 |
+
],
|
| 13 |
+
"original_clocks": {
|
| 14 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_governor": "ondemand",
|
| 15 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq": "1800000",
|
| 16 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_governor": "ondemand",
|
| 17 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_min_freq": "408000",
|
| 18 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_governor": "ondemand",
|
| 19 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_min_freq": "408000",
|
| 20 |
+
"/sys/class/devfreq/fdab0000.npu/governor": "rknpu_ondemand",
|
| 21 |
+
"/sys/class/devfreq/fdab0000.npu/min_freq": "300000000",
|
| 22 |
+
"/sys/class/devfreq/dmc/governor": "dmc_ondemand",
|
| 23 |
+
"/sys/class/devfreq/dmc/min_freq": "534000000"
|
| 24 |
+
},
|
| 25 |
+
"meminfo_before": "MemTotal: 16342628 kB\nMemFree: 260916 kB\nMemAvailable: 14314584 kB\nBuffers: 87808 kB\nCached: 13925596 kB\nSwapCached: 0 kB\nActive: 7125980 kB\nInactive: 7658296 kB\nActive(anon): 23304 kB\nInactive(anon): 830208 kB\nActive(file): 7102676 kB\nInactive(file): 6828088 kB\nUnevictable: 14400 kB\nMlocked: 0 kB\nSwapTotal: 8171312 kB\nSwapFree: 8119344 kB\nZswap: 0 kB\nZswapped: 0 kB\nDirty: 316 kB\nWriteback: 0 kB\nAnonPages: 785292 kB\nMapped: 276836 kB\nShmem: 82640 kB\nKReclaimable: 383648 kB\nSlab: 541800 kB\nSReclaimable: 383648 kB\nSUnreclaim: 158152 kB\nKernelStack: 9184 kB\nPageTables: 18632 kB\nSecPageTables: 0 kB\nNFS_Unstable: 0 kB\nBounce: 0 kB\nWritebackTmp: 0 kB\nCommitLimit: 16342624 kB\nCommitted_AS: 4934684 kB\nVmallocTotal: 133143592960 kB\nVmallocUsed: 104164 kB\nVmallocChunk: 0 kB\nPercpu: 7776 kB\nCmaTotal: 786432 kB\nCmaAllocated: 8780 kB\nCmaReleased: 777652 kB\nCmaFree: 588 kB\nHugePages_Total: 0\nHugePages_Free: 0\nHugePages_Rsvd: 0\nHugePages_Surp: 0\nHugepagesize: 2048 kB\nHugetlb: 0 kB",
|
| 26 |
+
"applied_clocks": {
|
| 27 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_governor": "performance",
|
| 28 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq": "1800000",
|
| 29 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_governor": "performance",
|
| 30 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_min_freq": "2304000",
|
| 31 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_governor": "performance",
|
| 32 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_min_freq": "2352000",
|
| 33 |
+
"/sys/class/devfreq/fdab0000.npu/governor": "performance",
|
| 34 |
+
"/sys/class/devfreq/fdab0000.npu/min_freq": "1000000000",
|
| 35 |
+
"/sys/class/devfreq/dmc/governor": "performance",
|
| 36 |
+
"/sys/class/devfreq/dmc/min_freq": "2400000000"
|
| 37 |
+
},
|
| 38 |
+
"profile": {
|
| 39 |
+
"LING3_PREWARM_EXPERTS": "1",
|
| 40 |
+
"LING3_EXPERT_BALANCED": "1",
|
| 41 |
+
"LING3_EXPERT_ZERO_COPY": "1",
|
| 42 |
+
"LING3_GDN_CPU_PREFILL": "1",
|
| 43 |
+
"LING3_GDN_CPU_FP32_STATE": "1",
|
| 44 |
+
"LING3_GDN_CPU_DECODE": "1",
|
| 45 |
+
"LING3_GDN_FULL_FP32": "1",
|
| 46 |
+
"LING3_MLA_SIMD": "1",
|
| 47 |
+
"LING3_VECTOR_MATH": "1",
|
| 48 |
+
"LING3_MLA_BACKEND": "cpu"
|
| 49 |
+
},
|
| 50 |
+
"exit_code": 0,
|
| 51 |
+
"elapsed_s": 138.48842622793745,
|
| 52 |
+
"restored_clocks": {
|
| 53 |
+
"/sys/class/devfreq/dmc/min_freq": "534000000",
|
| 54 |
+
"/sys/class/devfreq/dmc/governor": "dmc_ondemand",
|
| 55 |
+
"/sys/class/devfreq/fdab0000.npu/min_freq": "300000000",
|
| 56 |
+
"/sys/class/devfreq/fdab0000.npu/governor": "rknpu_ondemand",
|
| 57 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_min_freq": "408000",
|
| 58 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_governor": "ondemand",
|
| 59 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_min_freq": "408000",
|
| 60 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_governor": "ondemand",
|
| 61 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq": "1800000",
|
| 62 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_governor": "ondemand"
|
| 63 |
+
}
|
| 64 |
+
}
|
docs/measurements/mtp-latest-20261002/combined-32k/metadata.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"started_utc": "2026-10-02T14:35:58.845242+00:00",
|
| 3 |
+
"command": [
|
| 4 |
+
"/home/serial/ling3-latest-mtp-20261002/build/ling3-rknn",
|
| 5 |
+
"mtp-benchmark",
|
| 6 |
+
"/home/serial/ling3-latest-mtp-20261002/artifacts/ling3-tiny-v6-mtp-experimental.l3r",
|
| 7 |
+
"128",
|
| 8 |
+
"64",
|
| 9 |
+
"3",
|
| 10 |
+
"32768",
|
| 11 |
+
"/home/serial/ling3-latest-mtp-20261002/source/tests/data/mtp_probe_questions.txt"
|
| 12 |
+
],
|
| 13 |
+
"original_clocks": {
|
| 14 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_governor": "ondemand",
|
| 15 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq": "1800000",
|
| 16 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_governor": "ondemand",
|
| 17 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_min_freq": "408000",
|
| 18 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_governor": "ondemand",
|
| 19 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_min_freq": "408000",
|
| 20 |
+
"/sys/class/devfreq/fdab0000.npu/governor": "rknpu_ondemand",
|
| 21 |
+
"/sys/class/devfreq/fdab0000.npu/min_freq": "300000000",
|
| 22 |
+
"/sys/class/devfreq/dmc/governor": "dmc_ondemand",
|
| 23 |
+
"/sys/class/devfreq/dmc/min_freq": "534000000"
|
| 24 |
+
},
|
| 25 |
+
"meminfo_before": "MemTotal: 16342628 kB\nMemFree: 7260096 kB\nMemAvailable: 14319844 kB\nBuffers: 52036 kB\nCached: 7005492 kB\nSwapCached: 0 kB\nActive: 3938108 kB\nInactive: 3889344 kB\nActive(anon): 28320 kB\nInactive(anon): 830228 kB\nActive(file): 3909788 kB\nInactive(file): 3059116 kB\nUnevictable: 14400 kB\nMlocked: 0 kB\nSwapTotal: 8171312 kB\nSwapFree: 8113968 kB\nZswap: 0 kB\nZswapped: 0 kB\nDirty: 0 kB\nWriteback: 0 kB\nAnonPages: 784344 kB\nMapped: 232044 kB\nShmem: 88876 kB\nKReclaimable: 351588 kB\nSlab: 506672 kB\nSReclaimable: 351588 kB\nSUnreclaim: 155084 kB\nKernelStack: 9104 kB\nPageTables: 18624 kB\nSecPageTables: 0 kB\nNFS_Unstable: 0 kB\nBounce: 0 kB\nWritebackTmp: 0 kB\nCommitLimit: 16342624 kB\nCommitted_AS: 4947628 kB\nVmallocTotal: 133143592960 kB\nVmallocUsed: 104052 kB\nVmallocChunk: 0 kB\nPercpu: 7712 kB\nCmaTotal: 786432 kB\nCmaAllocated: 8780 kB\nCmaReleased: 777652 kB\nCmaFree: 402076 kB\nHugePages_Total: 0\nHugePages_Free: 0\nHugePages_Rsvd: 0\nHugePages_Surp: 0\nHugepagesize: 2048 kB\nHugetlb: 0 kB",
|
| 26 |
+
"applied_clocks": {
|
| 27 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_governor": "performance",
|
| 28 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq": "1800000",
|
| 29 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_governor": "performance",
|
| 30 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_min_freq": "2304000",
|
| 31 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_governor": "performance",
|
| 32 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_min_freq": "2352000",
|
| 33 |
+
"/sys/class/devfreq/fdab0000.npu/governor": "performance",
|
| 34 |
+
"/sys/class/devfreq/fdab0000.npu/min_freq": "1000000000",
|
| 35 |
+
"/sys/class/devfreq/dmc/governor": "performance",
|
| 36 |
+
"/sys/class/devfreq/dmc/min_freq": "2400000000"
|
| 37 |
+
},
|
| 38 |
+
"profile": {
|
| 39 |
+
"LING3_PREWARM_EXPERTS": "1",
|
| 40 |
+
"LING3_EXPERT_BALANCED": "1",
|
| 41 |
+
"LING3_EXPERT_ZERO_COPY": "1",
|
| 42 |
+
"LING3_GDN_CPU_PREFILL": "1",
|
| 43 |
+
"LING3_GDN_CPU_FP32_STATE": "1",
|
| 44 |
+
"LING3_GDN_CPU_DECODE": "1",
|
| 45 |
+
"LING3_GDN_FULL_FP32": "1",
|
| 46 |
+
"LING3_MLA_SIMD": "1",
|
| 47 |
+
"LING3_VECTOR_MATH": "1",
|
| 48 |
+
"LING3_MLA_BACKEND": "cpu"
|
| 49 |
+
},
|
| 50 |
+
"exit_code": 0,
|
| 51 |
+
"elapsed_s": 262.595832146937,
|
| 52 |
+
"restored_clocks": {
|
| 53 |
+
"/sys/class/devfreq/dmc/min_freq": "534000000",
|
| 54 |
+
"/sys/class/devfreq/dmc/governor": "dmc_ondemand",
|
| 55 |
+
"/sys/class/devfreq/fdab0000.npu/min_freq": "300000000",
|
| 56 |
+
"/sys/class/devfreq/fdab0000.npu/governor": "rknpu_ondemand",
|
| 57 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_min_freq": "408000",
|
| 58 |
+
"/sys/devices/system/cpu/cpufreq/policy6/scaling_governor": "ondemand",
|
| 59 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_min_freq": "408000",
|
| 60 |
+
"/sys/devices/system/cpu/cpufreq/policy4/scaling_governor": "ondemand",
|
| 61 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq": "1800000",
|
| 62 |
+
"/sys/devices/system/cpu/cpufreq/policy0/scaling_governor": "ondemand"
|
| 63 |
+
}
|
| 64 |
+
}
|
docs/measurements/mtp-latest-20261002/ling3-tiny-v6-mtp-experimental.manifest.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"base": "ling3-tiny-w4.l3r",
|
| 3 |
+
"base_sha256": "80f0086247328fe77b2f939df460b4955287b6f75f6aaada85512c56e652f435",
|
| 4 |
+
"mtp_package": "ling3-tiny-w4-mtp-experimental.l3r",
|
| 5 |
+
"mtp_source_sha256": "27911ca182d5a16d3c8e80e069e60a893ceb77f6ddfa3ac3da4182e5ed2e0485",
|
| 6 |
+
"output": "ling3-tiny-v6-mtp-experimental.l3r",
|
| 7 |
+
"output_sha256": "edfc670a0d27d9e5db1a49e18584adb1f47af1120b2060cfa5042ec9332ce8a9",
|
| 8 |
+
"bytes": 5188395008,
|
| 9 |
+
"flags": 513,
|
| 10 |
+
"trunk_tensors_unchanged": 18029,
|
| 11 |
+
"appended_mtp_tensors": 798,
|
| 12 |
+
"recomputed_quantization": false
|
| 13 |
+
}
|
docs/measurements/mtp-latest-20261002/summary.json
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"test": "resident NEXTN forward, not speculative decoding",
|
| 3 |
+
"context_capacity": 32768,
|
| 4 |
+
"new_tokens": 64,
|
| 5 |
+
"measured_rounds": 3,
|
| 6 |
+
"cases": [
|
| 7 |
+
{
|
| 8 |
+
"case": 0,
|
| 9 |
+
"label": "fixed_128_token_work_prompt",
|
| 10 |
+
"input_tokens": 128,
|
| 11 |
+
"baseline": {
|
| 12 |
+
"decode_wall_tokens_per_second": 18.429956132608858,
|
| 13 |
+
"target_mean_ms": 54.117866666666664,
|
| 14 |
+
"rss_mib_min": 6515.7969,
|
| 15 |
+
"rss_mib_max": 6515.7969,
|
| 16 |
+
"process_hwm_mib": 6952.4688
|
| 17 |
+
},
|
| 18 |
+
"combined_target_only": {
|
| 19 |
+
"decode_wall_tokens_per_second": 18.564440680217608,
|
| 20 |
+
"target_mean_ms": 53.7246,
|
| 21 |
+
"rss_mib_min": 7011.7422,
|
| 22 |
+
"rss_mib_max": 7011.7422,
|
| 23 |
+
"process_hwm_mib": 7011.7422
|
| 24 |
+
},
|
| 25 |
+
"target_plus_mtp": {
|
| 26 |
+
"decode_wall_tokens_per_second": 15.343605430662127,
|
| 27 |
+
"target_mean_ms": 54.265033333333335,
|
| 28 |
+
"rss_mib_min": 7011.7422,
|
| 29 |
+
"rss_mib_max": 7011.7422,
|
| 30 |
+
"process_hwm_mib": 7011.7422,
|
| 31 |
+
"mtp_mean_ms": 10.624833333333333,
|
| 32 |
+
"mtp_layers_mean_ms": 2.1126666666666667,
|
| 33 |
+
"mtp_head_mean_ms": 8.5121,
|
| 34 |
+
"mtp_p95_ms": 10.869200000000001
|
| 35 |
+
},
|
| 36 |
+
"teacher_hash": "658009ec40b8a851",
|
| 37 |
+
"predictions_unchanged": true,
|
| 38 |
+
"draft_top1_hits": 3,
|
| 39 |
+
"draft_positions": 63
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"case": 1,
|
| 43 |
+
"label": "story",
|
| 44 |
+
"input_tokens": 44,
|
| 45 |
+
"baseline": {
|
| 46 |
+
"decode_wall_tokens_per_second": 18.7953734859991,
|
| 47 |
+
"target_mean_ms": 53.06296666666667,
|
| 48 |
+
"rss_mib_min": 6519.8555,
|
| 49 |
+
"rss_mib_max": 6519.8555,
|
| 50 |
+
"process_hwm_mib": 6952.4688
|
| 51 |
+
},
|
| 52 |
+
"combined_target_only": {
|
| 53 |
+
"decode_wall_tokens_per_second": 18.82802610460991,
|
| 54 |
+
"target_mean_ms": 52.9705,
|
| 55 |
+
"rss_mib_min": 7016.4453,
|
| 56 |
+
"rss_mib_max": 7016.4453,
|
| 57 |
+
"process_hwm_mib": 7016.4453
|
| 58 |
+
},
|
| 59 |
+
"target_plus_mtp": {
|
| 60 |
+
"decode_wall_tokens_per_second": 15.614778103593295,
|
| 61 |
+
"target_mean_ms": 53.24966666666667,
|
| 62 |
+
"rss_mib_min": 7016.4453,
|
| 63 |
+
"rss_mib_max": 7016.4453,
|
| 64 |
+
"process_hwm_mib": 7016.4453,
|
| 65 |
+
"mtp_mean_ms": 10.5083,
|
| 66 |
+
"mtp_layers_mean_ms": 2.0058,
|
| 67 |
+
"mtp_head_mean_ms": 8.502466666666667,
|
| 68 |
+
"mtp_p95_ms": 10.743
|
| 69 |
+
},
|
| 70 |
+
"teacher_hash": "871f43b0902ff7a0",
|
| 71 |
+
"predictions_unchanged": true,
|
| 72 |
+
"draft_top1_hits": 6,
|
| 73 |
+
"draft_positions": 63
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"case": 2,
|
| 77 |
+
"label": "office_meeting",
|
| 78 |
+
"input_tokens": 42,
|
| 79 |
+
"baseline": {
|
| 80 |
+
"decode_wall_tokens_per_second": 18.730715539100714,
|
| 81 |
+
"target_mean_ms": 53.2465,
|
| 82 |
+
"rss_mib_min": 6521.8945,
|
| 83 |
+
"rss_mib_max": 6521.8945,
|
| 84 |
+
"process_hwm_mib": 6952.4688
|
| 85 |
+
},
|
| 86 |
+
"combined_target_only": {
|
| 87 |
+
"decode_wall_tokens_per_second": 18.6898446583178,
|
| 88 |
+
"target_mean_ms": 53.36313333333333,
|
| 89 |
+
"rss_mib_min": 7018.6602,
|
| 90 |
+
"rss_mib_max": 7018.6602,
|
| 91 |
+
"process_hwm_mib": 7018.6602
|
| 92 |
+
},
|
| 93 |
+
"target_plus_mtp": {
|
| 94 |
+
"decode_wall_tokens_per_second": 15.613767923149531,
|
| 95 |
+
"target_mean_ms": 53.2464,
|
| 96 |
+
"rss_mib_min": 7018.6602,
|
| 97 |
+
"rss_mib_max": 7018.6602,
|
| 98 |
+
"process_hwm_mib": 7018.6602,
|
| 99 |
+
"mtp_mean_ms": 10.515466666666667,
|
| 100 |
+
"mtp_layers_mean_ms": 2.0067666666666666,
|
| 101 |
+
"mtp_head_mean_ms": 8.5087,
|
| 102 |
+
"mtp_p95_ms": 10.748233333333333
|
| 103 |
+
},
|
| 104 |
+
"teacher_hash": "34da8f0ff021a5e",
|
| 105 |
+
"predictions_unchanged": true,
|
| 106 |
+
"draft_top1_hits": 3,
|
| 107 |
+
"draft_positions": 63
|
| 108 |
+
},
|
| 109 |
+
{
|
| 110 |
+
"case": 3,
|
| 111 |
+
"label": "long_context_explanation",
|
| 112 |
+
"input_tokens": 39,
|
| 113 |
+
"baseline": {
|
| 114 |
+
"decode_wall_tokens_per_second": 18.826060646809655,
|
| 115 |
+
"target_mean_ms": 52.97593333333333,
|
| 116 |
+
"rss_mib_min": 6524.0742,
|
| 117 |
+
"rss_mib_max": 6524.0742,
|
| 118 |
+
"process_hwm_mib": 6952.4688
|
| 119 |
+
},
|
| 120 |
+
"combined_target_only": {
|
| 121 |
+
"decode_wall_tokens_per_second": 18.744271096191156,
|
| 122 |
+
"target_mean_ms": 53.207766666666664,
|
| 123 |
+
"rss_mib_min": 7020.9922,
|
| 124 |
+
"rss_mib_max": 7020.9922,
|
| 125 |
+
"process_hwm_mib": 7020.9922
|
| 126 |
+
},
|
| 127 |
+
"target_plus_mtp": {
|
| 128 |
+
"decode_wall_tokens_per_second": 15.580993868400766,
|
| 129 |
+
"target_mean_ms": 53.38976666666667,
|
| 130 |
+
"rss_mib_min": 7020.9922,
|
| 131 |
+
"rss_mib_max": 7020.9922,
|
| 132 |
+
"process_hwm_mib": 7020.9922,
|
| 133 |
+
"mtp_mean_ms": 10.506966666666667,
|
| 134 |
+
"mtp_layers_mean_ms": 2.0087,
|
| 135 |
+
"mtp_head_mean_ms": 8.498233333333333,
|
| 136 |
+
"mtp_p95_ms": 10.7371
|
| 137 |
+
},
|
| 138 |
+
"teacher_hash": "d5dafb8e2a4e1f3f",
|
| 139 |
+
"predictions_unchanged": true,
|
| 140 |
+
"draft_top1_hits": 5,
|
| 141 |
+
"draft_positions": 63
|
| 142 |
+
}
|
| 143 |
+
],
|
| 144 |
+
"baseline-32k": {
|
| 145 |
+
"initialization_ms": 26056.0407,
|
| 146 |
+
"clocks_restored": true,
|
| 147 |
+
"maximum_temperature_C": 57.307,
|
| 148 |
+
"minimum_system_mem_available_mib": 7443.515625,
|
| 149 |
+
"maximum_process_swap_kib": 0
|
| 150 |
+
},
|
| 151 |
+
"combined-32k": {
|
| 152 |
+
"initialization_ms": 21645.1046,
|
| 153 |
+
"clocks_restored": true,
|
| 154 |
+
"maximum_temperature_C": 58.23,
|
| 155 |
+
"minimum_system_mem_available_mib": 6909.07421875,
|
| 156 |
+
"maximum_process_swap_kib": 0
|
| 157 |
+
},
|
| 158 |
+
"distinct_draft_positions": 252,
|
| 159 |
+
"distinct_draft_hits": 17,
|
| 160 |
+
"draft_top1_agreement": 0.06746031746031746,
|
| 161 |
+
"package": {
|
| 162 |
+
"base": "ling3-tiny-w4.l3r",
|
| 163 |
+
"base_sha256": "80f0086247328fe77b2f939df460b4955287b6f75f6aaada85512c56e652f435",
|
| 164 |
+
"mtp_package": "ling3-tiny-w4-mtp-experimental.l3r",
|
| 165 |
+
"mtp_source_sha256": "27911ca182d5a16d3c8e80e069e60a893ceb77f6ddfa3ac3da4182e5ed2e0485",
|
| 166 |
+
"output": "ling3-tiny-v6-mtp-experimental.l3r",
|
| 167 |
+
"output_sha256": "edfc670a0d27d9e5db1a49e18584adb1f47af1120b2060cfa5042ec9332ce8a9",
|
| 168 |
+
"bytes": 5188395008,
|
| 169 |
+
"flags": 513,
|
| 170 |
+
"trunk_tensors_unchanged": 18029,
|
| 171 |
+
"appended_mtp_tensors": 798,
|
| 172 |
+
"recomputed_quantization": false
|
| 173 |
+
},
|
| 174 |
+
"aggregate": {
|
| 175 |
+
"baseline-32k/target_only": {
|
| 176 |
+
"decode_wall_tokens_per_second": 18.69419444496819,
|
| 177 |
+
"target_mean_ms": 53.35081666666667,
|
| 178 |
+
"rss_mib_min": 6515.7969,
|
| 179 |
+
"rss_mib_max": 6524.0742,
|
| 180 |
+
"process_hwm_mib": 6952.4688
|
| 181 |
+
},
|
| 182 |
+
"combined-32k/target_only": {
|
| 183 |
+
"decode_wall_tokens_per_second": 18.70615503663274,
|
| 184 |
+
"target_mean_ms": 53.3165,
|
| 185 |
+
"rss_mib_min": 7011.7422,
|
| 186 |
+
"rss_mib_max": 7020.9922,
|
| 187 |
+
"process_hwm_mib": 7020.9922
|
| 188 |
+
},
|
| 189 |
+
"combined-32k/target_plus_mtp": {
|
| 190 |
+
"decode_wall_tokens_per_second": 15.537454676617868,
|
| 191 |
+
"target_mean_ms": 53.53771666666667,
|
| 192 |
+
"rss_mib_min": 7011.7422,
|
| 193 |
+
"rss_mib_max": 7020.9922,
|
| 194 |
+
"process_hwm_mib": 7020.9922,
|
| 195 |
+
"mtp_mean_ms": 10.538891666666666,
|
| 196 |
+
"mtp_layers_mean_ms": 2.0334833333333333,
|
| 197 |
+
"mtp_head_mean_ms": 8.505375
|
| 198 |
+
}
|
| 199 |
+
}
|
| 200 |
+
}
|
include/ling3/decoder.h
CHANGED
|
@@ -39,6 +39,14 @@ public:
|
|
| 39 |
std::size_t RestoreState(const DecoderState & state);
|
| 40 |
const std::string & StateSignature() const;
|
| 41 |
DecodeTimings Eval(std::uint32_t token, std::span<float> logits);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
DecodeTimings EvalBatch(
|
| 43 |
std::span<const std::uint32_t> tokens,
|
| 44 |
std::span<float> logits);
|
|
|
|
| 39 |
std::size_t RestoreState(const DecoderState & state);
|
| 40 |
const std::string & StateSignature() const;
|
| 41 |
DecodeTimings Eval(std::uint32_t token, std::span<float> logits);
|
| 42 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 43 |
+
// Only the explicit experiment initializes MTP; normal chat does not.
|
| 44 |
+
bool EnableMtp();
|
| 45 |
+
// Experimental NEXTN probe; requires a contiguous MTP prefix. Restored
|
| 46 |
+
// trunk states and batch prefill alone do not populate the MTP cache.
|
| 47 |
+
bool HasMtp() const noexcept;
|
| 48 |
+
DecodeTimings EvalMtp(std::uint32_t next_token, std::span<float> logits);
|
| 49 |
+
#endif
|
| 50 |
DecodeTimings EvalBatch(
|
| 51 |
std::span<const std::uint32_t> tokens,
|
| 52 |
std::span<float> logits);
|
include/ling3/precision_policy.h
CHANGED
|
@@ -25,9 +25,19 @@ inline std::string W8MatrixFamily(const std::string& base) {
|
|
| 25 |
constexpr std::string_view prefix="model.layers.";
|
| 26 |
if(!base.starts_with(prefix))throw std::invalid_argument("invalid attention matrix name");
|
| 27 |
int layer=-1;auto [end,ec]=std::from_chars(base.data()+prefix.size(),base.data()+base.size(),layer);
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
throw std::invalid_argument("invalid attention layer");
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
return std::string((layer+1)%4==0?"mla_":"kda_")+(base.ends_with(".o_proj")?"out":"in");
|
|
|
|
| 31 |
}
|
| 32 |
if(base.find(".mlp.shared_experts.")!=std::string::npos)return "shared";
|
| 33 |
if(base=="model.layers.0.mlp.gate_up"||base=="model.layers.0.mlp.down_proj")return "dense";
|
|
|
|
| 25 |
constexpr std::string_view prefix="model.layers.";
|
| 26 |
if(!base.starts_with(prefix))throw std::invalid_argument("invalid attention matrix name");
|
| 27 |
int layer=-1;auto [end,ec]=std::from_chars(base.data()+prefix.size(),base.data()+base.size(),layer);
|
| 28 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 29 |
+
constexpr int maximum_layer=24;
|
| 30 |
+
#else
|
| 31 |
+
constexpr int maximum_layer=23;
|
| 32 |
+
#endif
|
| 33 |
+
if(ec!=std::errc{}||end==base.data()+base.size()||*end!='.'||layer<0||layer>maximum_layer)
|
| 34 |
throw std::invalid_argument("invalid attention layer");
|
| 35 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 36 |
+
// Optional layer 24 is MTP MLA, outside the main 24-layer trunk.
|
| 37 |
+
return std::string((layer==24||(layer+1)%4==0)?"mla_":"kda_")+(base.ends_with(".o_proj")?"out":"in");
|
| 38 |
+
#else
|
| 39 |
return std::string((layer+1)%4==0?"mla_":"kda_")+(base.ends_with(".o_proj")?"out":"in");
|
| 40 |
+
#endif
|
| 41 |
}
|
| 42 |
if(base.find(".mlp.shared_experts.")!=std::string::npos)return "shared";
|
| 43 |
if(base=="model.layers.0.mlp.gate_up"||base=="model.layers.0.mlp.down_proj")return "dense";
|
models/local-experimental/mtp/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MTP 实验模型
|
| 2 |
+
|
| 3 |
+
本目录仅供 NEXTN/MTP 驻留前向测试,不属于默认部署流程。尚未实现完整的投机验证、接受前缀提交和拒绝回滚;当前测试未证明加速收益。
|
| 4 |
+
|
| 5 |
+
组合模型为 `ling3-tiny-v6-mtp-experimental.l3r`(5,188,395,008 bytes):
|
| 6 |
+
|
| 7 |
+
- 主干来自正式 v6 混合 W4A8/W8A8 模型,原 18,029 个张量数据保持不变。
|
| 8 |
+
- MTP 来自 [inclusionAI/Ling-3.0-tiny-base](https://huggingface.co/inclusionAI/Ling-3.0-tiny-base) 的 layer 24,追加 798 个转换张量。
|
| 9 |
+
- SHA256:`edfc670a0d27d9e5db1a49e18584adb1f47af1120b2060cfa5042ec9332ce8a9`。
|
| 10 |
+
|
| 11 |
+
本项目转换模型资产使用 [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/),许可边界见 [LICENSES.md](../../../LICENSES.md)。上游来源及其原有许可须保留。
|
| 12 |
+
|
| 13 |
+
需要单独使用 `-DLING3_EXPERIMENTAL_MTP=ON` 编译;编译产物不随本目录发布。默认 `OFF` 不编译 MTP,终端和 OpenAI API 对话不会自动启用它。详细命令、内存、速度和预测命中见 [实验报告](../../../docs/MTP_LATEST_20261002.md)。
|
| 14 |
+
|
| 15 |
+
此包包含完整主干,约 4.832 GiB。普通推理只需下载正式 v6 模型,无需下载本目录。
|
models/local-experimental/mtp/ling3-tiny-v6-mtp-experimental.manifest.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"base": "ling3-tiny-w4.l3r",
|
| 3 |
+
"base_sha256": "80f0086247328fe77b2f939df460b4955287b6f75f6aaada85512c56e652f435",
|
| 4 |
+
"mtp_package": "ling3-tiny-w4-mtp-experimental.l3r",
|
| 5 |
+
"mtp_source_sha256": "27911ca182d5a16d3c8e80e069e60a893ceb77f6ddfa3ac3da4182e5ed2e0485",
|
| 6 |
+
"output": "ling3-tiny-v6-mtp-experimental.l3r",
|
| 7 |
+
"output_sha256": "edfc670a0d27d9e5db1a49e18584adb1f47af1120b2060cfa5042ec9332ce8a9",
|
| 8 |
+
"bytes": 5188395008,
|
| 9 |
+
"flags": 513,
|
| 10 |
+
"trunk_tensors_unchanged": 18029,
|
| 11 |
+
"appended_mtp_tensors": 798,
|
| 12 |
+
"recomputed_quantization": false
|
| 13 |
+
}
|
src/decoder.cpp
CHANGED
|
@@ -133,10 +133,19 @@ std::unique_ptr<Linear> MakeLinear(
|
|
| 133 |
const auto begin = base.data() + layer_prefix.size();
|
| 134 |
const auto end = base.data() + base.size();
|
| 135 |
const auto [parsed_end, error] = std::from_chars(begin, end, layer);
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
throw std::runtime_error("cannot derive W4 IOMMU domain from " + base);
|
| 138 |
}
|
|
|
|
|
|
|
|
|
|
| 139 |
iommu_domain_id = 2 + layer / 2;
|
|
|
|
| 140 |
} else if (base == "lm_head") {
|
| 141 |
iommu_domain_id = 14;
|
| 142 |
}
|
|
@@ -1414,6 +1423,62 @@ private:
|
|
| 1414 |
bool reuse_batch_input_ = false;
|
| 1415 |
};
|
| 1416 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1417 |
class DecoderLayer {
|
| 1418 |
public:
|
| 1419 |
DecoderLayer(
|
|
@@ -1543,6 +1608,12 @@ struct Decoder::Impl {
|
|
| 1543 |
std::vector<float> final_norm;
|
| 1544 |
DecoderScratch scratch; // Declared before layers: outlives all borrowers.
|
| 1545 |
std::vector<std::unique_ptr<DecoderLayer>> layers;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1546 |
std::unique_ptr<Linear> lm_head;
|
| 1547 |
std::vector<float> hidden;
|
| 1548 |
std::vector<float> normalized;
|
|
@@ -1595,11 +1666,31 @@ struct Decoder::Impl {
|
|
| 1595 |
lm_head = MakeLinear(package, "lm_head");
|
| 1596 |
}
|
| 1597 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1598 |
void Reset() {
|
| 1599 |
for (auto & weak : checkpoints) if (auto checkpoint = weak.lock()) checkpoint->valid = false;
|
| 1600 |
checkpoints.clear();
|
| 1601 |
current_position = 0;
|
| 1602 |
for (auto & layer : layers) layer->Reset();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1603 |
}
|
| 1604 |
|
| 1605 |
void InvalidateOverwrittenCheckpoints() {
|
|
@@ -1649,6 +1740,9 @@ struct Decoder::Impl {
|
|
| 1649 |
throw std::invalid_argument("stale or foreign decoder checkpoint");
|
| 1650 |
for (std::size_t i = 0; i < layers.size(); ++i) layers[i]->RestoreCheckpoint(checkpoint.layers[i]);
|
| 1651 |
current_position = checkpoint.position;
|
|
|
|
|
|
|
|
|
|
| 1652 |
return current_position;
|
| 1653 |
}
|
| 1654 |
|
|
@@ -1678,6 +1772,9 @@ struct Decoder::Impl {
|
|
| 1678 |
lm_head->Run(normalized, logits);
|
| 1679 |
const auto end = Clock::now();
|
| 1680 |
++current_position;
|
|
|
|
|
|
|
|
|
|
| 1681 |
return {
|
| 1682 |
Milliseconds(begin, layers_end),
|
| 1683 |
Milliseconds(layers_end, end),
|
|
@@ -1727,6 +1824,9 @@ struct Decoder::Impl {
|
|
| 1727 |
}
|
| 1728 |
const auto end = Clock::now();
|
| 1729 |
current_position += rows;
|
|
|
|
|
|
|
|
|
|
| 1730 |
return {
|
| 1731 |
Milliseconds(begin, layers_end),
|
| 1732 |
Milliseconds(layers_end, end),
|
|
@@ -1734,6 +1834,28 @@ struct Decoder::Impl {
|
|
| 1734 |
};
|
| 1735 |
}
|
| 1736 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1737 |
void PrepareBatch(std::size_t rows) {
|
| 1738 |
if (rows < 1 || rows > kMaxBatch) {
|
| 1739 |
throw std::invalid_argument("decoder batch rows must be in [1, 128]");
|
|
@@ -1756,6 +1878,14 @@ MlaBackendStats Decoder::AttentionStats() const { return impl_->scratch.mla.npu.
|
|
| 1756 |
DecodeTimings Decoder::Eval(std::uint32_t token, std::span<float> logits) {
|
| 1757 |
return impl_->Eval(token, logits);
|
| 1758 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1759 |
DecodeTimings Decoder::EvalBatch(
|
| 1760 |
std::span<const std::uint32_t> tokens,
|
| 1761 |
std::span<float> logits) {
|
|
|
|
| 133 |
const auto begin = base.data() + layer_prefix.size();
|
| 134 |
const auto end = base.data() + base.size();
|
| 135 |
const auto [parsed_end, error] = std::from_chars(begin, end, layer);
|
| 136 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 137 |
+
constexpr int maximum_layer = 24;
|
| 138 |
+
#else
|
| 139 |
+
constexpr int maximum_layer = 23;
|
| 140 |
+
#endif
|
| 141 |
+
if (error != std::errc {} || parsed_end == begin || layer < 0 || layer > maximum_layer) {
|
| 142 |
throw std::runtime_error("cannot derive W4 IOMMU domain from " + base);
|
| 143 |
}
|
| 144 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 145 |
+
iommu_domain_id = layer == 24 ? 15 : 2 + layer / 2;
|
| 146 |
+
#else
|
| 147 |
iommu_domain_id = 2 + layer / 2;
|
| 148 |
+
#endif
|
| 149 |
} else if (base == "lm_head") {
|
| 150 |
iommu_domain_id = 14;
|
| 151 |
}
|
|
|
|
| 1423 |
bool reuse_batch_input_ = false;
|
| 1424 |
};
|
| 1425 |
|
| 1426 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 1427 |
+
// Layer 24 from the optional Ling NEXTN/MTP sidecar. It is deliberately
|
| 1428 |
+
// separate from DecoderLayer: the main trunk has KDA/MLA grouping and its
|
| 1429 |
+
// state/checkpoints, while MTP consumes the trunk's normalized hidden state
|
| 1430 |
+
// together with the embedding of the candidate token.
|
| 1431 |
+
class MtpLayer {
|
| 1432 |
+
public:
|
| 1433 |
+
MtpLayer(const ModelPackage & package, std::size_t max_context, DecoderScratch & scratch)
|
| 1434 |
+
: input_norm_(DecodeBf16(package.tensor("model.layers.24.input_layernorm.weight"), kHidden)),
|
| 1435 |
+
post_norm_(DecodeBf16(package.tensor("model.layers.24.post_attention_layernorm.weight"), kHidden)),
|
| 1436 |
+
embedding_norm_(DecodeBf16(package.tensor("model.layers.24.enorm.weight"), kHidden)),
|
| 1437 |
+
hidden_norm_(DecodeBf16(package.tensor("model.layers.24.hnorm.weight"), kHidden)),
|
| 1438 |
+
final_norm_(DecodeBf16(package.tensor("model.layers.24.final_layernorm.weight"), kHidden)),
|
| 1439 |
+
embedding_hidden_(kHidden * 2), projected_(kHidden), residual_(kHidden),
|
| 1440 |
+
normalized_(kHidden), attention_output_(kHidden), ffn_output_(kHidden),
|
| 1441 |
+
eh_projection_(MakeLinear(package, "model.layers.24.eh_proj")),
|
| 1442 |
+
attention_(std::make_unique<MlaAttention>(package, 24, max_context, scratch.mla)),
|
| 1443 |
+
feed_forward_(std::make_unique<SparseFeedForward>(package, 24, scratch.sparse)) {}
|
| 1444 |
+
|
| 1445 |
+
void Reset() { attention_->Reset(); }
|
| 1446 |
+
|
| 1447 |
+
void Run(std::span<const float> token_embedding, std::span<const float> trunk_hidden,
|
| 1448 |
+
std::size_t position, std::span<float> output) {
|
| 1449 |
+
if (token_embedding.size() != kHidden || trunk_hidden.size() != kHidden ||
|
| 1450 |
+
output.size() != kHidden) {
|
| 1451 |
+
throw std::invalid_argument("MTP hidden shape mismatch");
|
| 1452 |
+
}
|
| 1453 |
+
RmsNorm(token_embedding.data(), embedding_norm_.data(), embedding_hidden_.data(), kHidden, kEpsilon);
|
| 1454 |
+
RmsNorm(trunk_hidden.data(), hidden_norm_.data(), embedding_hidden_.data() + kHidden, kHidden, kEpsilon);
|
| 1455 |
+
eh_projection_->Run(embedding_hidden_, projected_);
|
| 1456 |
+
std::copy(projected_.begin(), projected_.end(), residual_.begin());
|
| 1457 |
+
RmsNorm(residual_.data(), input_norm_.data(), normalized_.data(), kHidden, kEpsilon);
|
| 1458 |
+
attention_->Run(normalized_, position, attention_output_);
|
| 1459 |
+
for (int i = 0; i < kHidden; ++i) residual_[i] += attention_output_[i];
|
| 1460 |
+
RmsNorm(residual_.data(), post_norm_.data(), normalized_.data(), kHidden, kEpsilon);
|
| 1461 |
+
feed_forward_->Run(normalized_, ffn_output_);
|
| 1462 |
+
for (int i = 0; i < kHidden; ++i) residual_[i] += ffn_output_[i];
|
| 1463 |
+
RmsNorm(residual_.data(), final_norm_.data(), output.data(), kHidden, kEpsilon);
|
| 1464 |
+
}
|
| 1465 |
+
|
| 1466 |
+
void PrepareBatch(std::size_t rows) {
|
| 1467 |
+
eh_projection_->PrepareBatch(rows);
|
| 1468 |
+
attention_->PrepareBatch(rows);
|
| 1469 |
+
feed_forward_->PrepareBatch(rows);
|
| 1470 |
+
}
|
| 1471 |
+
|
| 1472 |
+
private:
|
| 1473 |
+
std::vector<float> input_norm_, post_norm_, embedding_norm_, hidden_norm_, final_norm_;
|
| 1474 |
+
std::vector<float> embedding_hidden_, projected_, residual_, normalized_, attention_output_, ffn_output_;
|
| 1475 |
+
std::unique_ptr<Linear> eh_projection_;
|
| 1476 |
+
std::unique_ptr<MlaAttention> attention_;
|
| 1477 |
+
std::unique_ptr<SparseFeedForward> feed_forward_;
|
| 1478 |
+
};
|
| 1479 |
+
|
| 1480 |
+
#endif
|
| 1481 |
+
|
| 1482 |
class DecoderLayer {
|
| 1483 |
public:
|
| 1484 |
DecoderLayer(
|
|
|
|
| 1608 |
std::vector<float> final_norm;
|
| 1609 |
DecoderScratch scratch; // Declared before layers: outlives all borrowers.
|
| 1610 |
std::vector<std::unique_ptr<DecoderLayer>> layers;
|
| 1611 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 1612 |
+
std::unique_ptr<MtpLayer> mtp;
|
| 1613 |
+
std::vector<float> mtp_hidden, mtp_embedding;
|
| 1614 |
+
std::size_t mtp_position = 0;
|
| 1615 |
+
bool mtp_trunk_ready = false;
|
| 1616 |
+
#endif
|
| 1617 |
std::unique_ptr<Linear> lm_head;
|
| 1618 |
std::vector<float> hidden;
|
| 1619 |
std::vector<float> normalized;
|
|
|
|
| 1666 |
lm_head = MakeLinear(package, "lm_head");
|
| 1667 |
}
|
| 1668 |
|
| 1669 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 1670 |
+
bool EnableMtp() {
|
| 1671 |
+
if (current_position != 0)
|
| 1672 |
+
throw std::invalid_argument("MTP must be enabled before processing the prefix");
|
| 1673 |
+
if (mtp) return true;
|
| 1674 |
+
if (std::any_of(package.tensors().begin(), package.tensors().end(),
|
| 1675 |
+
[](const TensorView & t) { return t.name == "model.layers.24.eh_proj.weight"; })) {
|
| 1676 |
+
mtp = std::make_unique<MtpLayer>(package, context_capacity, scratch);
|
| 1677 |
+
mtp_hidden.resize(kHidden);
|
| 1678 |
+
mtp_embedding.resize(kHidden);
|
| 1679 |
+
}
|
| 1680 |
+
return mtp != nullptr;
|
| 1681 |
+
}
|
| 1682 |
+
#endif
|
| 1683 |
+
|
| 1684 |
void Reset() {
|
| 1685 |
for (auto & weak : checkpoints) if (auto checkpoint = weak.lock()) checkpoint->valid = false;
|
| 1686 |
checkpoints.clear();
|
| 1687 |
current_position = 0;
|
| 1688 |
for (auto & layer : layers) layer->Reset();
|
| 1689 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 1690 |
+
if (mtp) mtp->Reset();
|
| 1691 |
+
mtp_position = 0;
|
| 1692 |
+
mtp_trunk_ready = false;
|
| 1693 |
+
#endif
|
| 1694 |
}
|
| 1695 |
|
| 1696 |
void InvalidateOverwrittenCheckpoints() {
|
|
|
|
| 1740 |
throw std::invalid_argument("stale or foreign decoder checkpoint");
|
| 1741 |
for (std::size_t i = 0; i < layers.size(); ++i) layers[i]->RestoreCheckpoint(checkpoint.layers[i]);
|
| 1742 |
current_position = checkpoint.position;
|
| 1743 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 1744 |
+
mtp_trunk_ready = false;
|
| 1745 |
+
#endif
|
| 1746 |
return current_position;
|
| 1747 |
}
|
| 1748 |
|
|
|
|
| 1772 |
lm_head->Run(normalized, logits);
|
| 1773 |
const auto end = Clock::now();
|
| 1774 |
++current_position;
|
| 1775 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 1776 |
+
mtp_trunk_ready = true;
|
| 1777 |
+
#endif
|
| 1778 |
return {
|
| 1779 |
Milliseconds(begin, layers_end),
|
| 1780 |
Milliseconds(layers_end, end),
|
|
|
|
| 1824 |
}
|
| 1825 |
const auto end = Clock::now();
|
| 1826 |
current_position += rows;
|
| 1827 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 1828 |
+
mtp_trunk_ready = compute_logits;
|
| 1829 |
+
#endif
|
| 1830 |
return {
|
| 1831 |
Milliseconds(begin, layers_end),
|
| 1832 |
Milliseconds(layers_end, end),
|
|
|
|
| 1834 |
};
|
| 1835 |
}
|
| 1836 |
|
| 1837 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 1838 |
+
DecodeTimings EvalMtp(std::uint32_t token, std::span<float> logits) {
|
| 1839 |
+
if (!mtp) throw std::runtime_error("model package has no MTP layer");
|
| 1840 |
+
if (token >= package.header().vocab_size || logits.size() != package.header().vocab_size ||
|
| 1841 |
+
current_position == 0 || !mtp_trunk_ready || mtp_position != current_position - 1)
|
| 1842 |
+
throw std::invalid_argument("MTP requires a contiguous, aligned prefix and valid token/logits");
|
| 1843 |
+
const auto begin = Clock::now();
|
| 1844 |
+
const auto embedding = embeddings.subspan(static_cast<std::size_t>(token) * kHidden, kHidden);
|
| 1845 |
+
for (int i = 0; i < kHidden; ++i) mtp_embedding[i] = BFloat16ToFloat(embedding[i]);
|
| 1846 |
+
// Upstream passes the main model's final normalized hidden state.
|
| 1847 |
+
// Keep MTP output separate so probing cannot overwrite trunk scratch.
|
| 1848 |
+
mtp->Run(mtp_embedding, normalized, mtp_position, mtp_hidden);
|
| 1849 |
+
const auto layers_end = Clock::now();
|
| 1850 |
+
lm_head->Run(mtp_hidden, logits);
|
| 1851 |
+
const auto end = Clock::now();
|
| 1852 |
+
++mtp_position;
|
| 1853 |
+
mtp_trunk_ready = false;
|
| 1854 |
+
return {Milliseconds(begin, layers_end), Milliseconds(layers_end, end), Milliseconds(begin, end)};
|
| 1855 |
+
}
|
| 1856 |
+
|
| 1857 |
+
#endif
|
| 1858 |
+
|
| 1859 |
void PrepareBatch(std::size_t rows) {
|
| 1860 |
if (rows < 1 || rows > kMaxBatch) {
|
| 1861 |
throw std::invalid_argument("decoder batch rows must be in [1, 128]");
|
|
|
|
| 1878 |
DecodeTimings Decoder::Eval(std::uint32_t token, std::span<float> logits) {
|
| 1879 |
return impl_->Eval(token, logits);
|
| 1880 |
}
|
| 1881 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 1882 |
+
bool Decoder::EnableMtp() { return impl_->EnableMtp(); }
|
| 1883 |
+
bool Decoder::HasMtp() const noexcept { return impl_->mtp != nullptr; }
|
| 1884 |
+
DecodeTimings Decoder::EvalMtp(std::uint32_t token, std::span<float> logits) {
|
| 1885 |
+
return impl_->EvalMtp(token, logits);
|
| 1886 |
+
}
|
| 1887 |
+
#endif
|
| 1888 |
+
|
| 1889 |
DecodeTimings Decoder::EvalBatch(
|
| 1890 |
std::span<const std::uint32_t> tokens,
|
| 1891 |
std::span<float> logits) {
|
src/main.cpp
CHANGED
|
@@ -64,6 +64,9 @@ void PrintUsage(const char * program) {
|
|
| 64 |
<< " " << program << " logits-smoke MODEL.l3r TOKEN [REF.f32]\n"
|
| 65 |
<< " " << program << " generate MODEL.l3r USER_TEXT [MAX_NEW_TOKENS]\n"
|
| 66 |
<< " " << program << " benchmark MODEL.l3r SEQLEN NEW_TOKENS [ITERATIONS]\n"
|
|
|
|
|
|
|
|
|
|
| 67 |
<< " " << program << " sequence-check MODEL.l3r SEQLEN NEW_TOKENS OUT_PREFIX [REFERENCE_PREFIX [CORPUS.txt]]\n"
|
| 68 |
<< " " << program << " serve MODEL.l3r [PORT]\n";
|
| 69 |
}
|
|
@@ -1209,6 +1212,130 @@ struct BenchmarkSample {
|
|
| 1209 |
ProcessMemory memory;
|
| 1210 |
};
|
| 1211 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1212 |
int RunBenchmark(
|
| 1213 |
const std::string & package_path,
|
| 1214 |
std::size_t sequence_length,
|
|
@@ -1449,6 +1576,13 @@ int main(int argc, char ** argv) {
|
|
| 1449 |
const std::size_t iterations = argc == 6 ? ParseSize(argv[5], "iterations") : 3;
|
| 1450 |
return RunBenchmark(argv[2], sequence_length, new_tokens, iterations);
|
| 1451 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1452 |
if (command == "sequence-check" && argc >= 6 && argc <= 8) {
|
| 1453 |
return RunSequenceCheck(argv[2], ParseSize(argv[3], "sequence length"),
|
| 1454 |
ParseSize(argv[4], "new tokens"), argv[5],
|
|
|
|
| 64 |
<< " " << program << " logits-smoke MODEL.l3r TOKEN [REF.f32]\n"
|
| 65 |
<< " " << program << " generate MODEL.l3r USER_TEXT [MAX_NEW_TOKENS]\n"
|
| 66 |
<< " " << program << " benchmark MODEL.l3r SEQLEN NEW_TOKENS [ITERATIONS]\n"
|
| 67 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 68 |
+
<< " " << program << " mtp-benchmark MODEL.l3r SEQLEN NEW_TOKENS ITERATIONS CONTEXT [CASES.txt]\n"
|
| 69 |
+
#endif
|
| 70 |
<< " " << program << " sequence-check MODEL.l3r SEQLEN NEW_TOKENS OUT_PREFIX [REFERENCE_PREFIX [CORPUS.txt]]\n"
|
| 71 |
<< " " << program << " serve MODEL.l3r [PORT]\n";
|
| 72 |
}
|
|
|
|
| 1212 |
ProcessMemory memory;
|
| 1213 |
};
|
| 1214 |
|
| 1215 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 1216 |
+
// Same resident decoder for baseline and MTP. Populate every MTP prefix slot
|
| 1217 |
+
// sequentially, and teacher-force baseline tokens so the work and histories
|
| 1218 |
+
// stay comparable. This measures forward overhead, not speculative speedup.
|
| 1219 |
+
int RunMtpBenchmark(const std::string & path, std::size_t length,
|
| 1220 |
+
std::size_t steps, std::size_t repeats, std::size_t capacity,
|
| 1221 |
+
const std::string & cases_path) {
|
| 1222 |
+
ConfigureInferenceProcess();
|
| 1223 |
+
const ling3::ModelPackage package(path);
|
| 1224 |
+
if (!length || steps < 2 || !repeats || length + steps > capacity || capacity > 262144)
|
| 1225 |
+
throw std::invalid_argument("invalid MTP benchmark lengths");
|
| 1226 |
+
const auto tokenizer = PackageTokenizer(package);
|
| 1227 |
+
std::vector<std::vector<std::uint32_t>> prompts{BuildBenchmarkPrompt(tokenizer, length)};
|
| 1228 |
+
if (!cases_path.empty()) {
|
| 1229 |
+
std::ifstream file(cases_path);
|
| 1230 |
+
if (!file) throw std::runtime_error("cannot open MTP cases file");
|
| 1231 |
+
std::string question;
|
| 1232 |
+
while (std::getline(file, question)) {
|
| 1233 |
+
if (question.empty()) continue;
|
| 1234 |
+
auto ids = tokenizer.Encode("<role>SYSTEM</role>detailed thinking off<|role_end|>"
|
| 1235 |
+
"<role>HUMAN</role>" + question + "<|role_end|><role>ASSISTANT</role>\n<think></think>");
|
| 1236 |
+
if (ids.empty() || ids.size() + steps > capacity)
|
| 1237 |
+
throw std::invalid_argument("case does not fit the requested context");
|
| 1238 |
+
prompts.push_back(std::move(ids));
|
| 1239 |
+
}
|
| 1240 |
+
}
|
| 1241 |
+
const auto started = std::chrono::steady_clock::now();
|
| 1242 |
+
ling3::Decoder decoder(package, capacity);
|
| 1243 |
+
decoder.EnableMtp();
|
| 1244 |
+
const std::vector<bool> modes = decoder.HasMtp() ? std::vector<bool>{false, true} : std::vector<bool>{false};
|
| 1245 |
+
std::vector<float> logits(package.header().vocab_size), draft(logits.size());
|
| 1246 |
+
const auto milliseconds = [](auto a, auto b) {
|
| 1247 |
+
return std::chrono::duration<double, std::milli>(b - a).count();
|
| 1248 |
+
};
|
| 1249 |
+
std::cout << std::fixed << std::setprecision(4)
|
| 1250 |
+
<< "benchmark=resident_mtp_forward_not_speculative\n"
|
| 1251 |
+
<< "context_capacity=" << capacity << " cases=" << prompts.size()
|
| 1252 |
+
<< " mtp_enabled=" << decoder.HasMtp() << " new_tokens=" << steps << " iterations=" << repeats << '\n'
|
| 1253 |
+
<< "initialization_ms=" << milliseconds(started, std::chrono::steady_clock::now()) << std::endl;
|
| 1254 |
+
const auto initial_memory = ReadProcessMemory();
|
| 1255 |
+
std::cout << "initial_rss_mib=" << initial_memory.rss_kb / 1024.0
|
| 1256 |
+
<< " initial_hwm_mib=" << initial_memory.hwm_kb / 1024.0 << std::endl;
|
| 1257 |
+
for (std::size_t case_index = 0; case_index < prompts.size(); ++case_index) {
|
| 1258 |
+
const auto & prompt = prompts[case_index];
|
| 1259 |
+
length = prompt.size();
|
| 1260 |
+
for (std::size_t round = 0; round <= repeats; ++round) {
|
| 1261 |
+
std::vector<std::uint32_t> teacher;
|
| 1262 |
+
teacher.reserve(steps);
|
| 1263 |
+
for (bool with_mtp : modes) {
|
| 1264 |
+
decoder.Reset();
|
| 1265 |
+
const auto attention_before = decoder.AttentionStats();
|
| 1266 |
+
const auto begin = std::chrono::steady_clock::now();
|
| 1267 |
+
double trunk = 0, mtp = 0, mtp_layers = 0, mtp_head = 0;
|
| 1268 |
+
for (std::size_t i = 0; i < length; ++i) {
|
| 1269 |
+
decoder.Eval(prompt[i], logits);
|
| 1270 |
+
if (with_mtp && i + 1 < length) decoder.EvalMtp(prompt[i + 1], draft);
|
| 1271 |
+
}
|
| 1272 |
+
std::uint32_t token = GreedyToken(logits);
|
| 1273 |
+
std::size_t target_agreement = 0, draft_hits = 0;
|
| 1274 |
+
if (!with_mtp) teacher.push_back(token);
|
| 1275 |
+
else {
|
| 1276 |
+
target_agreement += token == teacher[0];
|
| 1277 |
+
token = teacher[0];
|
| 1278 |
+
}
|
| 1279 |
+
const auto first = std::chrono::steady_clock::now();
|
| 1280 |
+
std::vector<double> trunk_samples, mtp_samples;
|
| 1281 |
+
for (std::size_t i = 1; i < steps; ++i) {
|
| 1282 |
+
std::uint32_t proposed = 0;
|
| 1283 |
+
if (with_mtp) {
|
| 1284 |
+
const auto t = decoder.EvalMtp(token, draft);
|
| 1285 |
+
mtp += t.total_ms; mtp_layers += t.layers_ms; mtp_head += t.output_head_ms;
|
| 1286 |
+
mtp_samples.push_back(t.total_ms);
|
| 1287 |
+
proposed = GreedyToken(draft);
|
| 1288 |
+
}
|
| 1289 |
+
const auto t = decoder.Eval(token, logits);
|
| 1290 |
+
trunk += t.total_ms; trunk_samples.push_back(t.total_ms);
|
| 1291 |
+
token = GreedyToken(logits);
|
| 1292 |
+
if (!with_mtp) teacher.push_back(token);
|
| 1293 |
+
else {
|
| 1294 |
+
target_agreement += token == teacher[i];
|
| 1295 |
+
draft_hits += proposed == teacher[i];
|
| 1296 |
+
token = teacher[i];
|
| 1297 |
+
}
|
| 1298 |
+
}
|
| 1299 |
+
const auto end = std::chrono::steady_clock::now();
|
| 1300 |
+
const auto percentile = [](std::vector<double> values, double q) {
|
| 1301 |
+
std::sort(values.begin(), values.end());
|
| 1302 |
+
return values[static_cast<std::size_t>(q * (values.size() - 1))];
|
| 1303 |
+
};
|
| 1304 |
+
const auto memory = ReadProcessMemory();
|
| 1305 |
+
const auto attention = decoder.AttentionStats();
|
| 1306 |
+
std::cout << "phase=" << (round == 0 ? "warmup" : "measured")
|
| 1307 |
+
<< " case=" << case_index << " seqlen=" << length
|
| 1308 |
+
<< " round=" << round << " mode=" << (with_mtp ? "target_plus_mtp" : "target_only")
|
| 1309 |
+
<< " sequential_prefill_ms=" << milliseconds(begin, first)
|
| 1310 |
+
<< " decode_wall_ms=" << milliseconds(first, end)
|
| 1311 |
+
<< " tokens_per_second=" << 1000.0 * (steps - 1) / milliseconds(first, end)
|
| 1312 |
+
<< " target_mean_ms=" << trunk / (steps - 1)
|
| 1313 |
+
<< " target_p50_ms=" << percentile(trunk_samples, 0.5)
|
| 1314 |
+
<< " target_p95_ms=" << percentile(trunk_samples, 0.95);
|
| 1315 |
+
if (with_mtp)
|
| 1316 |
+
std::cout << " mtp_mean_ms=" << mtp / (steps - 1)
|
| 1317 |
+
<< " mtp_p50_ms=" << percentile(mtp_samples, 0.5)
|
| 1318 |
+
<< " mtp_p95_ms=" << percentile(mtp_samples, 0.95)
|
| 1319 |
+
<< " mtp_layers_mean_ms=" << mtp_layers / (steps - 1)
|
| 1320 |
+
<< " mtp_head_mean_ms=" << mtp_head / (steps - 1)
|
| 1321 |
+
<< " target_agreement=" << target_agreement << '/' << steps
|
| 1322 |
+
<< " draft_top1_hits=" << draft_hits << '/' << (steps - 1);
|
| 1323 |
+
std::cout << " mla_npu_calls=" << attention.npu_calls - attention_before.npu_calls
|
| 1324 |
+
<< " mla_cpu_calls=" << attention.cpu_calls - attention_before.cpu_calls
|
| 1325 |
+
<< " mla_fallbacks=" << attention.fallbacks - attention_before.fallbacks
|
| 1326 |
+
<< " rss_mib=" << memory.rss_kb / 1024.0
|
| 1327 |
+
<< " hwm_mib=" << memory.hwm_kb / 1024.0
|
| 1328 |
+
<< " teacher_hash=" << std::hex << TokenHash(teacher) << std::dec << std::endl;
|
| 1329 |
+
if (with_mtp && target_agreement != steps)
|
| 1330 |
+
throw std::runtime_error("MTP probe changed target predictions for the same teacher-forced tokens");
|
| 1331 |
+
}
|
| 1332 |
+
}
|
| 1333 |
+
}
|
| 1334 |
+
return 0;
|
| 1335 |
+
}
|
| 1336 |
+
|
| 1337 |
+
#endif
|
| 1338 |
+
|
| 1339 |
int RunBenchmark(
|
| 1340 |
const std::string & package_path,
|
| 1341 |
std::size_t sequence_length,
|
|
|
|
| 1576 |
const std::size_t iterations = argc == 6 ? ParseSize(argv[5], "iterations") : 3;
|
| 1577 |
return RunBenchmark(argv[2], sequence_length, new_tokens, iterations);
|
| 1578 |
}
|
| 1579 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 1580 |
+
if (command == "mtp-benchmark" && (argc == 7 || argc == 8)) {
|
| 1581 |
+
return RunMtpBenchmark(argv[2], ParseSize(argv[3], "sequence length"),
|
| 1582 |
+
ParseSize(argv[4], "new tokens"), ParseSize(argv[5], "iterations"),
|
| 1583 |
+
ParseSize(argv[6], "context capacity"), argc == 8 ? argv[7] : "");
|
| 1584 |
+
}
|
| 1585 |
+
#endif
|
| 1586 |
if (command == "sequence-check" && argc >= 6 && argc <= 8) {
|
| 1587 |
return RunSequenceCheck(argv[2], ParseSize(argv[3], "sequence length"),
|
| 1588 |
ParseSize(argv[4], "new tokens"), argv[5],
|
tests/data/mtp_probe_questions.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
请用中文讲一个完整的小故事,主角是一个第一次出海的年轻人,故事要有起因、转折和结尾。
|
| 2 |
+
我明天要召开一个项目进度会,请帮我设计会议议程,并说明如何跟踪会议后的任务。
|
| 3 |
+
请解释为什么长上下文会影响大语言模型的解码速度,用通俗语言举一个例子。
|
tests/precision_policy_test.cpp
CHANGED
|
@@ -16,6 +16,21 @@ int main()try{
|
|
| 16 |
if(!SelectW8(small,"lm_head")||!SelectW8(small,"model.layers.0.mlp.down_proj")||
|
| 17 |
!SelectW8(small,"model.layers.1.mlp.shared_experts.gate_up")||SelectW8({},"lm_head"))
|
| 18 |
throw std::runtime_error("nonattention precision selection mismatch");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
for(auto bad:{"kda","head,","head,,shared"}){
|
| 20 |
bool rejected=false;try{ParseW8Families(bad);}catch(const std::invalid_argument&){rejected=true;}
|
| 21 |
if(!rejected)throw std::runtime_error("invalid selector accepted");
|
|
|
|
| 16 |
if(!SelectW8(small,"lm_head")||!SelectW8(small,"model.layers.0.mlp.down_proj")||
|
| 17 |
!SelectW8(small,"model.layers.1.mlp.shared_experts.gate_up")||SelectW8({},"lm_head"))
|
| 18 |
throw std::runtime_error("nonattention precision selection mismatch");
|
| 19 |
+
#if LING3_EXPERIMENTAL_MTP
|
| 20 |
+
if(W8MatrixFamily("model.layers.24.attention.qkv_gate_a")!="mla_in" ||
|
| 21 |
+
W8MatrixFamily("model.layers.24.attention.o_proj")!="mla_out" ||
|
| 22 |
+
SelectW8(all,"model.layers.24.mlp.experts.0.gate_up"))
|
| 23 |
+
throw std::runtime_error("MTP precision family mismatch");
|
| 24 |
+
#else
|
| 25 |
+
bool disabled_mtp_rejected=false;
|
| 26 |
+
try{W8MatrixFamily("model.layers.24.attention.o_proj");}
|
| 27 |
+
catch(const std::invalid_argument&){disabled_mtp_rejected=true;}
|
| 28 |
+
if(!disabled_mtp_rejected)throw std::runtime_error("disabled MTP layer accepted");
|
| 29 |
+
#endif
|
| 30 |
+
bool invalid_mtp_layer=false;
|
| 31 |
+
try{W8MatrixFamily("model.layers.25.attention.o_proj");}
|
| 32 |
+
catch(const std::invalid_argument&){invalid_mtp_layer=true;}
|
| 33 |
+
if(!invalid_mtp_layer)throw std::runtime_error("invalid MTP layer accepted");
|
| 34 |
for(auto bad:{"kda","head,","head,,shared"}){
|
| 35 |
bool rejected=false;try{ParseW8Families(bad);}catch(const std::invalid_argument&){rejected=true;}
|
| 36 |
if(!rejected)throw std::runtime_error("invalid selector accepted");
|
tools/append_mtp_package.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Append quantized layer-24 tensors without requantizing any trunk weight."""
|
| 3 |
+
import argparse
|
| 4 |
+
import hashlib
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
import zlib
|
| 9 |
+
|
| 10 |
+
from pack_model import HEADER, ENTRY, align, pad_to
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def index(path):
|
| 14 |
+
with path.open("rb") as stream:
|
| 15 |
+
header = list(HEADER.unpack(stream.read(HEADER.size)))
|
| 16 |
+
if (header[0] != b"L3RKNN1\0" or header[1:4] != [1, HEADER.size, 0x01020304]
|
| 17 |
+
or header[5] != ENTRY.size or header[12] != path.stat().st_size):
|
| 18 |
+
raise ValueError("invalid package header: " + str(path))
|
| 19 |
+
crc = header[14]
|
| 20 |
+
header[14] = 0
|
| 21 |
+
if zlib.crc32(HEADER.pack(*header)) & 0xFFFFFFFF != crc:
|
| 22 |
+
raise ValueError("header CRC mismatch")
|
| 23 |
+
header[14] = crc
|
| 24 |
+
stream.seek(header[8])
|
| 25 |
+
names = stream.read(header[9])
|
| 26 |
+
stream.seek(header[7])
|
| 27 |
+
result = []
|
| 28 |
+
for _ in range(header[4]):
|
| 29 |
+
entry = list(ENTRY.unpack(stream.read(ENTRY.size)))
|
| 30 |
+
name = names[entry[0]:entry[0] + entry[1]].decode("utf-8")
|
| 31 |
+
if not name or entry[3] > 4:
|
| 32 |
+
raise ValueError("invalid tensor entry")
|
| 33 |
+
for offset, size in ((entry[12], entry[13]), (entry[14], entry[15])):
|
| 34 |
+
if size and (offset < header[10] or offset + size > header[12]):
|
| 35 |
+
raise ValueError("out-of-range tensor: " + name)
|
| 36 |
+
result.append((path, name, entry))
|
| 37 |
+
if len({name for _, name, _ in result}) != len(result):
|
| 38 |
+
raise ValueError("duplicate tensor names")
|
| 39 |
+
return header, result
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def copy_range(source, output, offset, size):
|
| 43 |
+
source.seek(offset)
|
| 44 |
+
digest = hashlib.sha256()
|
| 45 |
+
remaining = size
|
| 46 |
+
while remaining:
|
| 47 |
+
chunk = source.read(min(remaining, 8 * 1024 * 1024))
|
| 48 |
+
if not chunk:
|
| 49 |
+
raise IOError("short tensor payload")
|
| 50 |
+
output.write(chunk)
|
| 51 |
+
digest.update(chunk)
|
| 52 |
+
remaining -= len(chunk)
|
| 53 |
+
return digest.digest()
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def file_hash(path):
|
| 57 |
+
with path.open("rb") as stream:
|
| 58 |
+
return hashlib.file_digest(stream, "sha256").hexdigest()
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def main():
|
| 62 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 63 |
+
parser.add_argument("--base", type=Path, required=True)
|
| 64 |
+
parser.add_argument("--mtp-package", type=Path, required=True)
|
| 65 |
+
parser.add_argument("--output", type=Path, required=True)
|
| 66 |
+
args = parser.parse_args()
|
| 67 |
+
if args.output.exists():
|
| 68 |
+
parser.error("refusing to overwrite an existing package")
|
| 69 |
+
header, trunk = index(args.base)
|
| 70 |
+
other_header, other = index(args.mtp_package)
|
| 71 |
+
if header[15:35] != other_header[15:35]:
|
| 72 |
+
parser.error("MTP architecture differs from trunk")
|
| 73 |
+
mtp = [row for row in other if row[1].startswith("model.layers.24.")]
|
| 74 |
+
if len(mtp) != 798 or any(row[1].startswith("model.layers.24.") for row in trunk):
|
| 75 |
+
parser.error("expected 798 MTP tensors and a trunk without MTP")
|
| 76 |
+
tensors = trunk + mtp
|
| 77 |
+
names = bytearray()
|
| 78 |
+
for _, name, entry in tensors:
|
| 79 |
+
entry[0] = len(names)
|
| 80 |
+
encoded = name.encode("utf-8")
|
| 81 |
+
entry[1] = len(encoded)
|
| 82 |
+
names.extend(encoded)
|
| 83 |
+
header[4] = len(tensors)
|
| 84 |
+
header[7] = HEADER.size
|
| 85 |
+
header[8] = HEADER.size + len(tensors) * ENTRY.size
|
| 86 |
+
header[9] = len(names)
|
| 87 |
+
header[10] = align(header[8] + len(names))
|
| 88 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 89 |
+
sources = {}
|
| 90 |
+
entries = []
|
| 91 |
+
try:
|
| 92 |
+
with args.output.open("xb") as output:
|
| 93 |
+
output.write(bytes(HEADER.size + len(tensors) * ENTRY.size))
|
| 94 |
+
output.write(names)
|
| 95 |
+
pad_to(output, header[10])
|
| 96 |
+
for path, name, entry in tensors:
|
| 97 |
+
if path not in sources:
|
| 98 |
+
sources[path] = path.open("rb")
|
| 99 |
+
source = sources[path]
|
| 100 |
+
data_offset = align(output.tell())
|
| 101 |
+
pad_to(output, data_offset)
|
| 102 |
+
if copy_range(source, output, entry[12], entry[13]) != entry[22]:
|
| 103 |
+
raise ValueError("tensor SHA256 mismatch: " + name)
|
| 104 |
+
aux_offset = align(output.tell()) if entry[15] else 0
|
| 105 |
+
if entry[15]:
|
| 106 |
+
pad_to(output, aux_offset)
|
| 107 |
+
copy_range(source, output, entry[14], entry[15])
|
| 108 |
+
entry[12], entry[14] = data_offset, aux_offset
|
| 109 |
+
entries.append(ENTRY.pack(*entry))
|
| 110 |
+
header[12] = align(output.tell())
|
| 111 |
+
pad_to(output, header[12])
|
| 112 |
+
header[11] = header[12] - header[10]
|
| 113 |
+
header[14] = 0
|
| 114 |
+
header[14] = zlib.crc32(HEADER.pack(*header)) & 0xFFFFFFFF
|
| 115 |
+
output.seek(0)
|
| 116 |
+
output.write(HEADER.pack(*header))
|
| 117 |
+
output.writelines(entries)
|
| 118 |
+
output.flush()
|
| 119 |
+
os.fsync(output.fileno())
|
| 120 |
+
finally:
|
| 121 |
+
for source in sources.values():
|
| 122 |
+
source.close()
|
| 123 |
+
record = {"base": args.base.name, "base_sha256": file_hash(args.base),
|
| 124 |
+
"mtp_package": args.mtp_package.name, "mtp_source_sha256": file_hash(args.mtp_package),
|
| 125 |
+
"output": args.output.name, "output_sha256": file_hash(args.output),
|
| 126 |
+
"bytes": header[12], "flags": header[6], "trunk_tensors_unchanged": len(trunk),
|
| 127 |
+
"appended_mtp_tensors": len(mtp), "recomputed_quantization": False}
|
| 128 |
+
args.output.with_suffix(".manifest.json").write_text(json.dumps(record, indent=2) + "\n")
|
| 129 |
+
print(json.dumps(record, indent=2), flush=True)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
if __name__ == "__main__":
|
| 133 |
+
main()
|
tools/mtp_resident_benchmark.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Resident MTP forward probe; temporarily lock clocks and restore on exit."""
|
| 3 |
+
import argparse
|
| 4 |
+
import datetime
|
| 5 |
+
import glob
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
import signal
|
| 10 |
+
import subprocess
|
| 11 |
+
import time
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def read(path):
|
| 15 |
+
try:
|
| 16 |
+
return Path(path).read_text().strip()
|
| 17 |
+
except OSError:
|
| 18 |
+
return None
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def main():
|
| 22 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 23 |
+
parser.add_argument("binary")
|
| 24 |
+
parser.add_argument("model")
|
| 25 |
+
parser.add_argument("--output", required=True)
|
| 26 |
+
parser.add_argument("--runtime", required=True)
|
| 27 |
+
parser.add_argument("--seqlen", type=int, default=128)
|
| 28 |
+
parser.add_argument("--tokens", type=int, default=64)
|
| 29 |
+
parser.add_argument("--rounds", type=int, default=3)
|
| 30 |
+
parser.add_argument("--context", type=int, default=32768)
|
| 31 |
+
parser.add_argument("--cases")
|
| 32 |
+
args = parser.parse_args()
|
| 33 |
+
if os.geteuid() != 0:
|
| 34 |
+
parser.error("run with sudo for temporary clock locking and NPU debugfs sampling")
|
| 35 |
+
help_env = os.environ.copy()
|
| 36 |
+
help_env["LD_LIBRARY_PATH"] = args.runtime + ":" + help_env.get("LD_LIBRARY_PATH", "")
|
| 37 |
+
probe = subprocess.run([args.binary], stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
| 38 |
+
text=True, env=help_env, timeout=15)
|
| 39 |
+
if "Usage:" not in probe.stdout:
|
| 40 |
+
parser.error("cannot query engine CLI: " + probe.stdout.strip())
|
| 41 |
+
if "mtp-benchmark" not in probe.stdout:
|
| 42 |
+
parser.error("MTP is not compiled; rebuild with -DLING3_EXPERIMENTAL_MTP=ON")
|
| 43 |
+
output = Path(args.output)
|
| 44 |
+
output.mkdir(parents=True, exist_ok=False)
|
| 45 |
+
original = {}
|
| 46 |
+
applied = {}
|
| 47 |
+
child = None
|
| 48 |
+
|
| 49 |
+
def write_clock(path, value):
|
| 50 |
+
original[path] = read(path)
|
| 51 |
+
if original[path] is None:
|
| 52 |
+
raise RuntimeError("cannot snapshot " + path)
|
| 53 |
+
Path(path).write_text(str(value))
|
| 54 |
+
applied[path] = read(path)
|
| 55 |
+
|
| 56 |
+
def stop(signum, frame):
|
| 57 |
+
raise KeyboardInterrupt
|
| 58 |
+
|
| 59 |
+
signal.signal(signal.SIGTERM, stop)
|
| 60 |
+
signal.signal(signal.SIGINT, stop)
|
| 61 |
+
try:
|
| 62 |
+
for policy in sorted(glob.glob("/sys/devices/system/cpu/cpufreq/policy*")):
|
| 63 |
+
write_clock(policy + "/scaling_governor", "performance")
|
| 64 |
+
write_clock(policy + "/scaling_min_freq", read(policy + "/scaling_max_freq"))
|
| 65 |
+
for device in ("fdab0000.npu", "dmc"):
|
| 66 |
+
base = "/sys/class/devfreq/" + device
|
| 67 |
+
frequencies = read(base + "/available_frequencies")
|
| 68 |
+
maximum = max(map(int, frequencies.split())) if frequencies else int(read(base + "/max_freq"))
|
| 69 |
+
write_clock(base + "/governor", "performance")
|
| 70 |
+
write_clock(base + "/min_freq", maximum)
|
| 71 |
+
env = os.environ.copy()
|
| 72 |
+
for key in ("LING3_GDN_PREFILL_DIR", "LING3_PREFILL_W4A4", "LING3_EXPERT_ALL_CORES",
|
| 73 |
+
"LING3_MLA_DECODE_NPU", "LING3_DISABLE_BATCH_INPUT_REUSE",
|
| 74 |
+
"LING3_DISABLE_PARALLEL_GATHER"):
|
| 75 |
+
env.pop(key, None)
|
| 76 |
+
for key in ("LING3_PREWARM_EXPERTS", "LING3_EXPERT_BALANCED", "LING3_EXPERT_ZERO_COPY",
|
| 77 |
+
"LING3_GDN_CPU_PREFILL", "LING3_GDN_CPU_FP32_STATE", "LING3_GDN_CPU_DECODE",
|
| 78 |
+
"LING3_GDN_FULL_FP32", "LING3_MLA_SIMD", "LING3_VECTOR_MATH"):
|
| 79 |
+
env[key] = "1"
|
| 80 |
+
env["LING3_MLA_BACKEND"] = "cpu"
|
| 81 |
+
env["LD_LIBRARY_PATH"] = args.runtime + ":" + env.get("LD_LIBRARY_PATH", "")
|
| 82 |
+
command = [args.binary, "mtp-benchmark", args.model, str(args.seqlen), str(args.tokens), str(args.rounds), str(args.context)]
|
| 83 |
+
if args.cases:
|
| 84 |
+
command.append(args.cases)
|
| 85 |
+
metadata = {"started_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
| 86 |
+
"command": command, "original_clocks": original, "meminfo_before": read("/proc/meminfo"), "applied_clocks": applied,
|
| 87 |
+
"profile": {k: v for k, v in env.items() if k.startswith("LING3_")}}
|
| 88 |
+
(output / "metadata.json").write_text(json.dumps(metadata, indent=2))
|
| 89 |
+
with (output / "benchmark.log").open("w") as log, (output / "telemetry.jsonl").open("w") as telemetry:
|
| 90 |
+
child = subprocess.Popen(command, stdout=log, stderr=subprocess.STDOUT, env=env)
|
| 91 |
+
print("pid=" + str(child.pid) + " output=" + str(output), flush=True)
|
| 92 |
+
start = time.monotonic()
|
| 93 |
+
while child.poll() is None:
|
| 94 |
+
row = {"elapsed_s": time.monotonic() - start,
|
| 95 |
+
"process_stat": read(f"/proc/{child.pid}/stat"),
|
| 96 |
+
"process_status": read(f"/proc/{child.pid}/status"),
|
| 97 |
+
"npu_load": read("/sys/kernel/debug/rknpu/load"),
|
| 98 |
+
"meminfo": read("/proc/meminfo")}
|
| 99 |
+
row["frequencies"] = {p: read(p) for p in glob.glob("/sys/devices/system/cpu/cpufreq/policy*/scaling_cur_freq")
|
| 100 |
+
+ ["/sys/class/devfreq/fdab0000.npu/cur_freq", "/sys/class/devfreq/dmc/cur_freq"]}
|
| 101 |
+
row["temperatures"] = {p: read(p) for p in glob.glob("/sys/class/thermal/thermal_zone*/temp")}
|
| 102 |
+
telemetry.write(json.dumps(row) + "\n")
|
| 103 |
+
telemetry.flush()
|
| 104 |
+
time.sleep(0.5)
|
| 105 |
+
metadata["exit_code"] = child.returncode
|
| 106 |
+
metadata["elapsed_s"] = time.monotonic() - start
|
| 107 |
+
print("exit_code=" + str(child.returncode), flush=True)
|
| 108 |
+
finally:
|
| 109 |
+
if child is not None and child.poll() is None:
|
| 110 |
+
child.terminate()
|
| 111 |
+
try:
|
| 112 |
+
child.wait(timeout=15)
|
| 113 |
+
except subprocess.TimeoutExpired:
|
| 114 |
+
child.kill()
|
| 115 |
+
child.wait()
|
| 116 |
+
restored = {}
|
| 117 |
+
for path, value in reversed(list(original.items())):
|
| 118 |
+
try:
|
| 119 |
+
Path(path).write_text(value)
|
| 120 |
+
restored[path] = read(path)
|
| 121 |
+
except OSError as error:
|
| 122 |
+
restored[path] = str(error)
|
| 123 |
+
final = locals().get("metadata", {"original_clocks": original, "applied_clocks": applied})
|
| 124 |
+
final["restored_clocks"] = restored
|
| 125 |
+
(output / "metadata.json").write_text(json.dumps(final, indent=2))
|
| 126 |
+
if "SUDO_UID" in os.environ:
|
| 127 |
+
for path in [output] + list(output.iterdir()):
|
| 128 |
+
os.chown(path, int(os.environ["SUDO_UID"]), int(os.environ["SUDO_GID"]))
|
| 129 |
+
print("clocks_restored", flush=True)
|
| 130 |
+
return child.returncode if child else 1
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
if __name__ == "__main__":
|
| 134 |
+
raise SystemExit(main())
|