adapt gym 5history
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
- `deploy_go1_rlgym_pro_sdk_fastcpp.py`:真机 Go1 PRO 低层部署脚本,独立使用 C++ LowCmd 构包/加密后端
|
||||
- `deploy_go1_rlgym_pro_sdk_lab.py`:RobotLab 策略真机部署脚本
|
||||
- `deploy_go1_rlgym_pro_sdk_lab_fastcpp.py`:RobotLab 策略真机部署脚本,独立使用 C++ LowCmd 构包/加密后端
|
||||
- `deploy_go1_rlgym_bpu_x5_fastcpp.py`:Gym 5 帧 BPU 真机部署脚本,使用 C++ LowCmd/LowState 热路径
|
||||
|
||||
该 ONNX 策略使用 45 维单帧观测,以及 5 帧、共 225 维的历史输入,按观测项分组堆叠:
|
||||
|
||||
@@ -52,6 +53,14 @@ cd /root/go1_pro_sdk/fast_lowcmd_cpp
|
||||
PYTHONPATH=/root/go1_pro_sdk python3 setup.py build_ext --inplace
|
||||
```
|
||||
|
||||
Gym 5 帧 BPU 部署入口默认走 `policy_35k` 对应的 `mapper_output_35k_gemm/policy_35k_int16_gemm.bin`,
|
||||
也可以显式切换:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=/root/go1_pro_deploy python3 deploy_45dim_rl_gym/deploy_go1_rlgym_bpu_x5_fastcpp.py --bpu-round 35k
|
||||
PYTHONPATH=/root/go1_pro_deploy python3 deploy_45dim_rl_gym/deploy_go1_rlgym_bpu_x5_fastcpp.py --bpu-round 30k
|
||||
```
|
||||
|
||||
## 安全说明
|
||||
|
||||
直接低层控电机是危险操作。
|
||||
|
||||
@@ -59,8 +59,6 @@ class BpuInferLibPolicy:
|
||||
"""Default fast backend: direct C++ DNN API via ctypes."""
|
||||
|
||||
backend_name = "cpp_dnn_api_x5"
|
||||
input_shape = (1, 1, 1, 450)
|
||||
output_shape = (1, 12, 1, 1)
|
||||
|
||||
def __init__(self, model_path, priority=0, bpu_cores=(0,), cpp_lib=DEFAULT_CPP_LIB):
|
||||
self.model_path = Path(model_path).expanduser().resolve()
|
||||
@@ -94,6 +92,10 @@ class BpuInferLibPolicy:
|
||||
self._lib.rlgym_bpu_infer.restype = ctypes.c_int
|
||||
self._lib.rlgym_bpu_destroy.argtypes = [ctypes.c_void_p]
|
||||
self._lib.rlgym_bpu_destroy.restype = None
|
||||
self._lib.rlgym_bpu_input_floats.argtypes = [ctypes.c_void_p]
|
||||
self._lib.rlgym_bpu_input_floats.restype = ctypes.c_int
|
||||
self._lib.rlgym_bpu_output_floats.argtypes = [ctypes.c_void_p]
|
||||
self._lib.rlgym_bpu_output_floats.restype = ctypes.c_int
|
||||
self._lib.rlgym_bpu_version.argtypes = []
|
||||
self._lib.rlgym_bpu_version.restype = ctypes.c_char_p
|
||||
|
||||
@@ -107,7 +109,13 @@ class BpuInferLibPolicy:
|
||||
)
|
||||
if not self._handle:
|
||||
raise RuntimeError(err.value.decode("utf-8", errors="replace"))
|
||||
self._output = np.empty(12, dtype=np.float32)
|
||||
self.input_size = int(self._lib.rlgym_bpu_input_floats(self._handle))
|
||||
self.output_size = int(self._lib.rlgym_bpu_output_floats(self._handle))
|
||||
if self.input_size <= 0 or self.output_size <= 0:
|
||||
raise RuntimeError("invalid BPU tensor sizes reported by runtime")
|
||||
self.input_shape = (1, 1, 1, self.input_size)
|
||||
self.output_shape = (1, self.output_size, 1, 1)
|
||||
self._output = np.empty(self.output_size, dtype=np.float32)
|
||||
self._output_ptr = self._output.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
|
||||
self._err = ctypes.create_string_buffer(1024)
|
||||
|
||||
@@ -131,8 +139,8 @@ class BpuInferLibPolicy:
|
||||
|
||||
def __call__(self, flat_input):
|
||||
arr = np.asarray(flat_input, dtype=np.float32)
|
||||
if arr.size != 450:
|
||||
raise ValueError(f"BPU policy input has {arr.size} values, expected 450")
|
||||
if arr.size != self.input_size:
|
||||
raise ValueError(f"BPU policy input has {arr.size} values, expected {self.input_size}")
|
||||
input_flat = np.ascontiguousarray(arr.reshape(-1), dtype=np.float32)
|
||||
rc = self._lib.rlgym_bpu_infer(
|
||||
self._handle,
|
||||
@@ -172,6 +180,9 @@ class BpuInferLibPythonPolicy:
|
||||
self.priority = int(priority)
|
||||
self.bpu_cores = tuple(int(core) for core in bpu_cores)
|
||||
self.suppress_runtime_output = bool(suppress_runtime_output)
|
||||
self.input_size = 450
|
||||
self.input_shape = (1, 1, 1, self.input_size)
|
||||
self.output_shape = (1, 12, 1, 1)
|
||||
with suppress_c_output(self.suppress_runtime_output):
|
||||
self.infer = Infer(False)
|
||||
loaded = self.infer.load_model(str(self.model_path))
|
||||
@@ -185,8 +196,8 @@ class BpuInferLibPythonPolicy:
|
||||
|
||||
def __call__(self, flat_input):
|
||||
arr = np.asarray(flat_input, dtype=np.float32)
|
||||
if arr.size != 450:
|
||||
raise ValueError(f"BPU policy input has {arr.size} values, expected 450")
|
||||
if arr.size != self.input_size:
|
||||
raise ValueError(f"BPU policy input has {arr.size} values, expected {self.input_size}")
|
||||
input_4d = np.ascontiguousarray(arr.reshape(self.input_shape), dtype=np.float32)
|
||||
with suppress_c_output(self.suppress_runtime_output):
|
||||
copied = self.infer.read_numpy_arr_float32(input_4d, 0)
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
extern "C" {
|
||||
void *rlgym_bpu_create(const char *model_path, int bpu_core, int priority,
|
||||
char *err, int err_len);
|
||||
int rlgym_bpu_infer(void *handle, const float *input450, float *output12, char *err,
|
||||
int rlgym_bpu_infer(void *handle, const float *input, float *output, char *err,
|
||||
int err_len);
|
||||
int rlgym_bpu_input_floats(void *handle);
|
||||
int rlgym_bpu_output_floats(void *handle);
|
||||
void rlgym_bpu_destroy(void *handle);
|
||||
}
|
||||
|
||||
@@ -54,12 +56,6 @@ int main(int argc, char **argv) {
|
||||
if (argc > 2) input = argv[2];
|
||||
if (argc > 3) repeat = std::atoi(argv[3]);
|
||||
|
||||
std::vector<float> obs;
|
||||
if (!read_f32_file(input, &obs) || obs.size() != 450) {
|
||||
std::cerr << "failed to read 450 float32 input: " << input << "\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
char err[1024] = {};
|
||||
void *handle = rlgym_bpu_create(model, 1, 0, err, sizeof(err));
|
||||
if (handle == nullptr) {
|
||||
@@ -67,20 +63,37 @@ int main(int argc, char **argv) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
float out[12] = {};
|
||||
if (rlgym_bpu_infer(handle, obs.data(), out, err, sizeof(err)) != 0) {
|
||||
const int input_floats = rlgym_bpu_input_floats(handle);
|
||||
const int output_floats = rlgym_bpu_output_floats(handle);
|
||||
if (input_floats <= 0 || output_floats <= 0) {
|
||||
std::cerr << "invalid tensor sizes from BPU runtime\n";
|
||||
rlgym_bpu_destroy(handle);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::vector<float> obs;
|
||||
if (!read_f32_file(input, &obs) || static_cast<int>(obs.size()) != input_floats) {
|
||||
std::cerr << "failed to read " << input_floats << " float32 input: " << input << "\n";
|
||||
rlgym_bpu_destroy(handle);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::vector<float> out(static_cast<size_t>(output_floats), 0.0f);
|
||||
if (rlgym_bpu_infer(handle, obs.data(), out.data(), err, sizeof(err)) != 0) {
|
||||
std::cerr << err << "\n";
|
||||
rlgym_bpu_destroy(handle);
|
||||
return 4;
|
||||
}
|
||||
float max_diff = 0.0f;
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
max_diff = std::max(max_diff, std::fabs(out[i] - kReference[i]));
|
||||
if (input_floats == 450 && output_floats == 12) {
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
max_diff = std::max(max_diff, std::fabs(out[static_cast<size_t>(i)] - kReference[i]));
|
||||
}
|
||||
}
|
||||
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
for (int i = 0; i < repeat; ++i) {
|
||||
if (rlgym_bpu_infer(handle, obs.data(), out, err, sizeof(err)) != 0) {
|
||||
if (rlgym_bpu_infer(handle, obs.data(), out.data(), err, sizeof(err)) != 0) {
|
||||
std::cerr << err << "\n";
|
||||
rlgym_bpu_destroy(handle);
|
||||
return 5;
|
||||
@@ -90,10 +103,13 @@ int main(int argc, char **argv) {
|
||||
const double elapsed_ms =
|
||||
std::chrono::duration<double, std::milli>(t1 - t0).count();
|
||||
|
||||
std::cout << "reference_max_abs_diff " << max_diff << "\n";
|
||||
std::cout << "input_floats=" << input_floats << " output_floats=" << output_floats << "\n";
|
||||
if (input_floats == 450 && output_floats == 12) {
|
||||
std::cout << "reference_max_abs_diff " << max_diff << "\n";
|
||||
}
|
||||
std::cout << "repeat=" << repeat << " cpp_avg_ms=" << (elapsed_ms / repeat) << "\n";
|
||||
std::cout << "action0=" << out[0] << " action_max_abs="
|
||||
<< *std::max_element(out, out + 12, [](float a, float b) {
|
||||
<< *std::max_element(out.begin(), out.end(), [](float a, float b) {
|
||||
return std::fabs(a) < std::fabs(b);
|
||||
})
|
||||
<< "\n";
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kInputFloats = 450;
|
||||
constexpr int kOutputFloats = 12;
|
||||
|
||||
void set_error(char *err, int err_len, const std::string &msg) {
|
||||
@@ -88,10 +87,10 @@ class BpuDnnPolicy {
|
||||
check_dnn(hbDNNGetOutputTensorProperties(&output_tensors_[0].properties, dnn_handle_, 0),
|
||||
"hbDNNGetOutputTensorProperties");
|
||||
|
||||
validate_tensor(input_tensors_[0].properties, kInputFloats, HB_DNN_TENSOR_TYPE_F32,
|
||||
"input");
|
||||
validate_tensor(output_tensors_[0].properties, kOutputFloats, HB_DNN_TENSOR_TYPE_F32,
|
||||
"output");
|
||||
input_floats_ = element_count(input_tensors_[0].properties.validShape);
|
||||
output_floats_ = element_count(output_tensors_[0].properties.validShape);
|
||||
validate_tensor(input_tensors_[0].properties, HB_DNN_TENSOR_TYPE_F32, "input");
|
||||
validate_tensor(output_tensors_[0].properties, HB_DNN_TENSOR_TYPE_F32, "output");
|
||||
|
||||
alloc_tensor_mem(input_tensors_[0]);
|
||||
alloc_tensor_mem(output_tensors_[0]);
|
||||
@@ -112,7 +111,7 @@ class BpuDnnPolicy {
|
||||
}
|
||||
|
||||
auto &input_mem = input_tensors_[0].sysMem[0];
|
||||
std::memcpy(input_mem.virAddr, input, sizeof(float) * kInputFloats);
|
||||
std::memcpy(input_mem.virAddr, input, sizeof(float) * input_floats_);
|
||||
int code = hbSysFlushMem(&input_mem, HB_SYS_MEM_CACHE_CLEAN);
|
||||
if (code != 0) {
|
||||
throw std::runtime_error(dnn_error("hbSysFlushMem(input)", code));
|
||||
@@ -137,18 +136,15 @@ class BpuDnnPolicy {
|
||||
if (code != 0) {
|
||||
throw std::runtime_error(dnn_error("hbSysFlushMem(output)", code));
|
||||
}
|
||||
std::memcpy(output, output_mem.virAddr, sizeof(float) * kOutputFloats);
|
||||
std::memcpy(output, output_mem.virAddr, sizeof(float) * output_floats_);
|
||||
}
|
||||
|
||||
int input_floats() const { return input_floats_; }
|
||||
int output_floats() const { return output_floats_; }
|
||||
|
||||
private:
|
||||
static void validate_tensor(const hbDNNTensorProperties &props, int expected_count,
|
||||
int expected_type, const char *name) {
|
||||
const int count = element_count(props.validShape);
|
||||
if (count != expected_count) {
|
||||
std::ostringstream oss;
|
||||
oss << name << " valid element count " << count << " != " << expected_count;
|
||||
throw std::runtime_error(oss.str());
|
||||
}
|
||||
static void validate_tensor(const hbDNNTensorProperties &props, int expected_type,
|
||||
const char *name) {
|
||||
if (props.tensorType != expected_type) {
|
||||
std::ostringstream oss;
|
||||
oss << name << " tensor type " << props.tensorType << " != " << expected_type;
|
||||
@@ -182,6 +178,8 @@ class BpuDnnPolicy {
|
||||
std::string model_name_;
|
||||
std::vector<hbDNNTensor> input_tensors_;
|
||||
std::vector<hbDNNTensor> output_tensors_;
|
||||
int input_floats_{0};
|
||||
int output_floats_{0};
|
||||
int bpu_core_{HB_BPU_CORE_ANY};
|
||||
int priority_{HB_DNN_PRIORITY_LOWEST};
|
||||
};
|
||||
@@ -201,14 +199,14 @@ void *rlgym_bpu_create(const char *model_path, int bpu_core, int priority,
|
||||
}
|
||||
}
|
||||
|
||||
int rlgym_bpu_infer(void *handle, const float *input450, float *output12, char *err,
|
||||
int rlgym_bpu_infer(void *handle, const float *input, float *output, char *err,
|
||||
int err_len) {
|
||||
try {
|
||||
set_error(err, err_len, "");
|
||||
if (handle == nullptr) {
|
||||
throw std::runtime_error("null policy handle");
|
||||
}
|
||||
static_cast<BpuDnnPolicy *>(handle)->infer(input450, output12);
|
||||
static_cast<BpuDnnPolicy *>(handle)->infer(input, output);
|
||||
return 0;
|
||||
} catch (const std::exception &e) {
|
||||
set_error(err, err_len, e.what());
|
||||
@@ -216,6 +214,20 @@ int rlgym_bpu_infer(void *handle, const float *input450, float *output12, char *
|
||||
}
|
||||
}
|
||||
|
||||
int rlgym_bpu_input_floats(void *handle) {
|
||||
if (handle == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<BpuDnnPolicy *>(handle)->input_floats();
|
||||
}
|
||||
|
||||
int rlgym_bpu_output_floats(void *handle) {
|
||||
if (handle == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<BpuDnnPolicy *>(handle)->output_floats();
|
||||
}
|
||||
|
||||
void rlgym_bpu_destroy(void *handle) {
|
||||
delete static_cast<BpuDnnPolicy *>(handle);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compatibility wrapper for the Gym 5-frame BPU deployment entrypoint."""
|
||||
|
||||
import runpy
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
entry = Path(__file__).resolve().parents[1] / "deploy_go1_rlgym_bpu_x5_fastcpp.py"
|
||||
runpy.run_path(str(entry), run_name="__main__")
|
||||
@@ -2,8 +2,8 @@
|
||||
"""Offline BPU policy smoke test for RDK X5.
|
||||
|
||||
This does not connect to the robot. It loads a BPU .bin and one raw float32
|
||||
input file, runs bpu_infer_lib repeatedly, and optionally checks the known
|
||||
00000.bin reference output captured from hrt_model_exec.
|
||||
input file, runs the C++ DNN API backend repeatedly, and optionally checks the
|
||||
known RobotLab 00000.bin reference output captured from hrt_model_exec.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -47,15 +47,19 @@ def main():
|
||||
|
||||
input_path = Path(args.input_bin).expanduser().resolve()
|
||||
data = np.fromfile(input_path, dtype=np.float32)
|
||||
if data.size != 450:
|
||||
raise ValueError(f"{input_path} has {data.size} float32 values, expected 450")
|
||||
|
||||
policy = BpuInferLibPolicy(args.bpu_model)
|
||||
if data.size != policy.input_size:
|
||||
raise ValueError(
|
||||
f"{input_path} has {data.size} float32 values, "
|
||||
f"but model expects {policy.input_size}"
|
||||
)
|
||||
action = policy(data)
|
||||
print("action", np.array2string(action, precision=6))
|
||||
print("action_max_abs", float(np.max(np.abs(action))))
|
||||
|
||||
if args.check_reference_00000:
|
||||
if policy.input_size != 450:
|
||||
raise ValueError("--check-reference-00000 is only valid for the 450-dim RobotLab reference input")
|
||||
diff = np.abs(action - REFERENCE_00000)
|
||||
print("reference_max_abs_diff", float(diff.max()))
|
||||
print("reference_mean_abs_diff", float(diff.mean()))
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# RDK X5 BPU 量化流程
|
||||
|
||||
这个目录用于把 `../policy_robotlab_15000.onnx` 转成 RDK X5 可运行的
|
||||
Horizon runtime `.bin`。量化在 Mac 上用 CPU Docker 完成,板端只做离线
|
||||
`hrt_model_exec` 验证,暂时不要直接接入实机控制。
|
||||
这个目录用于把 Gym/RobotLab 的 ONNX 策略转成 RDK X5 可运行的 Horizon
|
||||
runtime `.bin`。量化在 Mac 上用 CPU Docker 完成,板端可以做离线测速和实机
|
||||
部署测试。
|
||||
|
||||
参考资料:
|
||||
|
||||
@@ -10,14 +10,103 @@ Horizon runtime `.bin`。量化在 Mac 上用 CPU Docker 完成,板端只做
|
||||
- D-Robotics 论坛 WTW/Go2/X5 流程:`https://forum.d-robotics.cc/t/topic/28338`
|
||||
|
||||
官方工具链对 ONNX 的关键限制是:`ir_version <= 7`、`opset10/11`、固定
|
||||
4 维输入,且 N 维只能为 1。因此这里不能直接拿原始 RobotLab ONNX 编译,
|
||||
需要先降级 opset,再把 `[1, 450]` 输入包成固定 4D `NCHW`:
|
||||
`[1, 1, 1, 450]`。
|
||||
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
|
||||
量化。当前 BPU 部署默认使用 6500 版本:
|
||||
量化。RobotLab BPU 部署默认仍使用 6500 版本:
|
||||
|
||||
- 原始模型:`../policy_robotlab_6500.onnx`
|
||||
- 原始输入:`obs [1, 450]`
|
||||
|
||||
@@ -8,11 +8,17 @@ import numpy as np
|
||||
import onnxruntime as ort
|
||||
|
||||
|
||||
def load_samples(calibration_dir, limit):
|
||||
def load_samples(calibration_dir, limit, flat_dim):
|
||||
paths = sorted(Path(calibration_dir).glob("*.bin"))[:limit]
|
||||
if not paths:
|
||||
raise FileNotFoundError(f"No calibration .bin files found in {calibration_dir}")
|
||||
return [np.fromfile(path, dtype=np.float32).reshape(1, 450) for path in paths]
|
||||
samples = []
|
||||
for path in paths:
|
||||
sample = np.fromfile(path, dtype=np.float32)
|
||||
if sample.size != flat_dim:
|
||||
raise ValueError(f"{path} has {sample.size} float32 values, expected {flat_dim}")
|
||||
samples.append(sample.reshape(1, flat_dim))
|
||||
return samples
|
||||
|
||||
|
||||
def main():
|
||||
@@ -20,6 +26,7 @@ def main():
|
||||
parser.add_argument("--flat-onnx", type=Path, required=True)
|
||||
parser.add_argument("--bpu4d-onnx", type=Path, required=True)
|
||||
parser.add_argument("--calibration-dir", type=Path, default=Path("calibration_data"))
|
||||
parser.add_argument("--flat-dim", type=int, default=450)
|
||||
parser.add_argument("--limit", type=int, default=64)
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -30,9 +37,9 @@ def main():
|
||||
|
||||
max_abs = 0.0
|
||||
max_mean_abs = 0.0
|
||||
for sample in load_samples(args.calibration_dir, args.limit):
|
||||
for sample in load_samples(args.calibration_dir, args.limit, args.flat_dim):
|
||||
out_flat = flat.run(None, {flat_input: sample})[0]
|
||||
out_wrapped = wrapped.run(None, {wrapped_input: sample.reshape(1, 1, 1, 450)})[0]
|
||||
out_wrapped = wrapped.run(None, {wrapped_input: sample.reshape(1, 1, 1, args.flat_dim)})[0]
|
||||
diff = np.abs(out_flat - out_wrapped)
|
||||
max_abs = max(max_abs, float(diff.max()))
|
||||
max_mean_abs = max(max_mean_abs, float(diff.mean()))
|
||||
|
||||
40
deploy_45dim_rl_gym/bpu_quantization/keep_actions_output.py
Normal file
40
deploy_45dim_rl_gym/bpu_quantization/keep_actions_output.py
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep only the actions output in a policy ONNX graph."""
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
import onnx
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--input", type=Path, required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--output-name", default="actions")
|
||||
args = parser.parse_args()
|
||||
|
||||
model = onnx.load(str(args.input))
|
||||
outputs = list(model.graph.output)
|
||||
if not outputs:
|
||||
raise ValueError("ONNX graph has no outputs")
|
||||
|
||||
selected = None
|
||||
for output in outputs:
|
||||
if output.name == args.output_name:
|
||||
selected = output
|
||||
break
|
||||
if selected is None:
|
||||
selected = outputs[0]
|
||||
print(f"[WARN] output {args.output_name!r} not found; keeping first output {selected.name!r}")
|
||||
|
||||
del model.graph.output[:]
|
||||
model.graph.output.append(selected)
|
||||
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
onnx.save(model, str(args.output))
|
||||
print(f"Wrote actions-only ONNX: {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build float32 BPU calibration inputs from recorded RobotLab deployment logs."""
|
||||
"""Build float32 BPU calibration inputs from recorded deployment logs."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
@@ -11,16 +11,14 @@ import numpy as np
|
||||
|
||||
|
||||
NUM_OBS = 45
|
||||
HISTORY_LEN = 10
|
||||
ONNX_INPUT_DIM = NUM_OBS * HISTORY_LEN
|
||||
TERM_DIMS = (3, 3, 3, 12, 12, 12)
|
||||
HERE = Path(__file__).resolve().parent
|
||||
DEFAULT_LOG_ROOT = HERE.parents[1] / "logs"
|
||||
|
||||
|
||||
def build_onnx_input(history):
|
||||
def build_policy_input(history, history_len):
|
||||
frames = list(history)
|
||||
while len(frames) < HISTORY_LEN:
|
||||
while len(frames) < history_len:
|
||||
frames.insert(0, np.zeros(NUM_OBS, dtype=np.float32))
|
||||
|
||||
chunks = []
|
||||
@@ -41,14 +39,14 @@ def reservoir_add(samples, value, seen, max_samples, rng):
|
||||
samples[replace_index] = value
|
||||
|
||||
|
||||
def collect_samples(log_paths, max_samples, seed):
|
||||
def collect_samples(log_paths, max_samples, seed, history_len):
|
||||
rng = random.Random(seed)
|
||||
samples = []
|
||||
seen = 0
|
||||
usable_runs = []
|
||||
|
||||
for steps_path in log_paths:
|
||||
history = deque(maxlen=HISTORY_LEN)
|
||||
history = deque(maxlen=history_len)
|
||||
run_seen = 0
|
||||
was_rl = False
|
||||
|
||||
@@ -71,13 +69,13 @@ def collect_samples(log_paths, max_samples, seed):
|
||||
continue
|
||||
|
||||
history.append(obs)
|
||||
onnx_input = build_onnx_input(history)
|
||||
if not np.all(np.isfinite(onnx_input)):
|
||||
policy_input = build_policy_input(history, history_len)
|
||||
if not np.all(np.isfinite(policy_input)):
|
||||
continue
|
||||
|
||||
seen += 1
|
||||
run_seen += 1
|
||||
reservoir_add(samples, onnx_input, seen, max_samples, rng)
|
||||
reservoir_add(samples, policy_input, seen, max_samples, rng)
|
||||
|
||||
if run_seen:
|
||||
usable_runs.append({"steps": str(steps_path), "samples": run_seen})
|
||||
@@ -87,14 +85,18 @@ def collect_samples(log_paths, max_samples, seed):
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Create BPU float32 calibration .bin files from RobotLab JSONL logs."
|
||||
description="Create BPU float32 calibration .bin files from JSONL deployment logs."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--logs-root",
|
||||
type=Path,
|
||||
default=DEFAULT_LOG_ROOT,
|
||||
help="Directory containing robotlab_go1_deploy_*/steps.jsonl.",
|
||||
help="Directory containing <log-prefix>_*/steps.jsonl.",
|
||||
)
|
||||
parser.add_argument("--log-prefix", default="robotlab_go1_deploy",
|
||||
help="Run directory prefix below --logs-root")
|
||||
parser.add_argument("--history-len", type=int, default=10,
|
||||
help="Number of 45-dim observations to stack by term")
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=Path,
|
||||
@@ -102,23 +104,28 @@ def main():
|
||||
help="Output directory for raw float32 feature-map .bin files.",
|
||||
)
|
||||
parser.add_argument("--max-samples", type=int, default=512)
|
||||
parser.add_argument("--min-samples", type=int, default=32)
|
||||
parser.add_argument("--seed", type=int, default=20260727)
|
||||
parser.add_argument("--overwrite", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.max_samples < 32:
|
||||
raise ValueError("--max-samples must be at least 32")
|
||||
if args.max_samples < 1:
|
||||
raise ValueError("--max-samples must be positive")
|
||||
if args.history_len < 1:
|
||||
raise ValueError("--history-len must be positive")
|
||||
|
||||
log_paths = sorted(args.logs_root.glob("robotlab_go1_deploy_*/steps.jsonl"))
|
||||
log_paths = sorted(args.logs_root.glob(f"{args.log_prefix}_*/steps.jsonl"))
|
||||
if not log_paths:
|
||||
raise FileNotFoundError(f"No RobotLab step logs found below {args.logs_root}")
|
||||
raise FileNotFoundError(
|
||||
f"No step logs found for prefix {args.log_prefix!r} below {args.logs_root}"
|
||||
)
|
||||
|
||||
samples, total_seen, usable_runs = collect_samples(
|
||||
log_paths, args.max_samples, args.seed
|
||||
log_paths, args.max_samples, args.seed, args.history_len
|
||||
)
|
||||
if len(samples) < 32:
|
||||
if len(samples) < args.min_samples:
|
||||
raise RuntimeError(
|
||||
f"Only {len(samples)} valid RL inputs found; need at least 32 calibration samples."
|
||||
f"Only {len(samples)} valid RL inputs found; need at least {args.min_samples} calibration samples."
|
||||
)
|
||||
|
||||
output_dir = args.output_dir.resolve()
|
||||
@@ -134,13 +141,15 @@ def main():
|
||||
for index, sample in enumerate(samples):
|
||||
sample.astype(np.float32, copy=False).tofile(output_dir / f"{index:05d}.bin")
|
||||
|
||||
policy_input_dim = NUM_OBS * args.history_len
|
||||
metadata = {
|
||||
"format": "raw float32 feature-map",
|
||||
"flat_shape": [1, ONNX_INPUT_DIM],
|
||||
"mapper_shape": [1, 1, 1, ONNX_INPUT_DIM],
|
||||
"history_len": HISTORY_LEN,
|
||||
"flat_shape": [1, policy_input_dim],
|
||||
"mapper_shape": [1, 1, 1, policy_input_dim],
|
||||
"history_len": args.history_len,
|
||||
"num_obs": NUM_OBS,
|
||||
"term_dims": list(TERM_DIMS),
|
||||
"log_prefix": args.log_prefix,
|
||||
"selected_samples": len(samples),
|
||||
"candidate_rl_inputs": total_seen,
|
||||
"seed": args.seed,
|
||||
|
||||
199
deploy_45dim_rl_gym/bpu_quantization/quantize_policy_x5.sh
Executable file
199
deploy_45dim_rl_gym/bpu_quantization/quantize_policy_x5.sh
Executable file
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
|
||||
POLICY="../policy_35k.onnx"
|
||||
ROUND="35k"
|
||||
NAME=""
|
||||
HISTORY_LEN=5
|
||||
FLAT_DIM=""
|
||||
SAMPLES=64
|
||||
MIN_SAMPLES=32
|
||||
LOG_PREFIX="rlgym_go1_deploy"
|
||||
DOCKER_IMAGE="openexplorer/ai_toolchain_ubuntu_20_x5_cpu:v1.2.8"
|
||||
COMPARE_LIMIT=64
|
||||
RUN_CHECKER=1
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./quantize_policy_x5.sh [options]
|
||||
|
||||
Default: quantize Gym policy_35k.onnx as 5-frame/225-dim int16 Gemm BPU model.
|
||||
|
||||
Options:
|
||||
--policy PATH ONNX policy path, relative to this directory or absolute
|
||||
--round NAME round label used in output paths, e.g. 15k/25k/30k/35k
|
||||
--name NAME model basename; default is policy filename without .onnx
|
||||
--history-len N observation history length; Gym=5, RobotLab=10
|
||||
--flat-dim N flat input dim; default 45 * history-len
|
||||
--samples N calibration sample count; default 64 for faster mapping
|
||||
--min-samples N minimum valid samples required; default 32
|
||||
--log-prefix PREFIX log dir prefix below logs/, default rlgym_go1_deploy
|
||||
--docker-image IMAGE D-Robotics CPU toolchain image
|
||||
--compare-limit N float ONNX equivalence sample count, default 64
|
||||
--skip-checker skip hb_mapper checker before makertbin
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--policy) POLICY="$2"; shift 2 ;;
|
||||
--round) ROUND="$2"; shift 2 ;;
|
||||
--name) NAME="$2"; shift 2 ;;
|
||||
--history-len) HISTORY_LEN="$2"; shift 2 ;;
|
||||
--flat-dim) FLAT_DIM="$2"; shift 2 ;;
|
||||
--samples) SAMPLES="$2"; shift 2 ;;
|
||||
--min-samples) MIN_SAMPLES="$2"; shift 2 ;;
|
||||
--log-prefix) LOG_PREFIX="$2"; shift 2 ;;
|
||||
--docker-image) DOCKER_IMAGE="$2"; shift 2 ;;
|
||||
--compare-limit) COMPARE_LIMIT="$2"; shift 2 ;;
|
||||
--skip-checker) RUN_CHECKER=0; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${FLAT_DIM}" ]]; then
|
||||
FLAT_DIM=$((45 * HISTORY_LEN))
|
||||
fi
|
||||
|
||||
if [[ "${POLICY}" = /* ]]; then
|
||||
POLICY_ABS="${POLICY}"
|
||||
else
|
||||
POLICY_ABS="${SCRIPT_DIR}/${POLICY}"
|
||||
fi
|
||||
POLICY_ABS="$(cd "$(dirname "${POLICY_ABS}")" && pwd)/$(basename "${POLICY_ABS}")"
|
||||
|
||||
if [[ ! -f "${POLICY_ABS}" ]]; then
|
||||
echo "Policy not found: ${POLICY_ABS}" >&2
|
||||
exit 2
|
||||
fi
|
||||
case "${POLICY_ABS}" in
|
||||
"${REPO_ROOT}"/*) POLICY_REL="${POLICY_ABS#${REPO_ROOT}/}" ;;
|
||||
*) echo "Policy must be inside repo root ${REPO_ROOT}: ${POLICY_ABS}" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
if [[ -z "${NAME}" ]]; then
|
||||
NAME="$(basename "${POLICY_ABS}" .onnx)"
|
||||
fi
|
||||
|
||||
CAL_DIR="calibration_data_${ROUND}_gym_fast${SAMPLES}"
|
||||
OUTPUT_DIR="mapper_output_${ROUND}_gemm"
|
||||
OUTPUT_PREFIX="${NAME}_int16_gemm"
|
||||
YAML_FILE="${OUTPUT_PREFIX}.yaml"
|
||||
|
||||
echo "[INFO] repo : ${REPO_ROOT}"
|
||||
echo "[INFO] policy : ${POLICY_REL}"
|
||||
echo "[INFO] name/round : ${NAME} / ${ROUND}"
|
||||
echo "[INFO] history/shape : ${HISTORY_LEN} / 1x1x1x${FLAT_DIM}"
|
||||
echo "[INFO] calibration : ${CAL_DIR} (${SAMPLES} samples, prefix ${LOG_PREFIX})"
|
||||
echo "[INFO] output : ${OUTPUT_DIR}/${OUTPUT_PREFIX}.bin"
|
||||
|
||||
docker run --rm --platform linux/amd64 \
|
||||
-e POLICY_REL="${POLICY_REL}" \
|
||||
-e NAME="${NAME}" \
|
||||
-e HISTORY_LEN="${HISTORY_LEN}" \
|
||||
-e FLAT_DIM="${FLAT_DIM}" \
|
||||
-e SAMPLES="${SAMPLES}" \
|
||||
-e MIN_SAMPLES="${MIN_SAMPLES}" \
|
||||
-e LOG_PREFIX="${LOG_PREFIX}" \
|
||||
-e CAL_DIR="${CAL_DIR}" \
|
||||
-e OUTPUT_DIR="${OUTPUT_DIR}" \
|
||||
-e OUTPUT_PREFIX="${OUTPUT_PREFIX}" \
|
||||
-e YAML_FILE="${YAML_FILE}" \
|
||||
-e COMPARE_LIMIT="${COMPARE_LIMIT}" \
|
||||
-e RUN_CHECKER="${RUN_CHECKER}" \
|
||||
-v "${REPO_ROOT}:/workspace/deploy_go1_pro" \
|
||||
"${DOCKER_IMAGE}" \
|
||||
bash -lc '
|
||||
set -euo pipefail
|
||||
cd /workspace/deploy_go1_pro/deploy_45dim_rl_gym/bpu_quantization
|
||||
|
||||
POLICY="/workspace/deploy_go1_pro/${POLICY_REL}"
|
||||
ACTIONS_ONNX="${NAME}_actions.onnx"
|
||||
OPSET_ONNX="${NAME}_opset11.onnx"
|
||||
BPU4D_ONNX="${NAME}_bpu4d.onnx"
|
||||
GEMM_ONNX="${NAME}_bpu4d_gemm.onnx"
|
||||
|
||||
python3 make_calibration_data.py \
|
||||
--logs-root ../../logs \
|
||||
--log-prefix "${LOG_PREFIX}" \
|
||||
--history-len "${HISTORY_LEN}" \
|
||||
--output-dir "${CAL_DIR}" \
|
||||
--max-samples "${SAMPLES}" \
|
||||
--min-samples "${MIN_SAMPLES}" \
|
||||
--overwrite
|
||||
|
||||
python3 keep_actions_output.py \
|
||||
--input "${POLICY}" \
|
||||
--output "${ACTIONS_ONNX}"
|
||||
|
||||
python3 downgrade_policy_to_opset11.py \
|
||||
--input "${ACTIONS_ONNX}" \
|
||||
--output "${OPSET_ONNX}"
|
||||
|
||||
python3 make_bpu_4d_onnx.py \
|
||||
--input "${OPSET_ONNX}" \
|
||||
--output "${BPU4D_ONNX}" \
|
||||
--flat-dim "${FLAT_DIM}"
|
||||
|
||||
python3 replace_group_conv_with_gemm.py \
|
||||
--input "${BPU4D_ONNX}" \
|
||||
--output "${GEMM_ONNX}"
|
||||
|
||||
python3 compare_4d_onnx.py \
|
||||
--flat-onnx "${ACTIONS_ONNX}" \
|
||||
--bpu4d-onnx "${GEMM_ONNX}" \
|
||||
--calibration-dir "${CAL_DIR}" \
|
||||
--flat-dim "${FLAT_DIM}" \
|
||||
--limit "${COMPARE_LIMIT}"
|
||||
|
||||
cat > "${YAML_FILE}" <<YAML
|
||||
model_parameters:
|
||||
onnx_model: "./${GEMM_ONNX}"
|
||||
march: "bayes-e"
|
||||
layer_out_dump: false
|
||||
working_dir: "${OUTPUT_DIR}"
|
||||
output_model_file_prefix: "${OUTPUT_PREFIX}"
|
||||
|
||||
input_parameters:
|
||||
input_name: "obs_4d"
|
||||
input_shape: "1x1x1x${FLAT_DIM}"
|
||||
input_type_rt: "featuremap"
|
||||
input_layout_rt: "NCHW"
|
||||
input_type_train: "featuremap"
|
||||
input_layout_train: "NCHW"
|
||||
norm_type: "no_preprocess"
|
||||
|
||||
calibration_parameters:
|
||||
cal_data_dir: "./${CAL_DIR}"
|
||||
cal_data_type: "float32"
|
||||
calibration_type: "default"
|
||||
optimization: "set_all_nodes_int16"
|
||||
per_channel: true
|
||||
|
||||
compiler_parameters:
|
||||
compile_mode: "latency"
|
||||
debug: false
|
||||
optimize_level: "O3"
|
||||
YAML
|
||||
|
||||
if [[ "${RUN_CHECKER}" = "1" ]]; then
|
||||
hb_mapper checker \
|
||||
--model "${GEMM_ONNX}" \
|
||||
--model-type onnx \
|
||||
--march bayes-e \
|
||||
--input-shape obs_4d "1x1x1x${FLAT_DIM}"
|
||||
fi
|
||||
|
||||
hb_mapper makertbin \
|
||||
--config "${YAML_FILE}" \
|
||||
--model-type onnx
|
||||
|
||||
ls -lh "${OUTPUT_DIR}/${OUTPUT_PREFIX}.bin"
|
||||
'
|
||||
|
||||
echo "[INFO] Done: deploy_45dim_rl_gym/bpu_quantization/${OUTPUT_DIR}/${OUTPUT_PREFIX}.bin"
|
||||
@@ -48,7 +48,17 @@ def replace_node(model):
|
||||
|
||||
conv = next((node for node in nodes if node.name == CONV_NAME), None)
|
||||
if conv is None:
|
||||
raise ValueError(f"Cannot find node {CONV_NAME!r}")
|
||||
candidates = [
|
||||
node for node in nodes
|
||||
if node.op_type == "Conv"
|
||||
and int(attr_value(node, "group", 1)) > 1
|
||||
and attr_value(node, "kernel_shape") == [1]
|
||||
]
|
||||
if len(candidates) == 1:
|
||||
conv = candidates[0]
|
||||
print(f"[WARN] {CONV_NAME!r} not found; using grouped Conv {conv.name!r}")
|
||||
if conv is None:
|
||||
raise ValueError(f"Cannot find unique grouped 1x1 Conv node; fixed name {CONV_NAME!r} not found")
|
||||
if conv.op_type != "Conv":
|
||||
raise ValueError(f"{CONV_NAME!r} is {conv.op_type}, expected Conv")
|
||||
|
||||
|
||||
1281
deploy_45dim_rl_gym/deploy_go1_rlgym_bpu_x5_fastcpp.py
Normal file
1281
deploy_45dim_rl_gym/deploy_go1_rlgym_bpu_x5_fastcpp.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user