# RDK X5 BPU 量化流程

这个目录用于把 Gym/RobotLab 的 ONNX 策略转成 RDK X5 可运行的 Horizon
runtime `.bin`。量化在 Mac 上用 CPU Docker 完成，板端可以做离线测速和实机
部署测试。

参考资料：

- D-Robotics 官方 X5 工具链说明：`https://developer.d-robotics.cc/rdk_x_doc/Advanced_development/toolchain_development/overview?v=3.5.0&p=RDK+X5`
- D-Robotics 论坛 WTW/Go2/X5 流程：`https://forum.d-robotics.cc/t/topic/28338`

官方工具链对 ONNX 的关键限制是：`ir_version <= 7`、`opset10/11`、固定
4 维输入，且 N 维只能为 1。因此这里不能直接拿原始 ONNX 编译，需要先裁剪
actions-only 输出、降级 opset，再把 `[1, D]` 输入包成固定 4D `NCHW`：
Gym 5 帧是 `[1, 1, 1, 225]`，RobotLab 10 帧是 `[1, 1, 1, 450]`。

## 当前状态

新增 Gym 5 帧策略的一键量化入口，默认目标是：

- 原始模型：`../policy_35k.onnx`
- 原始输入：`obs [1, 225]`
- BPU 编译输入：`obs_4d [1, 1, 1, 225]`
- BPU 输出：`actions [1, 12, 1, 1]`
- 默认输出：`mapper_output_35k_gemm/policy_35k_int16_gemm.bin`
- 默认校准数据：`calibration_data_35k_gym_fast64/`

本机 Docker 已完成一次默认量化：

- 浮点 4D/Gemm 图等价对比：64 个真实样本上 `max_abs_diff = 7.15e-7`
- `hb_mapper makertbin` 输出：`actions` cosine `0.998524`，L1 `0.014313`，L2 `0.005103`，Chebyshev `0.040004`
- 编译估计延迟：`463.9 us`
- 产物大小：`2.0M`
- 产物路径：`deploy_45dim_rl_gym/bpu_quantization/mapper_output_35k_gemm/policy_35k_int16_gemm.bin`

一键量化命令：

```bash
cd /Users/chenyouyuan/cyy_ws/deploy_go1_pro/deploy_45dim_rl_gym/bpu_quantization
./quantize_policy_x5.sh
```

切换其他 Gym 轮次时直接指定模型和 round：

```bash
./quantize_policy_x5.sh --policy ../policy_30k.onnx --round 30k
./quantize_policy_x5.sh --policy ../policy_25k.onnx --round 25k
./quantize_policy_x5.sh --policy ../policy_15k.onnx --round 15k
```

脚本默认只抽 64 个真实 RL 样本做校准和最多 64 个样本做浮点等价对比，避免
之前 512 样本和 batch 回退导致的量化流程过慢。需要更稳的校准时再手动加大：

```bash
./quantize_policy_x5.sh --samples 128 --compare-limit 128
```

Gym BPU 部署入口支持快速切换轮次：

```bash
cd /root/go1_pro_deploy
PYTHONPATH=/root/go1_pro_deploy python3 deploy_45dim_rl_gym/deploy_go1_rlgym_bpu_x5_fastcpp.py \
  --bpu-round 35k \
  --kill-sport \
  --enable-rl \
  --log-dir logs \
  --kp 32 --kd 1.0 \
  --kp-cal 20 --kd-cal 1.0 \
  --power-factor 9 \
  --position-protect-limit 0.0 \
  --action-clip 6.5 \
  --action-trip-limit 8.0 \
  --action-hard-trip-limit 16.0 \
  --max-target-step 0.0 \
  --max-roll-deg 50 \
  --max-pitch-deg 50 \
  --swap-vy-yaw \
  --rc-vx-scale 0.5 \
  --rc-vy-scale 0.5 \
  --rc-wz-scale 1.0 \
  --log-timing
```

也可以直接指定 bin：

```bash
PYTHONPATH=/root/go1_pro_deploy python3 deploy_45dim_rl_gym/deploy_go1_rlgym_bpu_x5_fastcpp.py \
  --bpu-model deploy_45dim_rl_gym/bpu_quantization/mapper_output_35k_gemm/policy_35k_int16_gemm.bin \
  --infer-check --max-steps 1000 --log-timing
```

板端离线测速：

```bash
cd /root/go1_pro_deploy
PYTHONPATH=/root/go1_pro_deploy python3 deploy_45dim_rl_gym/bpu_deploy_x5/test_bpu_policy.py \
  --bpu-model deploy_45dim_rl_gym/bpu_quantization/mapper_output_35k_gemm/policy_35k_int16_gemm.bin \
  --input-bin deploy_45dim_rl_gym/bpu_quantization/calibration_data_35k_gym_fast64/00000.bin \
  --repeat 1000

cd /root/go1_pro_deploy/deploy_45dim_rl_gym/bpu_deploy_x5/cpp
./bpu_dnn_bench \
  /root/go1_pro_deploy/deploy_45dim_rl_gym/bpu_quantization/mapper_output_35k_gemm/policy_35k_int16_gemm.bin \
  /root/go1_pro_deploy/deploy_45dim_rl_gym/bpu_quantization/calibration_data_35k_gym_fast64/00000.bin \
  1000
```

已经完成 `policy_robotlab_15000.onnx` 和 `policy_robotlab_6500.onnx` 的 int16
量化。RobotLab BPU 部署默认仍使用 6500 版本：

- 原始模型：`../policy_robotlab_6500.onnx`
- 原始输入：`obs [1, 450]`
- BPU 编译输入：`obs_4d [1, 1, 1, 450]`
- BPU 输出：`actions [1, 12, 1, 1]`
- Docker 镜像：`openexplorer/ai_toolchain_ubuntu_20_x5_cpu:v1.2.8`
- `hb_mapper`：`1.24.3`
- `hbdk`：`3.49.15`
- 当前产物：`mapper_output_6500_gemm/policy_robotlab_6500_int16_gemm.bin`

`mapper_output*/`、`.hb_check/`、校准数据、中间 ONNX、`hb_mapper` 日志都被
`.gitignore` 忽略；需要时按下面步骤重新生成。仓库里只保留脚本和 YAML 配置。

6500 量化使用 `calibration_data_fast64/` 的 64 个真实样本。原因是
`hb_mapper` 会先尝试 calibration batch 8，但当前 4D featuremap 包装会被工具链
内部改成固定 batch 的 reshape，batch 8 失败后会退回 batch 1；用 64 样本可以把
校准时间从 512 次 batch1 显著降下来。

## 1. 生成校准数据

在本机 `deploy_go1_pro` 根目录执行：

```bash
/Users/chenyouyuan/miniconda/envs/free_dog_sdk/bin/python3.10 \
  deploy_45dim_rl_gym/bpu_quantization/make_calibration_data.py \
  --max-samples 512 \
  --overwrite
```

脚本会从 `logs/robotlab_go1_deploy_*/steps.jsonl` 中提取 RL 状态下的真实
`obs_single`，按部署代码相同的 10 帧历史顺序重建 450 维输入，并写成
`float32` feature-map `.bin`。

本次校准数据：

- 候选 RL 输入：`62606`
- 选中样本：`512`
- 单个样本形状：`1x1x1x450`
- 元数据：`deploy_45dim_rl_gym/bpu_quantization/calibration_data_metadata.json`

## 2. 生成 opset11 和 4D ONNX

启动 Docker Desktop 后，在 `deploy_go1_pro` 根目录执行：

```bash
docker run --rm --platform linux/amd64 \
  -v "$PWD:/workspace/deploy_go1_pro" \
  openexplorer/ai_toolchain_ubuntu_20_x5_cpu:v1.2.8 \
  bash -lc 'cd /workspace/deploy_go1_pro/deploy_45dim_rl_gym/bpu_quantization &&
    python3 downgrade_policy_to_opset11.py \
      --input ../policy_robotlab_15000.onnx \
      --output policy_robotlab_15000_opset11.onnx &&
    python3 make_bpu_4d_onnx.py \
      --input policy_robotlab_15000_opset11.onnx \
      --output policy_robotlab_15000_bpu4d.onnx &&
    python3 compare_4d_onnx.py \
      --flat-onnx ../policy_robotlab_15000.onnx \
      --bpu4d-onnx policy_robotlab_15000_bpu4d.onnx \
      --limit 256'
```

本次对比结果：256 个真实输入上 `max_abs_diff = 0`，说明降级和 4D 包装没有
改变浮点 ONNX 输出。

## 3. 可选：替换 MoE grouped Conv

原始导出的 MoE expert 末端有一个 `group=8, kernel=1` 的 `Conv1d`：

```text
[1, 2048] -> Unsqueeze -> Conv(group=8, kernel=1) -> Squeeze -> [1, 256]
```

在 X5 checker 里，这段会切到 CPU float，导致 BPU 子图被拆开。这个 Conv
等价于一个块对角 `Gemm`，可以不重训直接改图：

```bash
docker run --rm --platform linux/amd64 \
  -v "$PWD:/workspace/deploy_go1_pro" \
  openexplorer/ai_toolchain_ubuntu_20_x5_cpu:v1.2.8 \
  bash -lc 'cd /workspace/deploy_go1_pro/deploy_45dim_rl_gym/bpu_quantization &&
    python3 replace_group_conv_with_gemm.py \
      --input policy_robotlab_15000_bpu4d.onnx \
      --output policy_robotlab_15000_bpu4d_gemm.onnx &&
    python3 compare_4d_onnx.py \
      --flat-onnx ../policy_robotlab_15000.onnx \
      --bpu4d-onnx policy_robotlab_15000_bpu4d_gemm.onnx \
      --limit 512'
```

本次浮点对比结果：

- `max_abs_diff = 1.43e-6`
- `max_mean_abs_diff = 3.53e-7`

这是浮点舍入级误差，可以认为图变换等价。

注意：这里没有把 `ReduceL2/Pow/Reshape/Sqrt` 这类 norm 算子挪到后处理，
因为它们在 actor 之前，属于策略中间计算，不能当成输出后处理裁掉。当前
`hb_mapper makertbin` 已经能把这些 norm 节点放到 BPU 上。

## 4. checker 检查

```bash
docker run --rm --platform linux/amd64 \
  -v "$PWD:/workspace/deploy_go1_pro" \
  openexplorer/ai_toolchain_ubuntu_20_x5_cpu:v1.2.8 \
  bash -lc 'cd /workspace/deploy_go1_pro/deploy_45dim_rl_gym/bpu_quantization &&
    hb_mapper checker \
      --model policy_robotlab_15000_bpu4d_gemm.onnx \
      --model-type onnx \
      --march bayes-e \
      --input-shape obs_4d 1x1x1x450'
```

checker 可以通过。原始模型中 MoE expert 的 `Elu/Reshape/Conv` CPU fallback
会被消掉；checker 阶段仍可能显示 gating `Softmax` 或输出 reshape 的 CPU
适配，正式 int16 编译时 `Softmax` 会被量化拆成 BPU 子算子。

## 5. 编译 int16 `.bin`

```bash
docker run --rm --platform linux/amd64 \
  -v "$PWD:/workspace/deploy_go1_pro" \
  openexplorer/ai_toolchain_ubuntu_20_x5_cpu:v1.2.8 \
  bash -lc 'cd /workspace/deploy_go1_pro/deploy_45dim_rl_gym/bpu_quantization &&
    hb_mapper makertbin \
      --config policy_robotlab_15000_int16_gemm.yaml \
      --model-type onnx'
```

优化版 `hb_mapper` 输出的量化精度：

```text
Output   Cosine Similarity   L1 Distance   L2 Distance   Chebyshev Distance
actions  0.999910            0.009500      0.003393      0.024229
```

优化版正式 int16 编译后，日志中列出的策略节点全部在同一个 BPU 子图
`id(0)` 上，原始模型的 CPU `Elu/Reshape/Conv` fallback 已消失。

优化版 BPU 子图编译估计延迟：

- subgraph0：`424.5 us`

原始版 BPU 子图估计延迟是 `284.3 us + 80.8 us`，但带 CPU/hybrid 切换。
优化版单子图的编译估计延迟略高，实际是否更快要以板端 `hrt_model_exec perf`
为准。

## 6. 板端离线验证

把 `.bin` 和一个校准输入传到板端，例如：

```bash
ssh root@192.168.150.167 'mkdir -p /root/go1_pro_deploy/bpu_quant_test'
scp \
  deploy_45dim_rl_gym/bpu_quantization/mapper_output_gemm/policy_robotlab_15000_int16_gemm.bin \
  deploy_45dim_rl_gym/bpu_quantization/calibration_data/00000.bin \
  root@192.168.150.167:/root/go1_pro_deploy/bpu_quant_test/
```

板端查看模型：

```bash
cd /root/go1_pro_deploy/bpu_quant_test
hrt_model_exec model_info --model_file policy_robotlab_15000_int16_gemm.bin
```

板端测速：

```bash
hrt_model_exec perf \
  --model_file policy_robotlab_15000_int16_gemm.bin \
  --model_name policy_robotlab_15000_int16_gemm \
  --input_file 00000.bin \
  --frame_count 1000 \
  --thread_num 1
```

原始版板端结果：

- 平均延迟：`1.498257 ms`
- 最大延迟：`2.942 ms`
- FPS：`664.77`

优化版板端结果：

- 平均延迟：`0.872819 ms`
- 最大延迟：`1.627 ms`
- 最小延迟：`0.613 ms`
- FPS：`1133.28`

同一板端之前测 CPU ONNX 大约是 `1.63 ms`。原始 BPU hybrid `.bin` 只快了一点；
Gemm 优化版移掉 MoE 中间 CPU fallback 后，板端离线平均延迟比 CPU ONNX 快约
46%，比原始 BPU hybrid 快约 42%。

原始版板端单样本输出与本机浮点 ONNX 对比：

- `max_abs = 0.02545`
- `mean_abs = 0.01040`
- `l2 = 0.04584`

优化版板端单样本输出与本机浮点 ONNX 对比：

- `max_abs = 0.02528`
- `mean_abs = 0.00962`
- `l2 = 0.04187`

这个误差对离线验证是可接受的，但还不足以直接上实机。

## 7. 安全结论

当前建议只做离线推理验证，不要把 `.bin` 接入真实机器人控制循环。原因：

- 原始 BPU 端到端速度提升很小，不能解决目前右前腿掉线、力矩保护、上下坡不稳这些核心问题。
- Gemm 优化版已经改善离线推理速度，但还没有做部署代码适配和悬空状态机测试。
- 输出 shape 从 `[1, 12]` 变成 `[1, 12, 1, 1]`，部署代码需要单独适配。
- 接入前至少要做更多 held-out 真实日志对比、悬空状态机测试，再进入地面低速测试。
