adapt gym 5history
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -5,10 +5,18 @@ __pycache__/
|
|||||||
deploy_45dim_rl_gym/bpu_quantization/.hb_check/
|
deploy_45dim_rl_gym/bpu_quantization/.hb_check/
|
||||||
deploy_45dim_rl_gym/bpu_quantization/calibration_data/
|
deploy_45dim_rl_gym/bpu_quantization/calibration_data/
|
||||||
deploy_45dim_rl_gym/bpu_quantization/calibration_data_fast*/
|
deploy_45dim_rl_gym/bpu_quantization/calibration_data_fast*/
|
||||||
|
deploy_45dim_rl_gym/bpu_quantization/calibration_data_*_gym_fast*/
|
||||||
|
deploy_45dim_rl_gym/bpu_quantization/calibration_data_*_gym_fast*_metadata.json
|
||||||
deploy_45dim_rl_gym/bpu_quantization/mapper_output/
|
deploy_45dim_rl_gym/bpu_quantization/mapper_output/
|
||||||
deploy_45dim_rl_gym/bpu_quantization/mapper_output_gemm/
|
deploy_45dim_rl_gym/bpu_quantization/mapper_output_gemm/
|
||||||
|
deploy_45dim_rl_gym/bpu_quantization/mapper_output_*_gemm/
|
||||||
deploy_45dim_rl_gym/bpu_quantization/mapper_output_6500_gemm/
|
deploy_45dim_rl_gym/bpu_quantization/mapper_output_6500_gemm/
|
||||||
deploy_45dim_rl_gym/bpu_quantization/hb_mapper_*.log*
|
deploy_45dim_rl_gym/bpu_quantization/hb_mapper_*.log*
|
||||||
|
deploy_45dim_rl_gym/bpu_quantization/policy_[0-9]*_actions.onnx
|
||||||
|
deploy_45dim_rl_gym/bpu_quantization/policy_[0-9]*_bpu4d.onnx
|
||||||
|
deploy_45dim_rl_gym/bpu_quantization/policy_[0-9]*_bpu4d_gemm.onnx
|
||||||
|
deploy_45dim_rl_gym/bpu_quantization/policy_[0-9]*_int16_gemm.yaml
|
||||||
|
deploy_45dim_rl_gym/bpu_quantization/policy_[0-9]*_opset11.onnx
|
||||||
deploy_45dim_rl_gym/bpu_quantization/policy_robotlab_15000_bpu4d.onnx
|
deploy_45dim_rl_gym/bpu_quantization/policy_robotlab_15000_bpu4d.onnx
|
||||||
deploy_45dim_rl_gym/bpu_quantization/policy_robotlab_15000_bpu4d_gemm.onnx
|
deploy_45dim_rl_gym/bpu_quantization/policy_robotlab_15000_bpu4d_gemm.onnx
|
||||||
deploy_45dim_rl_gym/bpu_quantization/policy_robotlab_15000_opset11.onnx
|
deploy_45dim_rl_gym/bpu_quantization/policy_robotlab_15000_opset11.onnx
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
- `deploy_go1_rlgym_pro_sdk_fastcpp.py`:真机 Go1 PRO 低层部署脚本,独立使用 C++ LowCmd 构包/加密后端
|
- `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.py`:RobotLab 策略真机部署脚本
|
||||||
- `deploy_go1_rlgym_pro_sdk_lab_fastcpp.py`:RobotLab 策略真机部署脚本,独立使用 C++ LowCmd 构包/加密后端
|
- `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 维的历史输入,按观测项分组堆叠:
|
该 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
|
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."""
|
"""Default fast backend: direct C++ DNN API via ctypes."""
|
||||||
|
|
||||||
backend_name = "cpp_dnn_api_x5"
|
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):
|
def __init__(self, model_path, priority=0, bpu_cores=(0,), cpp_lib=DEFAULT_CPP_LIB):
|
||||||
self.model_path = Path(model_path).expanduser().resolve()
|
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_infer.restype = ctypes.c_int
|
||||||
self._lib.rlgym_bpu_destroy.argtypes = [ctypes.c_void_p]
|
self._lib.rlgym_bpu_destroy.argtypes = [ctypes.c_void_p]
|
||||||
self._lib.rlgym_bpu_destroy.restype = None
|
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.argtypes = []
|
||||||
self._lib.rlgym_bpu_version.restype = ctypes.c_char_p
|
self._lib.rlgym_bpu_version.restype = ctypes.c_char_p
|
||||||
|
|
||||||
@@ -107,7 +109,13 @@ class BpuInferLibPolicy:
|
|||||||
)
|
)
|
||||||
if not self._handle:
|
if not self._handle:
|
||||||
raise RuntimeError(err.value.decode("utf-8", errors="replace"))
|
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._output_ptr = self._output.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
|
||||||
self._err = ctypes.create_string_buffer(1024)
|
self._err = ctypes.create_string_buffer(1024)
|
||||||
|
|
||||||
@@ -131,8 +139,8 @@ class BpuInferLibPolicy:
|
|||||||
|
|
||||||
def __call__(self, flat_input):
|
def __call__(self, flat_input):
|
||||||
arr = np.asarray(flat_input, dtype=np.float32)
|
arr = np.asarray(flat_input, dtype=np.float32)
|
||||||
if arr.size != 450:
|
if arr.size != self.input_size:
|
||||||
raise ValueError(f"BPU policy input has {arr.size} values, expected 450")
|
raise ValueError(f"BPU policy input has {arr.size} values, expected {self.input_size}")
|
||||||
input_flat = np.ascontiguousarray(arr.reshape(-1), dtype=np.float32)
|
input_flat = np.ascontiguousarray(arr.reshape(-1), dtype=np.float32)
|
||||||
rc = self._lib.rlgym_bpu_infer(
|
rc = self._lib.rlgym_bpu_infer(
|
||||||
self._handle,
|
self._handle,
|
||||||
@@ -172,6 +180,9 @@ class BpuInferLibPythonPolicy:
|
|||||||
self.priority = int(priority)
|
self.priority = int(priority)
|
||||||
self.bpu_cores = tuple(int(core) for core in bpu_cores)
|
self.bpu_cores = tuple(int(core) for core in bpu_cores)
|
||||||
self.suppress_runtime_output = bool(suppress_runtime_output)
|
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):
|
with suppress_c_output(self.suppress_runtime_output):
|
||||||
self.infer = Infer(False)
|
self.infer = Infer(False)
|
||||||
loaded = self.infer.load_model(str(self.model_path))
|
loaded = self.infer.load_model(str(self.model_path))
|
||||||
@@ -185,8 +196,8 @@ class BpuInferLibPythonPolicy:
|
|||||||
|
|
||||||
def __call__(self, flat_input):
|
def __call__(self, flat_input):
|
||||||
arr = np.asarray(flat_input, dtype=np.float32)
|
arr = np.asarray(flat_input, dtype=np.float32)
|
||||||
if arr.size != 450:
|
if arr.size != self.input_size:
|
||||||
raise ValueError(f"BPU policy input has {arr.size} values, expected 450")
|
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)
|
input_4d = np.ascontiguousarray(arr.reshape(self.input_shape), dtype=np.float32)
|
||||||
with suppress_c_output(self.suppress_runtime_output):
|
with suppress_c_output(self.suppress_runtime_output):
|
||||||
copied = self.infer.read_numpy_arr_float32(input_4d, 0)
|
copied = self.infer.read_numpy_arr_float32(input_4d, 0)
|
||||||
|
|||||||
@@ -12,8 +12,10 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
void *rlgym_bpu_create(const char *model_path, int bpu_core, int priority,
|
void *rlgym_bpu_create(const char *model_path, int bpu_core, int priority,
|
||||||
char *err, int err_len);
|
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 err_len);
|
||||||
|
int rlgym_bpu_input_floats(void *handle);
|
||||||
|
int rlgym_bpu_output_floats(void *handle);
|
||||||
void rlgym_bpu_destroy(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 > 2) input = argv[2];
|
||||||
if (argc > 3) repeat = std::atoi(argv[3]);
|
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] = {};
|
char err[1024] = {};
|
||||||
void *handle = rlgym_bpu_create(model, 1, 0, err, sizeof(err));
|
void *handle = rlgym_bpu_create(model, 1, 0, err, sizeof(err));
|
||||||
if (handle == nullptr) {
|
if (handle == nullptr) {
|
||||||
@@ -67,20 +63,37 @@ int main(int argc, char **argv) {
|
|||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
float out[12] = {};
|
const int input_floats = rlgym_bpu_input_floats(handle);
|
||||||
if (rlgym_bpu_infer(handle, obs.data(), out, err, sizeof(err)) != 0) {
|
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";
|
std::cerr << err << "\n";
|
||||||
rlgym_bpu_destroy(handle);
|
rlgym_bpu_destroy(handle);
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
float max_diff = 0.0f;
|
float max_diff = 0.0f;
|
||||||
|
if (input_floats == 450 && output_floats == 12) {
|
||||||
for (int i = 0; i < 12; ++i) {
|
for (int i = 0; i < 12; ++i) {
|
||||||
max_diff = std::max(max_diff, std::fabs(out[i] - kReference[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();
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
for (int i = 0; i < repeat; ++i) {
|
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";
|
std::cerr << err << "\n";
|
||||||
rlgym_bpu_destroy(handle);
|
rlgym_bpu_destroy(handle);
|
||||||
return 5;
|
return 5;
|
||||||
@@ -90,10 +103,13 @@ int main(int argc, char **argv) {
|
|||||||
const double elapsed_ms =
|
const double elapsed_ms =
|
||||||
std::chrono::duration<double, std::milli>(t1 - t0).count();
|
std::chrono::duration<double, std::milli>(t1 - t0).count();
|
||||||
|
|
||||||
|
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 << "reference_max_abs_diff " << max_diff << "\n";
|
||||||
|
}
|
||||||
std::cout << "repeat=" << repeat << " cpp_avg_ms=" << (elapsed_ms / repeat) << "\n";
|
std::cout << "repeat=" << repeat << " cpp_avg_ms=" << (elapsed_ms / repeat) << "\n";
|
||||||
std::cout << "action0=" << out[0] << " action_max_abs="
|
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);
|
return std::fabs(a) < std::fabs(b);
|
||||||
})
|
})
|
||||||
<< "\n";
|
<< "\n";
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr int kInputFloats = 450;
|
|
||||||
constexpr int kOutputFloats = 12;
|
constexpr int kOutputFloats = 12;
|
||||||
|
|
||||||
void set_error(char *err, int err_len, const std::string &msg) {
|
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),
|
check_dnn(hbDNNGetOutputTensorProperties(&output_tensors_[0].properties, dnn_handle_, 0),
|
||||||
"hbDNNGetOutputTensorProperties");
|
"hbDNNGetOutputTensorProperties");
|
||||||
|
|
||||||
validate_tensor(input_tensors_[0].properties, kInputFloats, HB_DNN_TENSOR_TYPE_F32,
|
input_floats_ = element_count(input_tensors_[0].properties.validShape);
|
||||||
"input");
|
output_floats_ = element_count(output_tensors_[0].properties.validShape);
|
||||||
validate_tensor(output_tensors_[0].properties, kOutputFloats, HB_DNN_TENSOR_TYPE_F32,
|
validate_tensor(input_tensors_[0].properties, HB_DNN_TENSOR_TYPE_F32, "input");
|
||||||
"output");
|
validate_tensor(output_tensors_[0].properties, HB_DNN_TENSOR_TYPE_F32, "output");
|
||||||
|
|
||||||
alloc_tensor_mem(input_tensors_[0]);
|
alloc_tensor_mem(input_tensors_[0]);
|
||||||
alloc_tensor_mem(output_tensors_[0]);
|
alloc_tensor_mem(output_tensors_[0]);
|
||||||
@@ -112,7 +111,7 @@ class BpuDnnPolicy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto &input_mem = input_tensors_[0].sysMem[0];
|
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);
|
int code = hbSysFlushMem(&input_mem, HB_SYS_MEM_CACHE_CLEAN);
|
||||||
if (code != 0) {
|
if (code != 0) {
|
||||||
throw std::runtime_error(dnn_error("hbSysFlushMem(input)", code));
|
throw std::runtime_error(dnn_error("hbSysFlushMem(input)", code));
|
||||||
@@ -137,18 +136,15 @@ class BpuDnnPolicy {
|
|||||||
if (code != 0) {
|
if (code != 0) {
|
||||||
throw std::runtime_error(dnn_error("hbSysFlushMem(output)", code));
|
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:
|
private:
|
||||||
static void validate_tensor(const hbDNNTensorProperties &props, int expected_count,
|
static void validate_tensor(const hbDNNTensorProperties &props, int expected_type,
|
||||||
int expected_type, const char *name) {
|
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());
|
|
||||||
}
|
|
||||||
if (props.tensorType != expected_type) {
|
if (props.tensorType != expected_type) {
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << name << " tensor type " << props.tensorType << " != " << expected_type;
|
oss << name << " tensor type " << props.tensorType << " != " << expected_type;
|
||||||
@@ -182,6 +178,8 @@ class BpuDnnPolicy {
|
|||||||
std::string model_name_;
|
std::string model_name_;
|
||||||
std::vector<hbDNNTensor> input_tensors_;
|
std::vector<hbDNNTensor> input_tensors_;
|
||||||
std::vector<hbDNNTensor> output_tensors_;
|
std::vector<hbDNNTensor> output_tensors_;
|
||||||
|
int input_floats_{0};
|
||||||
|
int output_floats_{0};
|
||||||
int bpu_core_{HB_BPU_CORE_ANY};
|
int bpu_core_{HB_BPU_CORE_ANY};
|
||||||
int priority_{HB_DNN_PRIORITY_LOWEST};
|
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) {
|
int err_len) {
|
||||||
try {
|
try {
|
||||||
set_error(err, err_len, "");
|
set_error(err, err_len, "");
|
||||||
if (handle == nullptr) {
|
if (handle == nullptr) {
|
||||||
throw std::runtime_error("null policy handle");
|
throw std::runtime_error("null policy handle");
|
||||||
}
|
}
|
||||||
static_cast<BpuDnnPolicy *>(handle)->infer(input450, output12);
|
static_cast<BpuDnnPolicy *>(handle)->infer(input, output);
|
||||||
return 0;
|
return 0;
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
set_error(err, err_len, e.what());
|
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) {
|
void rlgym_bpu_destroy(void *handle) {
|
||||||
delete static_cast<BpuDnnPolicy *>(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.
|
"""Offline BPU policy smoke test for RDK X5.
|
||||||
|
|
||||||
This does not connect to the robot. It loads a BPU .bin and one raw float32
|
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
|
input file, runs the C++ DNN API backend repeatedly, and optionally checks the
|
||||||
00000.bin reference output captured from hrt_model_exec.
|
known RobotLab 00000.bin reference output captured from hrt_model_exec.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -47,15 +47,19 @@ def main():
|
|||||||
|
|
||||||
input_path = Path(args.input_bin).expanduser().resolve()
|
input_path = Path(args.input_bin).expanduser().resolve()
|
||||||
data = np.fromfile(input_path, dtype=np.float32)
|
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)
|
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)
|
action = policy(data)
|
||||||
print("action", np.array2string(action, precision=6))
|
print("action", np.array2string(action, precision=6))
|
||||||
print("action_max_abs", float(np.max(np.abs(action))))
|
print("action_max_abs", float(np.max(np.abs(action))))
|
||||||
|
|
||||||
if args.check_reference_00000:
|
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)
|
diff = np.abs(action - REFERENCE_00000)
|
||||||
print("reference_max_abs_diff", float(diff.max()))
|
print("reference_max_abs_diff", float(diff.max()))
|
||||||
print("reference_mean_abs_diff", float(diff.mean()))
|
print("reference_mean_abs_diff", float(diff.mean()))
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# RDK X5 BPU 量化流程
|
# RDK X5 BPU 量化流程
|
||||||
|
|
||||||
这个目录用于把 `../policy_robotlab_15000.onnx` 转成 RDK X5 可运行的
|
这个目录用于把 Gym/RobotLab 的 ONNX 策略转成 RDK X5 可运行的 Horizon
|
||||||
Horizon runtime `.bin`。量化在 Mac 上用 CPU Docker 完成,板端只做离线
|
runtime `.bin`。量化在 Mac 上用 CPU Docker 完成,板端可以做离线测速和实机
|
||||||
`hrt_model_exec` 验证,暂时不要直接接入实机控制。
|
部署测试。
|
||||||
|
|
||||||
参考资料:
|
参考资料:
|
||||||
|
|
||||||
@@ -10,14 +10,103 @@ Horizon runtime `.bin`。量化在 Mac 上用 CPU Docker 完成,板端只做
|
|||||||
- D-Robotics 论坛 WTW/Go2/X5 流程:`https://forum.d-robotics.cc/t/topic/28338`
|
- D-Robotics 论坛 WTW/Go2/X5 流程:`https://forum.d-robotics.cc/t/topic/28338`
|
||||||
|
|
||||||
官方工具链对 ONNX 的关键限制是:`ir_version <= 7`、`opset10/11`、固定
|
官方工具链对 ONNX 的关键限制是:`ir_version <= 7`、`opset10/11`、固定
|
||||||
4 维输入,且 N 维只能为 1。因此这里不能直接拿原始 RobotLab ONNX 编译,
|
4 维输入,且 N 维只能为 1。因此这里不能直接拿原始 ONNX 编译,需要先裁剪
|
||||||
需要先降级 opset,再把 `[1, 450]` 输入包成固定 4D `NCHW`:
|
actions-only 输出、降级 opset,再把 `[1, D]` 输入包成固定 4D `NCHW`:
|
||||||
`[1, 1, 1, 450]`。
|
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
|
已经完成 `policy_robotlab_15000.onnx` 和 `policy_robotlab_6500.onnx` 的 int16
|
||||||
量化。当前 BPU 部署默认使用 6500 版本:
|
量化。RobotLab BPU 部署默认仍使用 6500 版本:
|
||||||
|
|
||||||
- 原始模型:`../policy_robotlab_6500.onnx`
|
- 原始模型:`../policy_robotlab_6500.onnx`
|
||||||
- 原始输入:`obs [1, 450]`
|
- 原始输入:`obs [1, 450]`
|
||||||
|
|||||||
@@ -8,11 +8,17 @@ import numpy as np
|
|||||||
import onnxruntime as ort
|
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]
|
paths = sorted(Path(calibration_dir).glob("*.bin"))[:limit]
|
||||||
if not paths:
|
if not paths:
|
||||||
raise FileNotFoundError(f"No calibration .bin files found in {calibration_dir}")
|
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():
|
def main():
|
||||||
@@ -20,6 +26,7 @@ def main():
|
|||||||
parser.add_argument("--flat-onnx", type=Path, required=True)
|
parser.add_argument("--flat-onnx", type=Path, required=True)
|
||||||
parser.add_argument("--bpu4d-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("--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)
|
parser.add_argument("--limit", type=int, default=64)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@@ -30,9 +37,9 @@ def main():
|
|||||||
|
|
||||||
max_abs = 0.0
|
max_abs = 0.0
|
||||||
max_mean_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_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)
|
diff = np.abs(out_flat - out_wrapped)
|
||||||
max_abs = max(max_abs, float(diff.max()))
|
max_abs = max(max_abs, float(diff.max()))
|
||||||
max_mean_abs = max(max_mean_abs, float(diff.mean()))
|
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
|
#!/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 argparse
|
||||||
import json
|
import json
|
||||||
@@ -11,16 +11,14 @@ import numpy as np
|
|||||||
|
|
||||||
|
|
||||||
NUM_OBS = 45
|
NUM_OBS = 45
|
||||||
HISTORY_LEN = 10
|
|
||||||
ONNX_INPUT_DIM = NUM_OBS * HISTORY_LEN
|
|
||||||
TERM_DIMS = (3, 3, 3, 12, 12, 12)
|
TERM_DIMS = (3, 3, 3, 12, 12, 12)
|
||||||
HERE = Path(__file__).resolve().parent
|
HERE = Path(__file__).resolve().parent
|
||||||
DEFAULT_LOG_ROOT = HERE.parents[1] / "logs"
|
DEFAULT_LOG_ROOT = HERE.parents[1] / "logs"
|
||||||
|
|
||||||
|
|
||||||
def build_onnx_input(history):
|
def build_policy_input(history, history_len):
|
||||||
frames = list(history)
|
frames = list(history)
|
||||||
while len(frames) < HISTORY_LEN:
|
while len(frames) < history_len:
|
||||||
frames.insert(0, np.zeros(NUM_OBS, dtype=np.float32))
|
frames.insert(0, np.zeros(NUM_OBS, dtype=np.float32))
|
||||||
|
|
||||||
chunks = []
|
chunks = []
|
||||||
@@ -41,14 +39,14 @@ def reservoir_add(samples, value, seen, max_samples, rng):
|
|||||||
samples[replace_index] = value
|
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)
|
rng = random.Random(seed)
|
||||||
samples = []
|
samples = []
|
||||||
seen = 0
|
seen = 0
|
||||||
usable_runs = []
|
usable_runs = []
|
||||||
|
|
||||||
for steps_path in log_paths:
|
for steps_path in log_paths:
|
||||||
history = deque(maxlen=HISTORY_LEN)
|
history = deque(maxlen=history_len)
|
||||||
run_seen = 0
|
run_seen = 0
|
||||||
was_rl = False
|
was_rl = False
|
||||||
|
|
||||||
@@ -71,13 +69,13 @@ def collect_samples(log_paths, max_samples, seed):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
history.append(obs)
|
history.append(obs)
|
||||||
onnx_input = build_onnx_input(history)
|
policy_input = build_policy_input(history, history_len)
|
||||||
if not np.all(np.isfinite(onnx_input)):
|
if not np.all(np.isfinite(policy_input)):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
seen += 1
|
seen += 1
|
||||||
run_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:
|
if run_seen:
|
||||||
usable_runs.append({"steps": str(steps_path), "samples": 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():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
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(
|
parser.add_argument(
|
||||||
"--logs-root",
|
"--logs-root",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=DEFAULT_LOG_ROOT,
|
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(
|
parser.add_argument(
|
||||||
"--output-dir",
|
"--output-dir",
|
||||||
type=Path,
|
type=Path,
|
||||||
@@ -102,23 +104,28 @@ def main():
|
|||||||
help="Output directory for raw float32 feature-map .bin files.",
|
help="Output directory for raw float32 feature-map .bin files.",
|
||||||
)
|
)
|
||||||
parser.add_argument("--max-samples", type=int, default=512)
|
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("--seed", type=int, default=20260727)
|
||||||
parser.add_argument("--overwrite", action="store_true")
|
parser.add_argument("--overwrite", action="store_true")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.max_samples < 32:
|
if args.max_samples < 1:
|
||||||
raise ValueError("--max-samples must be at least 32")
|
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:
|
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(
|
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(
|
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()
|
output_dir = args.output_dir.resolve()
|
||||||
@@ -134,13 +141,15 @@ def main():
|
|||||||
for index, sample in enumerate(samples):
|
for index, sample in enumerate(samples):
|
||||||
sample.astype(np.float32, copy=False).tofile(output_dir / f"{index:05d}.bin")
|
sample.astype(np.float32, copy=False).tofile(output_dir / f"{index:05d}.bin")
|
||||||
|
|
||||||
|
policy_input_dim = NUM_OBS * args.history_len
|
||||||
metadata = {
|
metadata = {
|
||||||
"format": "raw float32 feature-map",
|
"format": "raw float32 feature-map",
|
||||||
"flat_shape": [1, ONNX_INPUT_DIM],
|
"flat_shape": [1, policy_input_dim],
|
||||||
"mapper_shape": [1, 1, 1, ONNX_INPUT_DIM],
|
"mapper_shape": [1, 1, 1, policy_input_dim],
|
||||||
"history_len": HISTORY_LEN,
|
"history_len": args.history_len,
|
||||||
"num_obs": NUM_OBS,
|
"num_obs": NUM_OBS,
|
||||||
"term_dims": list(TERM_DIMS),
|
"term_dims": list(TERM_DIMS),
|
||||||
|
"log_prefix": args.log_prefix,
|
||||||
"selected_samples": len(samples),
|
"selected_samples": len(samples),
|
||||||
"candidate_rl_inputs": total_seen,
|
"candidate_rl_inputs": total_seen,
|
||||||
"seed": args.seed,
|
"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)
|
conv = next((node for node in nodes if node.name == CONV_NAME), None)
|
||||||
if conv is 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":
|
if conv.op_type != "Conv":
|
||||||
raise ValueError(f"{CONV_NAME!r} is {conv.op_type}, expected 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