diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..11d795f255b4dc05ac31c0fa7aff840c81ad0462 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,52 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +adapter/audio_adapter_h104_t100_fp16.rknn filter=lfs diff=lfs merge=lfs -text +adapter/audio_adapter_h104_t25_fp16.rknn filter=lfs diff=lfs merge=lfs -text +adapter/audio_adapter_h104_t50_fp16.rknn filter=lfs diff=lfs merge=lfs -text +adapter/audio_adapter_h104_t75_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s128/layer0/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s128/layer1/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s128/layer2/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s128/layer3/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s128/layer4/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s128/layer5/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s128/layer6/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s128/layer7/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s256/layer0/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s256/layer1/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s256/layer2/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s256/layer3/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s256/layer4/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s256/layer5/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s256/layer6/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s256/layer7/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s512/layer0/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s512/layer1/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s512/layer2/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s512/layer3/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s512/layer4/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s512/layer5/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s512/layer6/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/block_s512/layer7/block_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/head_shards/shard00/head_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/head_shards/shard01/head_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/head_shards/shard02/head_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/head_shards/shard03/head_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/head_shards/shard04/head_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/head_shards/shard05/head_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/head_shards/shard06/head_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/head_shards/shard07/head_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/kv/layer0/kv_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/kv/layer1/kv_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/kv/layer2/kv_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/kv/layer3/kv_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/kv/layer4/kv_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/kv/layer5/kv_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/kv/layer6/kv_fp16.rknn filter=lfs diff=lfs merge=lfs -text +decoder/kv/layer7/kv_fp16.rknn filter=lfs diff=lfs merge=lfs -text +encoder/audio_encoder_f800_fp16.rknn filter=lfs diff=lfs merge=lfs -text +prefill/prefill_kv_s110_fp16.rknn filter=lfs diff=lfs merge=lfs -text +prefill/prefill_kv_s35_fp16.rknn filter=lfs diff=lfs merge=lfs -text +prefill/prefill_kv_s60_fp16.rknn filter=lfs diff=lfs merge=lfs -text +prefill/prefill_kv_s85_fp16.rknn filter=lfs diff=lfs merge=lfs -text diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7556712dae841c409f34c126f88263583dfcfa18 --- /dev/null +++ b/README.md @@ -0,0 +1,160 @@ +--- +license: other +library_name: rknn +tags: + - automatic-speech-recognition + - audio8-asr + - rknn + - rk3576 + - npu + - qwen2 + - fp16 +base_model: Edge0/Audio8-ASR-0.1B +--- + +# Audio8-ASR-0.1B — RK3576 FP16 RKNN deployment + +This repository provides a deployable FP16 RKNN partition of +[Edge0/Audio8-ASR-0.1B](https://huggingface.co/Edge0/Audio8-ASR-0.1B) for the +Rockchip RK3576 NPU, plus the board runtime needed to drive it. + +It is **not** a replacement for the upstream model snapshot. Download the +upstream model separately for `config.json`, tokenizer/processor files and the +original weights used by host-side preprocessing and validation. Follow the +upstream model license and terms in addition to this release's files. + +## What is included + +The release layout is intentionally explicit: + +```text +audio8-asr-rknn-rk3576/ +├── encoder/audio_encoder_f800_fp16.rknn +├── adapter/ +│ ├── audio_adapter_h104_t25_fp16.rknn +│ ├── audio_adapter_h104_t50_fp16.rknn +│ ├── audio_adapter_h104_t75_fp16.rknn +│ └── audio_adapter_h104_t100_fp16.rknn +├── prefill/ +│ ├── prefill_kv_s35_fp16.rknn +│ ├── prefill_kv_s60_fp16.rknn +│ ├── prefill_kv_s85_fp16.rknn +│ └── prefill_kv_s110_fp16.rknn +├── decoder/ +│ ├── kv/layer0..layer7/kv_fp16.rknn +│ ├── block_s128/layer0..layer7/block_fp16.rknn +│ ├── block_s256/layer0..layer7/block_fp16.rknn +│ ├── block_s512/layer0..layer7/block_fp16.rknn +│ └── head_shards/shard00..shard07/head_fp16.rknn +├── token_embeddings_fp32.npy +├── runtime/board_run_end_to_end_dynamic_buckets.py +├── runtime/requirements-rk3576.txt +├── docs/benchmark-and-acceptance.zh-CN.md +└── docs/architecture-and-operation.zh-CN.md +``` + +The release does not include upstream weights, source audio, ONNX intermediates, +or calibration/validation tensors. `token_embeddings_fp32.npy` is the decoder +embedding lookup table exported from the upstream snapshot; it is included +because the lightweight board runtime uses it for each generated token. + +## Architecture + +Audio8-ASR is autoregressive ASR. The runtime sequence is: + +```text +mel features (CPU preprocessing) + → audio encoder [RKNN / NPU] + → audio MLP tower + projector [RKNN / NPU] + → Qwen2 prefill, logits + initial K/V [RKNN / NPU] + → repeated Qwen2 token decoding to EOS [RKNN / NPU] +``` + +The language decoder has 8 layers, hidden size 512, 8 attention/KV heads and +head dimension 64. Its K/V cache is deliberately **host-owned**, rank-3 FP32 +buffers `[heads, Smax, head_dim] = [8, Smax, 64]`. The CPU performs only cache +allocation/updates, RoPE and attention-mask preparation, embedding lookup and +argmax. All neural layers stay in RKNN NPU graphs. + +RKNN uses static shapes, while the upstream model uses a DynamicCache. The +runtime reproduces the behavior through cache capacities `128 → 256 → 512`. +When full, it copies only the valid K/V region to a larger CPU buffer and picks +the matching stateless NPU decoder-block graph. There is no graph-internal +Concat, ScatterND or runtime-owned persistent cache. + +## Runtime requirements + +Tested target: + +```text +RK3576 +RKNPU driver 0.9.8 +RKNN Lite2 / Runtime 2.3.2 +Python 3 with numpy and rknnlite.api +``` + +Install the `runtime/` directory and the model tree at the same deployment root +used in its constants, or change those paths in the runtime. The runtime takes +already-prepared `input_features.npy`, `input_ids.npy`, `audio_positions.npy` +and `rotary_inv_freq.npy`; generate them using the accompanying source archive +with the same upstream processor and prompt template. + +For each audio length, select matching static components: + +| Audio duration | audio placeholders | Adapter | Prefill graph | +|---:|---:|---|---| +| 2 s | 25 | T25 | S35 | +| 4 s | 50 | T50 | S60 | +| 6 s | 75 | T75 | S85 | +| 8 s | 100 | T100 | S110 | + +The adapter output count must exactly equal the audio-placeholder count. Do +not take the first N values from T100 for a shorter prompt: that changes the +upstream adaptive-pooling semantics and is incorrect. + +Example board invocation after preparing an S85 input: + +```bash +python3 runtime/board_run_end_to_end_dynamic_buckets.py \ + --input-dir prefill_input_s6 \ + --prefill-rknn prefill/prefill_kv_s85_fp16.rknn \ + --adapter-rknn adapter/audio_adapter_h104_t75_fp16.rknn \ + --model /path/to/upstream-Audio8-ASR-0.1B \ + --output result.json +``` + +The program stops at the upstream EOS id under normal operation. Its +`--ignore-eos` option is only for cache scheduler stress validation; do not use +it for user-facing transcription. + +## Validated accuracy and board benchmark + +All regular tests ran from real RKNN prefill through real RKNN autoregressive +decoding to EOS on RK3576. Each output was token-identical to the original +PyTorch CPU FP32 greedy reference; thus NPU-vs-FP32 CER/WER was 0%/0%. + +| Audio | Prefill length | KV capacity used | Steady neural E2E | RTF | End RSS | +|---:|---:|---|---:|---:|---:| +| 2 s | 35 | 128 | 1231.49 ms | 0.616 | 1113.15 MiB | +| 4 s | 60 | 128 | 3371.87 ms | 0.843 | 1114.12 MiB | +| 6 s | 85 | 128 | 4116.86 ms | 0.686 | 1116.10 MiB | +| 8 s | 110 | 128 → 256 | 4344.29 ms | 0.543 | 1232.23 MiB | + +`Steady neural E2E` includes encoder, adapter, prefill, decoder RKNN calls, +CPU K/V writes and any cache migration. It excludes model loading/runtime +initialization. Normal RKNN Lite `inference()` timing combines buffer transfer, +NPU work and synchronization; the presented numbers do not falsely claim a +separate DMA/H2D/D2H time. + +Additional EOS-ignored scheduler stress testing ran 180 decoder steps from a +real 6-second prefill, triggering both `128→256` and `256→512`. All 181 NPU +tokens matched FP32; cache copies cost 11.21 ms and 34.70 ms respectively, and +the final RSS was 1379.02 MiB. This validates cache scheduling, not ASR text +quality beyond EOS. + +## Reproducibility source archive + +Export scripts, input preparation code, board probes, JSON evidence and full +Chinese acceptance documentation are in the companion GitHub branch: + +https://github.com/Aaaou/Audio8-ASR-RKNN/tree/rknn-dynamic-kv-cache-validated diff --git a/adapter/audio_adapter_h104_t100_fp16.rknn b/adapter/audio_adapter_h104_t100_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..41dbfcc877df70c1f81d813ce740be1354eae3df --- /dev/null +++ b/adapter/audio_adapter_h104_t100_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f40ee9fdcd504765b2a95c39198e7b30cdce2f653041707681fc734d258874b +size 68950208 diff --git a/adapter/audio_adapter_h104_t25_fp16.rknn b/adapter/audio_adapter_h104_t25_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..b4d6f14b9beeaf7d44352b1a737a0205b2d23b4a --- /dev/null +++ b/adapter/audio_adapter_h104_t25_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:606a5eeacfe9a83b4d2f56c7844151638df2123292f06b94cc557b1f4d22fb05 +size 68937791 diff --git a/adapter/audio_adapter_h104_t50_fp16.rknn b/adapter/audio_adapter_h104_t50_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..3da7229f6d01dbb50738742307b2c985e7ee3815 --- /dev/null +++ b/adapter/audio_adapter_h104_t50_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01dd259552ee74e865ddc1e663fced34ae2ca406745d77d876629ef0c61f8ffc +size 68942975 diff --git a/adapter/audio_adapter_h104_t75_fp16.rknn b/adapter/audio_adapter_h104_t75_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..5ef5ad8327e29f9cbb701cb8821d0eba46c63edf --- /dev/null +++ b/adapter/audio_adapter_h104_t75_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9be5d1ff9d34dc73fafdf207f530c22a34855590da3ed22c2c174056bda07dc7 +size 68948159 diff --git a/decoder/block_s128/layer0/block_fp16.rknn b/decoder/block_s128/layer0/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..7997068fddb29bd128a6d9a56e1288bd4216d186 --- /dev/null +++ b/decoder/block_s128/layer0/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e789691719abdfd1813a9d57e366a140c397af3b15d45c1e2dc3468bb1d6c41 +size 5706842 diff --git a/decoder/block_s128/layer1/block_fp16.rknn b/decoder/block_s128/layer1/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..accfaae91fb2527b46abecd04b8d8ac37af22447 --- /dev/null +++ b/decoder/block_s128/layer1/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9198906cef2e021bb19626c096db1992ae9c550d2cab2eb5ec848ed196958ff6 +size 5706842 diff --git a/decoder/block_s128/layer2/block_fp16.rknn b/decoder/block_s128/layer2/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..7731ac4d34a09da3d49263973e0c6d5804ead8f4 --- /dev/null +++ b/decoder/block_s128/layer2/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7a14f145ede4d32d13fbc4083b6ca5d04a01bfa1bc723688cfde782a65979be +size 5706842 diff --git a/decoder/block_s128/layer3/block_fp16.rknn b/decoder/block_s128/layer3/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..c030311ca1fc9e48b0b39980b66aeec3d911795c --- /dev/null +++ b/decoder/block_s128/layer3/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db8721dd3b128f473b684f1e999a1489eaded87c279eb9c35a89e13d6be63392 +size 5706842 diff --git a/decoder/block_s128/layer4/block_fp16.rknn b/decoder/block_s128/layer4/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..727982d2adc73cb3725991430e672fb430d8e5ac --- /dev/null +++ b/decoder/block_s128/layer4/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42df3fb10fca4c113a92c60afea8609505ff4be1a66611ef6fab18862e71eae0 +size 5706842 diff --git a/decoder/block_s128/layer5/block_fp16.rknn b/decoder/block_s128/layer5/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..21582370232373e70501945e88995ff0de594fea --- /dev/null +++ b/decoder/block_s128/layer5/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b59f45a049706687c0051d4f3b8be7ee35a233bab8dd61833277b50b9e257ba4 +size 5706842 diff --git a/decoder/block_s128/layer6/block_fp16.rknn b/decoder/block_s128/layer6/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..637a35085a4d039bed4c35d3e7f50a898d14e291 --- /dev/null +++ b/decoder/block_s128/layer6/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfde3e70b280b6ffdbd1197c72b61258a3e69740bebe3bda02317a2e6b3a10ec +size 5706842 diff --git a/decoder/block_s128/layer7/block_fp16.rknn b/decoder/block_s128/layer7/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..17b91fcf73cbe15972a0906d7a4d18b22743ac24 --- /dev/null +++ b/decoder/block_s128/layer7/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cd0a2aa5aa597b40c889afb5f9a33df95fd7ac2bc02bd4e76f97038fdacb3b4 +size 5706842 diff --git a/decoder/block_s256/layer0/block_fp16.rknn b/decoder/block_s256/layer0/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..eb54bfb77a023e70355da07d7de142e8c8adab38 --- /dev/null +++ b/decoder/block_s256/layer0/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:933dfdcd2c831df15df10cdce3f6d659d9161dc37d2fd7379d29df9f157f23b3 +size 5717594 diff --git a/decoder/block_s256/layer1/block_fp16.rknn b/decoder/block_s256/layer1/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..9f85db4bb07d134344bc3016ebf722b3cbdc9f23 --- /dev/null +++ b/decoder/block_s256/layer1/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13454a9f052bdb974ff5f7deb4b67a6913c778d530f8cc03a1c50688f364fa70 +size 5717594 diff --git a/decoder/block_s256/layer2/block_fp16.rknn b/decoder/block_s256/layer2/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..1bdb7d0ccefe776febf4fdd331b3dd31422ae071 --- /dev/null +++ b/decoder/block_s256/layer2/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb8021e743ee1d528922d2f7d9349f829f3639308af4f141db796e4e8ad9749e +size 5717594 diff --git a/decoder/block_s256/layer3/block_fp16.rknn b/decoder/block_s256/layer3/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..9bf5c66c7597223950057cbf1e11b57d5417f9fa --- /dev/null +++ b/decoder/block_s256/layer3/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6471d7974c489499fe3d488f4f28b2363e0d786d2640c8cbc1e3fbbe167637fc +size 5717594 diff --git a/decoder/block_s256/layer4/block_fp16.rknn b/decoder/block_s256/layer4/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..2483fde682b5c2d4c1eeb8da1a2dacbfc41f04b4 --- /dev/null +++ b/decoder/block_s256/layer4/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e7495b16a6437d74ca304081564caa2779fda10fb0d21961b2f4be0744d92fc +size 5717594 diff --git a/decoder/block_s256/layer5/block_fp16.rknn b/decoder/block_s256/layer5/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..c6dc643fc7e02a4f2115ed036e06e46c8adbf567 --- /dev/null +++ b/decoder/block_s256/layer5/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6ea02c345f559281fbaadcd2f008ad1d73dffa669bdb8df195de2915c47543a +size 5717594 diff --git a/decoder/block_s256/layer6/block_fp16.rknn b/decoder/block_s256/layer6/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..d612fbdda6773401cf67dbb37d0a8e16ec70f207 --- /dev/null +++ b/decoder/block_s256/layer6/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4806f8d72d6fc90f8aecc2e022be1e098857bfef094373504e7a5443ae1a9252 +size 5717594 diff --git a/decoder/block_s256/layer7/block_fp16.rknn b/decoder/block_s256/layer7/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..fba83cc4fbeba4b949c7797170b5cad3987353f2 --- /dev/null +++ b/decoder/block_s256/layer7/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00bd10a619285b80f659f4b0f15677bd88d43ec825e0a61bbbf844738e907631 +size 5717594 diff --git a/decoder/block_s512/layer0/block_fp16.rknn b/decoder/block_s512/layer0/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..e2ead61ca4f0ff657131623dc53e9f2f2825fbf0 --- /dev/null +++ b/decoder/block_s512/layer0/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae8ff11cffc050e3aed17ededf766a69a0c7bee6cfabcbac8aa2c0ac68d596ff +size 5739098 diff --git a/decoder/block_s512/layer1/block_fp16.rknn b/decoder/block_s512/layer1/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..eba21c272dd81e02eb38d9377c64152b48b946e8 --- /dev/null +++ b/decoder/block_s512/layer1/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87c12f32a19462d37a4d1b53911bac16155903b51dc475e619a4e68fe075e6e1 +size 5739098 diff --git a/decoder/block_s512/layer2/block_fp16.rknn b/decoder/block_s512/layer2/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..934c7fc5f6557577f3af6d3154040c33e3d9a6f7 --- /dev/null +++ b/decoder/block_s512/layer2/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5c9b0c1c933bf1dd00ee207956d768213b614d89369d79c8da4578d659b1c5b +size 5739098 diff --git a/decoder/block_s512/layer3/block_fp16.rknn b/decoder/block_s512/layer3/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..68ac13c4d8da74fc6a67cf62bb3a81a6890c075e --- /dev/null +++ b/decoder/block_s512/layer3/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6e442610370344a4304363c2f750bf166edc79d5290a383772c07f8a2b9a41f +size 5739098 diff --git a/decoder/block_s512/layer4/block_fp16.rknn b/decoder/block_s512/layer4/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..019d7ade149991748c6a3a92959b766e6aaf7a3f --- /dev/null +++ b/decoder/block_s512/layer4/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7a9e6c78777e975e4e64c81dbc190b7c62e4295e7ab0c372a0a72ea0442c69e +size 5739098 diff --git a/decoder/block_s512/layer5/block_fp16.rknn b/decoder/block_s512/layer5/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..1974c14f6081078f078e605e4701c7a6f1f10f4f --- /dev/null +++ b/decoder/block_s512/layer5/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ec3ad64e9f4596712f23816db6b41ab5a8a3a36f39091d334eed1a983162a97 +size 5739098 diff --git a/decoder/block_s512/layer6/block_fp16.rknn b/decoder/block_s512/layer6/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..f2ddcbf74e7283c062fd9283f4fe56594dc1ac41 --- /dev/null +++ b/decoder/block_s512/layer6/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d93d10eb9d62719860ad38d2e4728040d33016ecfdbf05457bb41373e39ab376 +size 5739098 diff --git a/decoder/block_s512/layer7/block_fp16.rknn b/decoder/block_s512/layer7/block_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..287e9f84f39b0ad166b557519393f692beca77aa --- /dev/null +++ b/decoder/block_s512/layer7/block_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fce264635762b53ed43166e35c026fa14ace4666441dc525b14b42d59241f98d +size 5739098 diff --git a/decoder/head_shards/shard00/head_fp16.rknn b/decoder/head_shards/shard00/head_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..e02392ba1e07cd3784e44ab50f17ed4a22b45c91 --- /dev/null +++ b/decoder/head_shards/shard00/head_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36020a743b7b7a7f49a2e076ebeb353b1502afab1bb37b52918ab617adf3b16f +size 19526321 diff --git a/decoder/head_shards/shard01/head_fp16.rknn b/decoder/head_shards/shard01/head_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..44895515ef2004fee607d7190a95dbedba5c338b --- /dev/null +++ b/decoder/head_shards/shard01/head_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:578ba0c7c5e63c1687bc851384019b91c7aaa86a91bc23a5dfe5db926c246f50 +size 19526321 diff --git a/decoder/head_shards/shard02/head_fp16.rknn b/decoder/head_shards/shard02/head_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..4a9573fe4a3ae25dae090dfc91726b3349671dd6 --- /dev/null +++ b/decoder/head_shards/shard02/head_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64335a3db4f4b54bac29dd4b79c6c34df4ce57fd5f8c3a0b0b6b3471c073672d +size 19526321 diff --git a/decoder/head_shards/shard03/head_fp16.rknn b/decoder/head_shards/shard03/head_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..2a5c91b0ec802ba24c7f0e1420b25693eb7f1ae4 --- /dev/null +++ b/decoder/head_shards/shard03/head_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce40f1a1105f70c75efcff1003ac859bd9d256e43c68a3796295d07eca4282a7 +size 19526321 diff --git a/decoder/head_shards/shard04/head_fp16.rknn b/decoder/head_shards/shard04/head_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..5d7de2fa206c6134d2c13f8d0f8f570c29beb3e6 --- /dev/null +++ b/decoder/head_shards/shard04/head_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22fda36159907ed3d2a640902a90c3482422f327e75369ad40bf3838e442a919 +size 19526321 diff --git a/decoder/head_shards/shard05/head_fp16.rknn b/decoder/head_shards/shard05/head_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..9af0a68a0e739349aefc08220cd23558a6c304fe --- /dev/null +++ b/decoder/head_shards/shard05/head_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:410832b318d1890b7221b2c82f9a356e4164b22db53fc04fd4137a0eccacbd1c +size 19526321 diff --git a/decoder/head_shards/shard06/head_fp16.rknn b/decoder/head_shards/shard06/head_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..fc8d0edd5ffd8e4b216c2c4fb17cf7ac15b73516 --- /dev/null +++ b/decoder/head_shards/shard06/head_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3bd1b398a5bccbb9790468a96f4447d9095761a00d2466913f607fd023c3f46 +size 19526321 diff --git a/decoder/head_shards/shard07/head_fp16.rknn b/decoder/head_shards/shard07/head_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..bf8f48684596702b4546a58910225beed5765a02 --- /dev/null +++ b/decoder/head_shards/shard07/head_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1df27beed56626fcdeaac99872f57d2bb63ab39f7360f7fb0f3e5e4c01134cd +size 19526321 diff --git a/decoder/kv/layer0/kv_fp16.rknn b/decoder/kv/layer0/kv_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..048ccd4b8a58740714c0eb7d8f8794930e4202b1 --- /dev/null +++ b/decoder/kv/layer0/kv_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a86a70a715ad90f063dac90e2e2f46153253ba4793343089ab1229cb4901f949 +size 1167000 diff --git a/decoder/kv/layer1/kv_fp16.rknn b/decoder/kv/layer1/kv_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..8192cb9c587d8182fabfb8a4451eebd6d2642287 --- /dev/null +++ b/decoder/kv/layer1/kv_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9544cbfeb17368be16a3dcad991218df564ae48f64d7e5b13deea28361cec8dd +size 1167000 diff --git a/decoder/kv/layer2/kv_fp16.rknn b/decoder/kv/layer2/kv_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..82e14d0ed6f4c7f490409d826345ad65c02ba74a --- /dev/null +++ b/decoder/kv/layer2/kv_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa021be3bdb14c2f1e0931bf1d646a4801ba6d38ab3bd41d0aaa12beb140b89f +size 1167000 diff --git a/decoder/kv/layer3/kv_fp16.rknn b/decoder/kv/layer3/kv_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..eae56d50dadead20491a70dc94b44197cf7c576f --- /dev/null +++ b/decoder/kv/layer3/kv_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7770bcf11d390a42a116a11e99acf62365dedce5e9d6f2154a03238687ee0e23 +size 1167000 diff --git a/decoder/kv/layer4/kv_fp16.rknn b/decoder/kv/layer4/kv_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..ba62471d764483b3bf44fe3240ceeaf9aeb6cd6f --- /dev/null +++ b/decoder/kv/layer4/kv_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:111446ce7395b82e0b11741e8f819327b66737d6bfeff09cc0e9f502e04e6ce2 +size 1167000 diff --git a/decoder/kv/layer5/kv_fp16.rknn b/decoder/kv/layer5/kv_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..748d08deffe090a159b73f7c7a684df64f2c049f --- /dev/null +++ b/decoder/kv/layer5/kv_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d89912dbe2a72a23f90fa55a2277947b2bc03e1ad70f93ec012919dfc81857f9 +size 1167000 diff --git a/decoder/kv/layer6/kv_fp16.rknn b/decoder/kv/layer6/kv_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..8c99cb4f1ba1865b7ff1f097a914b1868e4ff7e2 --- /dev/null +++ b/decoder/kv/layer6/kv_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80ff77e7caf6aa317f5b559fe011d5ac0e30ab8f139f2f0aad11fd201c1c0b77 +size 1167000 diff --git a/decoder/kv/layer7/kv_fp16.rknn b/decoder/kv/layer7/kv_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..1820634f62b7dc3a92a30d94a493d5d3a24bb323 --- /dev/null +++ b/decoder/kv/layer7/kv_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b2bc7a96937d3a35740167c67df2698398a5f9997c8ee8c8da098caa1b86a20 +size 1167000 diff --git a/docs/architecture-and-operation.zh-CN.md b/docs/architecture-and-operation.zh-CN.md new file mode 100644 index 0000000000000000000000000000000000000000..3692e9acb41a113c327da8e26f4a05c59ba0c851 --- /dev/null +++ b/docs/architecture-and-operation.zh-CN.md @@ -0,0 +1,70 @@ +# RK3576 / RKNN KV-cache 策略调研 + +## 结论 + +RK3576 NPU 可以执行带有 cache 输入、Concat、Gather/Slice、Attention 的静态图;因此不能说硬件“完全不支持 KV cache”。但 RKNN Lite 普通 `inference()` 是无状态函数接口,没有一个通用的、跨调用自动持久化的 KV-cache 对象。所谓“由 NPU 自己更新 cache”必须在图中显式实现写回(Concat、ScatterND、预分配 buffer + position 等),并不等同于 runtime 自动保存状态。 + +当前项目第一版用了 Transformers `DynamicCache.update()` 导出路径。该路径把 cache 更新/Concat trace 进 ONNX,再由 RKNN 重写 layout。它在 ONNX Runtime 上正确,但 RK3576 上第 0 层的 projection 正常、Concat 后即出现约 140% L2 偏差,说明当前问题是编译后 cache layout/reorder/Gather/Concat 语义,而非 RK3576 缺少 attention 算力。 + +本地 TTS RK3576 研究也记录了同类事实:完整 AR FP16 图曾在实板 `rs-gather` submit 失败;问题收敛到 compiler 为 attention / ScatterND 生成的固定重排 Gather 路径。另一个 Fast AR 图在 position 1 分叉;无状态 FFN 子图则稳定。这些记录支持“状态写回/重排路径是风险中心”的判断。 + +## 三种实现路线 + +### 1. NPU 图内动态 Concat/ScatterND + +```text +NPU 读取旧 cache → NPU 计算新 K/V → NPU Concat/Scatter 写回 → Attention +``` + +优点:理论上减少 CPU 拼接,数据路径短。 + +风险:当前已经失败;容易触发 RKNN 的 `-rs-gather`、layout 重排、ScatterND 和静态轴问题。即使 compiler 能生成 `.rknn`,也必须逐位置验证,不能以首轮能运行作为通过标准。 + +### 2. CPU 管理 cache,NPU 读只读历史 cache(推荐第一修复路线) + +```text +CPU 保存 old K/V +NPU 计算当前 token 的 attention、FFN、logits 和 new K/V +CPU concat(old, new_delta) +``` + +优点:跨调用状态明确,CPU concat 只有约 3.63 ms;失败时边界清楚,可按 token 调试。缺点:每步需要搬运历史 K/V,且 decoder 图需要避免再次调用 `DynamicCache.update()` 的内部 Concat。 + +### 3. 固定最大 cache + NPU 位置写入 + +```text +预分配 [B,H,Smax,D],用 cache_position 写入当前位置 +``` + +优点:固定 shape,有机会实现 zero-copy/少搬运。 + +风险:仍依赖 Scatter/Masked assignment、position 和 layout;应在路线 2 证明数学递归正确后再尝试。 + +## 当前推荐顺序 + +```text +先修正 decoder 图,去除 DynamicCache.update() 的图内写回/Concat; +让 CPU 跨步保存并追加 K/V,NPU 图只消费明确的只读历史 cache; +先做第 0 层和 token 1 的逐中间量比较; +通过后扩展 8 层与 token 0/1/2/3/16/EOS; +最后再试固定最大 cache 的 NPU 写入。 +``` + +## 验收门槛 + +KV cache 是递归状态,门槛比一次性 encoder 更严格: + +```text +每层 K/V delta normalized L2 <= 0.5%:候选通过 +0.5%~1%:警告,不能直接部署 +>1%:不通过 +decode logits normalized L2 <= 0.5%,且 top-1 token 不变 +token 0/1/2/3/16/EOS 全部一致 +最终 CER/WER 不出现稳定退化 +``` + +当前图:logits L2 113.16%、K/V delta 最大 L2 143.56%、token 13→3711,属于明确失败。 + +## 时间解释 + +当前 `97.68 ms` 是普通 `rknn.inference()` 混合总时间,包含输入准备、runtime/layout、driver submit、NPU 计算、输出回收和同步;不能称为纯搬运。单独可测的 CPU concat 是 3.63 ms,历史 K/V 输入约 3.44 MiB,delta 输出 32 KiB。后续应继续使用 runtime layer table 报告 NPU operator time 和 CPU boundary time,把剩余量称为 runtime/data-boundary overhead,而不是 DMA 纯时间。 diff --git a/docs/benchmark-and-acceptance.zh-CN.md b/docs/benchmark-and-acceptance.zh-CN.md new file mode 100644 index 0000000000000000000000000000000000000000..0aa03cae5661b7d03fe3a0be1156bbffc091623f --- /dev/null +++ b/docs/benchmark-and-acceptance.zh-CN.md @@ -0,0 +1,95 @@ +# Audio8-ASR:动态 KV-cache bucket 与多时长端到端验收 + +**结论:通过本阶段验收。** 在 RK3576 上,FP16 RKNN 路径以 CPU 持有 KV buffer、NPU 运行全部神经网络图的方式,完成了 2、4、6、8 秒音频的真实预填充、自回归生成和 EOS 终止。四组输出与原始 PyTorch CPU FP32 标准路径逐 token 完全一致,故 NPU 相对标准路径的 CER/WER 均为 **0% / 0%**。8 秒组还实际触发并验证了 `128 → 256` KV bucket 迁移,迁移后继续生成至 EOS,序列仍完全一致。 + +这里的“端到端”指:标准音频特征(mel)输入 → RKNN encoder → RKNN audio tower/projector → RKNN prefill → RKNN decoder 自回归至 EOS。音频文件读取、重采样/特征生成和 tokenizer 文本解码尚未包进同一个板端命令,故不把它们计入下述 neural-inference 时延。 + +## 已落地的动态窗口机制 + +原模型的 Qwen2 decoder 用 `DynamicCache`,最大 position 为 32768、没有 sliding window;本地 prefill ONNX 本身是固定长度且没有 `past_key_values` 输入。因此,RKNN 静态 shape 的部署实现以 bucket 复刻该语义:从最小可用的 `Smax=128` 开始;当写入位置达到上限时,CPU 将 8 层 K/V 的**有效区**复制到更大 buffer,切换同一层对应的 `block_256.rknn` 或 `block_512.rknn`,并从同一 token、同一 RoPE position 继续计算。`128→256` 已由自然 ASR EOS 样本触发;`256→512` 已由下文的 FP32 对照压力路径触发,尚未由自然 ASR EOS 样本触发。 + +跨 token 的 cache 采用 `[heads, Smax, head_dim] = [8, Smax, 64]` 的 rank-3 外部布局。CPU 只做 buffer 管理、KV 原地写入、RoPE/mask 准备、argmax 和 embedding lookup;每层新的 K/V projection、attention/FFN block、最终 RMSNorm/LM-head shard 都经 RKNN 在 NPU 上执行。没有图内 `DynamicCache`、Concat、ScatterND 或隐式 runtime cache state。 + +## 精度与功能验收 + +| 截取音频 | prefill 序列长度 | audio placeholder / adapter bucket | 生成 token 数(含首 token 与 EOS) | EOS | KV bucket | 与 CPU FP32 token | NPU vs CPU FP32 CER / WER | +|---:|---:|---:|---:|---|---|---|---| +| 2 s | 35 | 25 / T25 | 7 | 151645 | 128 | 7/7,一致 | 0% / 0% | +| 4 s | 60 | 50 / T50 | 19 | 151645 | 128 | 19/19,一致 | 0% / 0% | +| 6 s | 85 | 75 / T75 | 23 | 151645 | 128 | 23/23,一致 | 0% / 0% | +| 8 s | 110 | 100 / T100 | 23 | 151645 | 128→256 | 23/23,一致 | 0% / 0% | + +`CER/WER` 使用小写、去标点、压缩空白的英文规范化;CER 不计空格。由于每条 token id 序列完全相同,文本解码也必然完全相同。标准路径中的 2/4/6 秒文本依次为: + +| 时长 | 标准 FP32 文本(与 RKNN 相同) | +|---:|---| +| 2 s | `Quilter is apostle.` | +| 4 s | `Mr. Quilter is the apostle of the middle classes, and we are glad.` | +| 6 s / 8 s | `Mr. Quilter is the apostle of the middle classes, and we are glad to welcome his gospel.` | + +### 短音频问题及修复验证 + +首版短音频 runtime 将固定 `T100` adapter 的前 N 个 embedding 截取给 prompt。这与原模型不同:原模型会先运行 tower,再以**实际 placeholder 数**做 adaptive pooling。该错误使 2 秒组 embedding 相对 L2 达 156.97%,首 token 从 FP32 的 2183 错变为 12275;它是开发层面的 adapter pooling 形状错误,不是 decoder KV-cache 或量化精度问题。 + +现已将 adapter 导出为 T25、T50、T75、T100 四个静态 RKNN 图,runtime 按实际 placeholder 数选择,且强制要求 adapter 输出 token 数恰好等于 placeholder 数。板端探针的修复后结果如下。 + +| 时长 | adapter embedding 相对 L2(对 FP32 注入 embedding) | RKNN prefill logits 相对 L2(理想 embedding 对 FP32) | NPU frontend 相对理想 prefill logits L2 | 首 token FP32 / RKNN | +|---:|---:|---:|---:|---| +| 2 s | 0.7531% | 0.5552% | 0.5581% | 2183 / 2183 | +| 4 s | 1.1082% | 1.4813% | 0.8022% | 12275 / 12275 | +| 6 s | 0.8460% | 0.4250% | 0.6936% | 12275 / 12275 | + +这些 L2 是数值健康度的辅助指标,不单独定义 ASR 是否可接受;本验收的硬判据是 greedy 自回归过程中的完整 token、EOS 与文本误差。四条均通过。 + +## RK3576 板端性能(稳态神经网络路径) + +“稳态 E2E”是 encoder + adapter + prefill + decoder NPU 调用 + CPU KV 原地写入 + 已发生的 bucket copy;它排除了模型加载、RKNN runtime 初始化和释放。`process wall` 是整段 Python 进程壁钟时间,包含这些一次性成本,因此不能与稳态时延混用。 + +| 音频 | Encoder | Adapter | Prefill | Decoder NPU 总计 | CPU KV 写入 | bucket 迁移拷贝 | 稳态 E2E | RTF | process wall | RSS(结束时) | +|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| 2 s | 408.55 ms | 58.06 ms | 103.95 ms | 659.53 ms | 1.40 ms | — | **1231.49 ms** | **0.616** | 10179.55 ms | 1113.15 MiB | +| 4 s | 389.64 ms | 45.97 ms | 84.42 ms | 2844.99 ms | 6.84 ms | — | **3371.87 ms** | **0.843** | 13259.35 ms | 1114.12 MiB | +| 6 s | 413.23 ms | 62.38 ms | 150.35 ms | 3482.55 ms | 8.35 ms | — | **4116.86 ms** | **0.686** | 13771.63 ms | 1116.10 MiB | +| 8 s | 405.26 ms | 58.87 ms | 165.19 ms | 3690.71 ms | 7.81 ms | **16.46 ms** (`128→256`) | **4344.29 ms** | **0.543** | 15017.83 ms | 1232.23 MiB | + +8 秒组的 16.46 ms 是 CPU 对 16 个 K/V tensor 的有效内容重新分配/复制与 bucket 切换成本;不是 NPU 内部自动 cache 更新。迁移后 RSS 比未跨界的 6 秒组高约 116 MiB,符合同时驻留 S256 block 图及更大 K/V host buffer 的预期。 + +解码 NPU 调用内含 Host↔NPU input/output buffer 提交、NPU 执行和同步等待,当前 RKNN Lite `inference()` API 没有为这三项提供可用的逐项计时。因此本报告不伪造“搬运 xx ms”:上述 `decoder NPU 总计`是三者的合计;CPU KV 写入和 CPU bucket copy 则已独立计时。后续若要拆出 DMA/H2D/D2H,需切换至 RKNN zero-copy / memory binding API 并对 buffer 生命周期单独埋点。 + +## 产物与可复现入口 + +核心 runtime 是 [board_run_end_to_end_dynamic_buckets.py](C:/Users/Administrator/Documents/ChatGPT/asr/board_run_end_to_end_dynamic_buckets.py),它新增 `--adapter-rknn` 并对 adapter token 数与 placeholder 数做硬校验。`export_audio_adapter_bucket.py` 已修正为按 `--tokens` 命名 ONNX,以生成 T25/T50/T75/T100 图。 + +原始板端结果与 CPU 标准参考: + +- [2 秒 RKNN 结果](C:/Users/Administrator/Documents/ChatGPT/asr/dynamic_length_s2.json)、[2 秒 FP32 参考](C:/Users/Administrator/Documents/ChatGPT/asr/standard_length_s2.json) +- [4 秒 RKNN 结果](C:/Users/Administrator/Documents/ChatGPT/asr/dynamic_length_s4.json)、[4 秒 FP32 参考](C:/Users/Administrator/Documents/ChatGPT/asr/standard_length_s4.json) +- [6 秒 RKNN 结果](C:/Users/Administrator/Documents/ChatGPT/asr/dynamic_length_s6.json)、[6 秒 FP32 参考](C:/Users/Administrator/Documents/ChatGPT/asr/standard_length_s6.json) +- [8 秒动态跨 bucket 结果](C:/Users/Administrator/Documents/ChatGPT/asr/dynamic_length_s8.json) +- [FP32 参考生成工具](C:/Users/Administrator/Documents/ChatGPT/asr/generate_length_standard_reference.py) + +## 二级迁移压力验收:256→512 + +在上述自然 ASR 验收之外,增加了一组**缓存调度压力测试**,目的不是评价 EOS 后文本的语义,而是让同一条真实 6 秒音频的 NPU/FP32 decoder 在首次 EOS 后按 greedy 规则继续运行,以构造超过 256 的真实 KV context。两端均执行相同的“忽略 EOS、仍将当前 argmax token 回灌”规则;这不是正常用户可见的 ASR 解码模式。 + +| 项目 | 结果 | +|---|---| +| 起始 prefill 长度 | 85 | +| 后续 decoder step | 180 | +| 总 token(含 prefill 首 token) | 181 | +| 实际迁移 | `128→256`(11.21 ms);`256→512`(34.70 ms) | +| 最终 bucket | 512 | +| NPU 对 FP32 token | **181/181 完全一致** | +| 首个不一致 token | 无 | +| NPU decoder 合计 | 32611.01 ms | +| CPU KV 原地写入合计 | 61.94 ms | +| 稳态全链路合计 | 33321.24 ms | +| 结束 RSS | 1379.02 MiB | + +这证明 S512 RKNN decoder block 可在迁移后读取由 CPU 复制的 rank-3 K/V buffer,并维持与 FP32 相同的贪心轨迹。其结果是**二级 cache 调度的功能和数值验收通过**;但由于该轨迹被明确设计为 EOS 后继续回灌,不能将它误解为自然语音的 WER/CER 验收。自然 ASR 部分的质量结论仍以 2/4/6/8 秒四条真实 EOS 样本的 0% NPU-vs-FP32 CER/WER 为准。 + +对应的可复现产物: + +- [256→512 板端压力结果]() +- [对应 FP32 强制延长参考]() +- [FP32 强制延长参考生成器]() diff --git a/encoder/audio_encoder_f800_fp16.rknn b/encoder/audio_encoder_f800_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..24fe192013b5c64ba2d1dcb3b4c3ef41e036131c --- /dev/null +++ b/encoder/audio_encoder_f800_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eba40833fd2111d06089ac6d34fd9c418a37bc96167c4d0a1353941541a5e19a +size 389160766 diff --git a/prefill/prefill_kv_s110_fp16.rknn b/prefill/prefill_kv_s110_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..4c681ab6d8ccb72e7ac61d3be7a3b54940f7cef8 --- /dev/null +++ b/prefill/prefill_kv_s110_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09386971d142ab6a801b725503b338f824cd5ad0547b5127533320e2e9ad0b0a +size 212455543 diff --git a/prefill/prefill_kv_s35_fp16.rknn b/prefill/prefill_kv_s35_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..48989a811bce538c1a040d8e25d0ec886295b2a6 --- /dev/null +++ b/prefill/prefill_kv_s35_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fae162d02756b897e454e41953a5e04bf78c9fef7f99e94449f396235b8ae2b0 +size 211989734 diff --git a/prefill/prefill_kv_s60_fp16.rknn b/prefill/prefill_kv_s60_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..b81a67674430dd3ad86c5cd2ccad1062bc307ea9 --- /dev/null +++ b/prefill/prefill_kv_s60_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99441a3ca5fb91cfad374f74513f14a3f8d28adf4f0fb25b313766d2de28fbd5 +size 211846566 diff --git a/prefill/prefill_kv_s85_fp16.rknn b/prefill/prefill_kv_s85_fp16.rknn new file mode 100644 index 0000000000000000000000000000000000000000..fc37afa3642631c784ebcea1ec130e7091faafc8 --- /dev/null +++ b/prefill/prefill_kv_s85_fp16.rknn @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b29935978e8a3fd06cd795e87de6fae4fd6facffc52e31fb525c92c09397263 +size 212251686 diff --git a/release-manifest.json b/release-manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..229514c0bcc15e0577aa933bdfd1dff606120151 --- /dev/null +++ b/release-manifest.json @@ -0,0 +1,9 @@ +{ + "release": "Audio8-ASR-0.1B RK3576 FP16 RKNN dynamic-KV deployment", + "base_model": "Edge0/Audio8-ASR-0.1B", + "target": "RK3576 / RKNN Runtime 2.3.2 / RKNPU driver 0.9.8", + "cache_layout": "8 layers x K/V [8, Smax, 64] FP32, CPU-owned", + "cache_buckets": [128, 256, 512], + "excluded": ["upstream weights", "audio", "ONNX", "calibration tensors"], + "source_archive": "https://github.com/Aaaou/Audio8-ASR-RKNN/tree/rknn-dynamic-kv-cache-validated" +} diff --git a/runtime/board_run_end_to_end_dynamic_buckets.py b/runtime/board_run_end_to_end_dynamic_buckets.py new file mode 100644 index 0000000000000000000000000000000000000000..3cd070df2d351419eaf0ba980d607786ecd72da6 --- /dev/null +++ b/runtime/board_run_end_to_end_dynamic_buckets.py @@ -0,0 +1,47 @@ +"""Full RKNN ASR with CPU-owned KV cache that grows across 128/256/512 buckets.""" +import argparse,json,time +from pathlib import Path +import numpy as np +from rknnlite.api import RKNNLite +def load(p): + r=RKNNLite(verbose=False);assert r.load_rknn(str(p))==0 and r.init_runtime()==0;return r +def rss(): + for x in Path('/proc/self/status').read_text().splitlines(): + if x.startswith('VmRSS:'):return int(x.split()[1])/1024 +def st(x): + a=np.asarray(x)*1000;return {'mean':float(a.mean()),'p50':float(np.percentile(a,50)),'p95':float(np.percentile(a,95))} +a=argparse.ArgumentParser();a.add_argument('--input-dir',type=Path,required=True);a.add_argument('--prefill-rknn',type=Path,required=True);a.add_argument('--asset-root',type=Path,help='release root; uses legacy board paths when omitted');a.add_argument('--adapter-rknn',type=Path);a.add_argument('--model',type=Path,required=True);a.add_argument('--max-new',type=int,default=400);a.add_argument('--ignore-eos',action='store_true',help='scheduler stress mode: continue greedy decoding after EOS');a.add_argument('--output',type=Path,required=True);z=a.parse_args();inp=z.input_dir;cfg=json.loads((z.model/'config.json').read_text());eos={cfg['eos_token_id']} if isinstance(cfg['eos_token_id'],int) else set(cfg['eos_token_id']);allr=[];before=rss();total=time.perf_counter() +root=z.asset_root +def asset(release,legacy): return root/release if root else Path(legacy) +adapter=z.adapter_rknn or asset('adapter/audio_adapter_h104_t100_fp16.rknn','/root/audio8-asr/audio_adapter_h104_t100/audio_adapter_h104_t100_fp16.rknn') +try: + enc=load(asset('encoder/audio_encoder_f800_fp16.rknn','/root/audio8-asr/encoder_f800_v2/audio_encoder_f800_fp16.rknn'));adp=load(adapter);pre=load(z.prefill_rknn);allr=[enc,adp,pre] + mel=np.load(inp/'input_features.npy').astype('float32');ids=np.load(inp/'input_ids.npy').reshape(-1);audio_pos=np.load(inp/'audio_positions.npy').reshape(-1);table=np.load(asset('token_embeddings_fp32.npy','/root/audio8-asr/decoder_3d_fixed_s256/token_embeddings_fp32.npy'),mmap_mode='r');t=time.perf_counter();e=enc.inference(inputs=[mel])[0];enc_s=time.perf_counter()-t;t=time.perf_counter();audio=adp.inference(inputs=[e])[0];adp_s=time.perf_counter()-t + if len(audio) != len(audio_pos): raise RuntimeError(f'adapter tokens {len(audio)} != audio placeholders {len(audio_pos)}') + embed=np.asarray(table[ids],np.float32)[None,:,:];embed[0,audio_pos,:]=audio;t=time.perf_counter();po=pre.inference(inputs=[embed]);pre_s=time.perf_counter()-t + for r in allr:r.release() + allr=[];kvs=[load(asset(f'decoder/kv/layer{i}/kv_fp16.rknn',f'/root/audio8-asr/decoder_3d_fixed_s128/layer{i}/kv_fp16.rknn')) for i in range(8)];heads=[load(asset(f'decoder/head_shards/shard{i:02d}/head_fp16.rknn',f'/root/audio8-asr/decoder_3d_fixed_s128/head_shards/shard{i:02d}/head_fp16.rknn')) for i in range(8)];allr=kvs+heads;blocks={};capacity=128 + def get_blocks(c): + if c not in blocks: + blocks[c]=[load(asset(f'decoder/block_s{c}/layer{i}/block_fp16.rknn',f'/root/audio8-asr/decoder_3d_fixed_s{c}/layer{i}/block_fp16.rknn')) for i in range(8)];allr.extend(blocks[c]) + return blocks[c] + initial=po[1].shape[-2];ck=[];cv=[] + for i in range(8): + k=np.zeros((8,capacity,64),np.float32);v=np.zeros_like(k);k[:,:initial]=po[1+2*i].reshape(8,initial,64);v[:,:initial]=po[2+2*i].reshape(8,initial,64);ck.append(k);cv.append(v) + inv=np.load(inp/'rotary_inv_freq.npy').astype('float32');token=int(np.asarray(po[0]).argmax());tokens=[token];switches=[];kv_t=[];block_t=[];head_t=[];write_t=[] + for step in range(z.max_new): + if step and token in eos and not z.ignore_eos:break + pos=initial+step + if pos>=capacity: + next_c=256 if capacity==128 else 512 if capacity==256 else None + if next_c is None:raise RuntimeError(f'no bucket above {capacity}') + t=time.perf_counter();newk=[np.pad(x,((0,0),(0,next_c-capacity),(0,0))) for x in ck];newv=[np.pad(x,((0,0),(0,next_c-capacity),(0,0))) for x in cv];mig=time.perf_counter()-t;switches.append({'at_position':pos,'from':capacity,'to':next_c,'cpu_cache_copy_ms':mig*1000});ck,cv,capacity=newk,newv,next_c + bl=get_blocks(capacity);x=np.asarray(table[token:token+1],np.float32).reshape(1,1,512);f=np.concatenate((inv*pos,inv*pos))[None,None,:];co=np.cos(f).astype('float32');si=np.sin(f).astype('float32');mask=np.full((8,1,capacity),-10000,np.float32);mask[:,:,:pos+1]=0 + for i in range(8): + t=time.perf_counter();dk,dv=kvs[i].inference(inputs=[x,co,si]);kv_t.append(time.perf_counter()-t);t=time.perf_counter();ck[i][:,pos:pos+1]=dk;cv[i][:,pos:pos+1]=dv;write_t.append(time.perf_counter()-t);t=time.perf_counter();x=bl[i].inference(inputs=[x,ck[i],cv[i],co,si,mask])[0];block_t.append(time.perf_counter()-t) + pieces=[] + for h in heads:t=time.perf_counter();pieces.append(h.inference(inputs=[x])[0]);head_t.append(time.perf_counter()-t) + token=int(np.concatenate(pieces,-1).argmax());tokens.append(token) + out={'complete_to_eos':tokens[-1] in eos,'scheduler_stress_ignore_eos':z.ignore_eos,'eos_ids':list(eos),'tokens':tokens,'generated_tokens_excluding_prefill_token':len(tokens)-1,'bucket_switches':switches,'final_bucket':capacity,'latency_ms':{'encoder':enc_s*1000,'adapter':adp_s*1000,'prefill':pre_s*1000,'kv_npu':st(kv_t),'block_npu':st(block_t),'head_npu':st(head_t),'cpu_kv_write':st(write_t),'decoder_npu_total':float((sum(kv_t)+sum(block_t)+sum(head_t))*1000),'decoder_cpu_kv_total':float(sum(write_t)*1000),'steady_end_to_end_total':float((enc_s+adp_s+pre_s+sum(kv_t)+sum(block_t)+sum(head_t)+sum(write_t)+sum(x['cpu_cache_copy_ms']/1000 for x in switches))*1000),'process_wall_total':float((time.perf_counter()-total)*1000)},'rss_mib':{'before':before,'after':rss()}};z.output.write_text(json.dumps(out,indent=2));print(json.dumps(out,indent=2)) +finally: + for r in allr:r.release() diff --git a/runtime/requirements-rk3576.txt b/runtime/requirements-rk3576.txt new file mode 100644 index 0000000000000000000000000000000000000000..07ef5eb81e2acb7b81374ce576a66ce4691bfd45 --- /dev/null +++ b/runtime/requirements-rk3576.txt @@ -0,0 +1,2 @@ +numpy>=1.24 +rknn-toolkit-lite2==2.3.2 diff --git a/token_embeddings_fp32.npy b/token_embeddings_fp32.npy new file mode 100644 index 0000000000000000000000000000000000000000..e648c502e2b24b089cb000b46e793084255e439f --- /dev/null +++ b/token_embeddings_fp32.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8d6949be746c1e11335250a67ef9c58f7b01e2fcc89edf265743a8484114ac5 +size 311165056