Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Python
__pycache__/

# RMA experiment runtime/build artifacts. Curated small reports live in results/rma/.
build/
build-*/
*.pt
*.pth
*.onnx
*.engine
*.plan
events.out.tfevents.*
dist/
.eggs/
*.egg-info/
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Contract-driven infrastructure for robot learning across physics backends and ha

<p align="center">Languages: English | <a href="README_zh.md">简体中文</a></p>

> **Fork experiment: Go2 RMA locomotion.** See the [experiment portfolio](docs/rma/README.md)
> for teacher/student code, reproducible evaluation data, learning curves and C++ mock deployment.
> Current forward-speed tracking has not reached the target; failures and reproduction limits are documented.

<p align="center">
<a href="https://github.com/unilabsim/UniLab/actions/workflows/ci.yml"><img src="https://github.com/unilabsim/UniLab/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://unilabsim.github.io"><img src="https://img.shields.io/badge/project-page-brightgreen" alt="Project Page"></a>
Expand Down
3 changes: 3 additions & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

<p align="center">语言:简体中文 | <a href="README.md">English</a></p>

> **本 fork 的 Go2 RMA 运动控制实验**:[查看项目展示与实验记录](docs/rma/README.md)。
> 包含教师/学生代码、真实评估数据、训练曲线和 C++ mock 部署;当前前进速度跟踪尚未达标。

<p align="center">
<a href="https://github.com/unilabsim/UniLab/actions/workflows/ci.yml"><img src="https://github.com/unilabsim/UniLab/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://unilabsim.github.io"><img src="https://img.shields.io/badge/project-page-brightgreen" alt="Project Page"></a>
Expand Down
43 changes: 43 additions & 0 deletions deploy_cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
cmake_minimum_required(VERSION 3.20)
project(unilab_rma_deploy LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
option(WITH_TENSORRT "Build TensorRT 10 policy inference" OFF)
option(ENABLE_TSAN "Enable thread sanitizer on supported Unix compilers" OFF)
find_package(Threads REQUIRED)
add_executable(control_loop src/main_control_loop.cpp)
add_executable(test_double_buffer tests/test_double_buffer.cpp)
add_executable(test_control_mapping tests/test_control_mapping.cpp)
foreach(target control_loop test_double_buffer test_control_mapping)
target_include_directories(${target} PRIVATE include)
target_link_libraries(${target} PRIVATE Threads::Threads)
if(MSVC)
target_compile_options(${target} PRIVATE /W4 /EHsc)
else()
target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic)
if(ENABLE_TSAN)
target_compile_options(${target} PRIVATE -fsanitize=thread -g)
target_link_options(${target} PRIVATE -fsanitize=thread)
endif()
endif()
endforeach()
if(WITH_TENSORRT)
find_package(CUDAToolkit REQUIRED)
find_path(TENSORRT_INCLUDE NvInfer.h HINTS ${TENSORRT_ROOT}/include REQUIRED)
find_library(TENSORRT_LIBRARY nvinfer HINTS ${TENSORRT_ROOT}/lib ${TENSORRT_ROOT}/lib64 REQUIRED)
target_compile_definitions(control_loop PRIVATE WITH_TENSORRT)
target_include_directories(control_loop PRIVATE ${TENSORRT_INCLUDE})
target_link_libraries(control_loop PRIVATE ${TENSORRT_LIBRARY} CUDA::cudart)
add_executable(policy_worker src/policy_worker.cpp)
target_include_directories(policy_worker PRIVATE include ${TENSORRT_INCLUDE})
target_link_libraries(policy_worker PRIVATE ${TENSORRT_LIBRARY} CUDA::cudart)
if(MSVC)
target_compile_options(policy_worker PRIVATE /W4 /EHsc)
else()
target_compile_options(policy_worker PRIVATE -Wall -Wextra -Wpedantic)
endif()
endif()
enable_testing()
add_test(NAME double_buffer_consistency COMMAND test_double_buffer)
add_test(NAME mock_control_loop COMMAND control_loop --seconds 1)
add_test(NAME policy_actuator_mapping COMMAND test_control_mapping)
21 changes: 21 additions & 0 deletions deploy_cpp/include/ControlMapping.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#include <array>

namespace rma {
// Policy/observations: FL FR RL RR. Scene actuator storage: FR FL RR RL.
// Per leg: hip, thigh, calf. Verify the scene contract before hardware use.
inline constexpr std::array<int, 12> policyToActuator{3, 4, 5, 0, 1, 2, 9, 10, 11, 6, 7, 8};

inline std::array<float, 12> actuatorTargets(
const std::array<float, 12>& home,
const std::array<float, 12>& policyAction,
float scale = 0.25f) {
std::array<float, 12> target{};
for (int joint = 0; joint < 12; ++joint) {
const int actuator = policyToActuator[joint];
target[actuator] = home[actuator] + scale * policyAction[joint];
}
return target;
}
} // namespace rma
54 changes: 54 additions & 0 deletions deploy_cpp/include/DoubleBuffer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#pragma once
#include <array>
#include <atomic>
#include <type_traits>

// Single producer / single consumer, latest-value mailbox, exactly two payload slots.
// Publication alone is insufficient: slot ownership prevents payload data races.
// Nonblocking try operations: caller retains last valid value on read failure.
template<class T> class DoubleBuffer {
static_assert(std::is_trivially_copyable_v<T>, "Payload must be trivially copyable");
static_assert(std::atomic<int>::is_always_lock_free, "Requires lock-free atomic<int>");
enum { Free=0, Writing=1, Ready=2, Reading=3 };
struct alignas(64) Slot {
std::atomic<int> state{Free};
T value{};
};
std::array<Slot,2> slots_{};
std::atomic<int> published_{-1};
public:
bool tryWrite(const T& value) noexcept {
const int previous=published_.load(std::memory_order_acquire);
const int first=previous<0?0:1-previous;
for(int attempt=0;attempt<2;++attempt){
const int index=attempt==0?first:1-first;
auto& slot=slots_[index];
int state=slot.state.load(std::memory_order_acquire);
if(state!=Free && state!=Ready) continue;
if(!slot.state.compare_exchange_strong(state,Writing,
std::memory_order_acq_rel,std::memory_order_relaxed)) continue;
slot.value=value;
// Announce while Writing, then release the payload. A reader of an
// old index cannot consume a recycled slot before its publication;
// Ready acquire also orders the publication before its next read.
published_.store(index,std::memory_order_release);
slot.state.store(Ready,std::memory_order_release);
return true;
}
return false;
}
bool tryRead(T& value) noexcept {
const int index=published_.load(std::memory_order_acquire);
if(index<0) return false;
auto& slot=slots_[index];
int state=Ready;
if(!slot.state.compare_exchange_strong(state,Reading,
std::memory_order_acq_rel,std::memory_order_relaxed)) return false;
value=slot.value;
slot.state.store(Free,std::memory_order_release);
return true;
}
DoubleBuffer()=default;
DoubleBuffer(const DoubleBuffer&)=delete;
DoubleBuffer& operator=(const DoubleBuffer&)=delete;
};
80 changes: 80 additions & 0 deletions deploy_cpp/include/TensorRTPolicy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#pragma once
#include <NvInfer.h>
#include <cuda_runtime_api.h>
#include <array>
#include <cstdio>
#include <initializer_list>
#include <fstream>
#include <iterator>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

class TensorRTPolicy {
struct Logger : nvinfer1::ILogger {
void log(Severity severity,const char* msg) noexcept override {
if(severity<=Severity::kERROR) std::fprintf(stderr,"TensorRT: %s\n",msg);
}
} logger_;
std::unique_ptr<nvinfer1::IRuntime> runtime_;
std::unique_ptr<nvinfer1::ICudaEngine> engine_;
std::unique_ptr<nvinfer1::IExecutionContext> context_;
cudaStream_t stream_{};
void *obs_{},*history_{},*action_{};
static void check(cudaError_t result){
if(result!=cudaSuccess) throw std::runtime_error(cudaGetErrorString(result));
}
void cleanup() noexcept {
if(stream_) cudaStreamSynchronize(stream_);
if(obs_) cudaFree(obs_);
if(history_) cudaFree(history_);
if(action_) cudaFree(action_);
if(stream_) cudaStreamDestroy(stream_);
}
void require(const char* name,nvinfer1::TensorIOMode mode,std::initializer_list<int> shape){
if(engine_->getTensorIOMode(name)!=mode || engine_->getTensorDataType(name)!=nvinfer1::DataType::kFLOAT ||
engine_->getTensorLocation(name)!=nvinfer1::TensorLocation::kDEVICE)
throw std::runtime_error(std::string("Engine IO type/mode mismatch: ")+name);
const auto dims=engine_->getTensorShape(name);
if(dims.nbDims!=static_cast<int>(shape.size())) throw std::runtime_error("Engine rank mismatch");
int i=0;
for(int d:shape) if(dims.d[i++]!=d) throw std::runtime_error("Engine shape mismatch");
}
public:
explicit TensorRTPolicy(const std::string& path){
try {
std::ifstream file(path,std::ios::binary);
if(!file) throw std::runtime_error("Cannot open TensorRT engine");
std::vector<char> bytes((std::istreambuf_iterator<char>(file)),{});
runtime_.reset(nvinfer1::createInferRuntime(logger_));
if(!runtime_) throw std::runtime_error("Cannot create TensorRT runtime");
engine_.reset(runtime_->deserializeCudaEngine(bytes.data(),bytes.size()));
if(!engine_) throw std::runtime_error("Cannot deserialize engine");
if(engine_->getNbIOTensors()!=3) throw std::runtime_error("Expected exactly 3 IO tensors");
require("observation",nvinfer1::TensorIOMode::kINPUT,{1,49});
require("history",nvinfer1::TensorIOMode::kINPUT,{1,50,49});
require("action",nvinfer1::TensorIOMode::kOUTPUT,{1,12});
context_.reset(engine_->createExecutionContext());
if(!context_) throw std::runtime_error("Cannot create execution context");
check(cudaStreamCreate(&stream_));
check(cudaMalloc(&obs_,49*sizeof(float)));
check(cudaMalloc(&history_,2450*sizeof(float)));
check(cudaMalloc(&action_,12*sizeof(float)));
if(!context_->setTensorAddress("observation",obs_) ||
!context_->setTensorAddress("history",history_) ||
!context_->setTensorAddress("action",action_)) throw std::runtime_error("Cannot bind IO");
} catch(...) { cleanup(); throw; }
}
~TensorRTPolicy(){ cleanup(); }
void infer(const std::array<float,49>& obs,const std::array<float,2450>& history,
std::array<float,12>& action){
check(cudaMemcpyAsync(obs_,obs.data(),sizeof(obs),cudaMemcpyHostToDevice,stream_));
check(cudaMemcpyAsync(history_,history.data(),sizeof(history),cudaMemcpyHostToDevice,stream_));
if(!context_->enqueueV3(stream_)) throw std::runtime_error("enqueueV3 failed");
check(cudaMemcpyAsync(action.data(),action_,sizeof(action),cudaMemcpyDeviceToHost,stream_));
check(cudaStreamSynchronize(stream_));
}
TensorRTPolicy(const TensorRTPolicy&)=delete;
TensorRTPolicy& operator=(const TensorRTPolicy&)=delete;
};
Loading