# RK3588 KV 缓存容量与长输入测试(2026-09-22) 后续 16K/64 复测、CPU/NPU 曲线和优化分析见 [占用分析](KV_UTILIZATION_16K_20260922.md)。 测试设备:NanoPi M6(RK3588,物理内存 15959 MiB,RKNPU 驱动 0.9.8),Ling3 v6 混合 W4A8/W8A8 权重。只比较 BF16 MLA KV 和 INT8 MLA KV(批量注意力采用原生 NPU INT8 矩阵乘,即此处的 W8A8 KV 路径);其他模型权重、KDA FP32 状态不变。FP16 仍保留为可选实验模式,不参与此表。 测试期间暂时停止 MindNano WebUI、OpenAI 适配服务、安卓视频桥、redroid 和 GNOME 会话;CPU 小核 1.8 GHz、大核 2.304/2.352 GHz,NPU 1 GHz,DMC 2.4 GHz,均固定最高频率。测试后恢复此前服务与调频设置。每一容量档独立启动服务,关闭历史会话快照和前缀检查点,以隔离模型及常驻 KV 内存。 ## 上下文容量 `RSS` 是服务完成模型加载和预热时的进程常驻内存,**已经包含该档 KV 容量**;不是后续还要加上 KV 容量。`KV` 是按 6 层 MLA、每层 16 头计算的缓存字节数,BF16 每元素 2 字节,INT8 每元素 1 字节另加每 token/head 两个 FP32 scale。`余量` 是测量时系统的 `MemAvailable`,不是可供该服务独占的配额;RSS 也包含 NPU 共享映射。 | 上下文 | BF16 KV | BF16 启动 RSS | BF16 余量 | INT8 KV | INT8 启动 RSS | INT8 余量 | | ---: | ---: | ---: | ---: | ---: | ---: | ---: | | 8K | 480 MiB | 5750 MiB | 7006 MiB | 246 MiB | 5512 MiB | 7411 MiB | | 16K | 960 MiB | 6230 MiB | 6482 MiB | 492 MiB | 5760 MiB | 7116 MiB | | 32K | 1920 MiB | 7192 MiB | 5473 MiB | 984 MiB | 6253 MiB | 6570 MiB | | 64K | 3840 MiB | 9115 MiB | 3642 MiB | 1968 MiB | 7242 MiB | 5583 MiB | | 128K | 7680 MiB | 12457 MiB | 334 MiB | 3936 MiB | 9217 MiB | 3605 MiB | | 256K | 15360 MiB | **约 19–21 GiB(推测,未启动)** | 不适用 | 7872 MiB | 12745 MiB | 95 MiB | BF16 128K 和 INT8 256K 虽完成初始化,但内存余量过低并观察到少量系统 swap 增长,不适合作为此设备的可靠工作档位;BF16 256K 明显超过 16GB,因此没有强行启动。INT8 256K 只是初始化成功,位置大于模型原生 128K 尚未验证质量,不能算作 256K 长文本可用。 容量阶段原始 JSONL 中早期脚本产生的 `predicted_filled_rss_mib` **错误地在启动 RSS 上再次叠加 KV**,不要使用;表中的 `rss_mib`、`kv_capacity_mib` 和 `mem_available_mib` 是实测值或直接按布局计算的值。现有脚本已改为根据 8K 基线只外推容量的增量。 ## 输入与输出性能 请求的目标是 `输入 token/最多输出 64 token`,相同模型与 ASCII 数字输入,各请求独立、缓存命中 0、关闭 thinking。小于等于 4096 输入使用 8K 上下文;8K、16K、32K、64K 输入分别使用 16K、32K、64K、128K 上下文,以给输出留足空间。下表填写实际 tokenizer 与 API 返回的 token 数、TTFT 和解码计算速度;每档运行一次,无法代表统计分布。初次短输入可能由于自动调度未达到 NPU 注意力启用门槛,不能归因于 INT8 矩阵乘。 | 输入/输出 | BF16 TTFT (s) | INT8 TTFT (s) | INT8 TTFT 改善 | BF16 解码 tok/s | INT8 解码 tok/s | | ---: | ---: | ---: | ---: | ---: | ---: | | 128/64 | 0.778 | 0.808 | -4% | 18.38 | 18.53 | | 256/64 | 1.487 | 1.485 | 0% | 18.18 | 18.19 | | 512/64 | 2.834 | 2.755 | 3% | 17.15 | 17.71 | | 1024/64 | 5.902 | 5.559 | 6% | 16.08 | 16.16 | | 2048/64 | 13.217 | 12.111 | 8% | 13.45 | 13.51 | | 4096/64 | 32.731 | 28.834 | 12% | 10.64 | 10.22 | | 8192/64 | 90.562 | 76.846 | 15% | 7.11 | 7.11 | | 16384/64 | 285.410 | 231.574 | 19% | 4.28 | 4.45 | | 32768/64 | 986.870 | 772.622 | 22% | 2.32 | 2.53 | | 65536/64 | 内存余量不足 | 2780.517 | 不可直接比较 | 不适用 | 1.35 | BF16 128K 上下文启动时只剩约 334 MiB 可用,本次速度测试启动后最低仅约 65 MiB,故未请求 65536/64;不能把该项写成实测性能。短输入 128 token 两格式均为 `mla_npu_calls=0`;256 输入起逐步进入 NPU 路径,32K 输入为 1530 次 NPU MLA 调用、390 次 CPU MLA 调用(主要对应解码)。长输入下 TTFT 增长显著快于输入长度,32K 后解码仅约 2.3–2.5 token/s。单次结果用于观察趋势,不能据此断言百分比差异的统计显著性。 32K 输入时 MLA NPU 路径记录的耗时约 BF16 840 秒、INT8 624 秒,已占 TTFT 的 81%~85%;64K INT8 约 2484 秒,占 TTFT 的 89%。此字段包含排布、内存同步及 attention 计算,并非纯 NPU 矩阵乘耗时。这说明继续优化长输入,应优先检查 MLA 的历史 KV 搬运与批量注意力,而不仅是启动内存。 INT8 的 65536/64 在 128K 上下文中完整返回:输入、输出 token 数均准确,`finish_reason=length`,3066 次 NPU MLA 调用、0 回退,进程峰值 RSS 9225 MiB、进程 `VmSwap=0`,系统测试时余量约 3593 MiB。TTFT 约 46 分 21 秒,64-token 解码约 47 秒,证明这档能运行但不适合交互响应。INT8 256K 只是启动成功且仅余 95 MiB,超过模型原生 128K 位置范围,不做 128K 以上实际输入测试。 在本机同时考虑内存余量时,可先以 BF16 64K、INT8 128K 作为**有余量的初始化档位**;这不是交互可用长度的建议。即便 INT8,4096 输入的 TTFT 也约 29 秒,32K 达约 13 分钟;对低延迟应用仍需压缩输入或改进长预填充。这里只测速度和内存,不代表两种 KV 格式的长文本回答质量相同。 脚本:[kv_context_benchmark.py](../engine/source/tests/kv_context_benchmark.py);板端原始 JSONL 和逐请求引擎日志保存在测试设备的独立运行目录,未纳入仓库。FP16 独立实验仍可用相同脚本指定 `--mode fp16` 重测,旧版 `mla_int8_api_benchmark.py` 与既有 FP16 数据保留在板端 `run/`;本次正式结论不混入 FP16。 复现时在仓库根目录执行 `python3 engine/source/tests/kv_context_benchmark.py --help`,分别对 `--mode bf16` 和 `--mode int8` 运行 `--phase capacity` 与 `--phase perf`,使用相对路径传入 `--binary`、`--model` 和 `--output-dir` 即可。脚本只停止自己启动的测试服务,不会自动暂停其他系统服务或调整频率;测试前应检查可用内存与电源、散热状态。