Skip to content

adding features and tasks for the Spot-related tasks#95

Open
johnzhang3 wants to merge 112 commits intorai-opensource:mainfrom
johnzhang3:spot_cpp
Open

adding features and tasks for the Spot-related tasks#95
johnzhang3 wants to merge 112 commits intorai-opensource:mainfrom
johnzhang3:spot_cpp

Conversation

@johnzhang3
Copy link
Copy Markdown
Contributor

this PR introduces several significant changes

  1. implements mujoco rollouts in cpp
  2. separates task-space and mujoco simulation number of controls @jbruedigam-bdai
  3. custom cpp rollout functions for the Spot robot that interleaves mujoco simulation and an RL policy inference from Relic. also implements a simulation cutoff time if a single thread is taking significant time
  4. other changes include the CMAES optimizer and some benchmarking scripts from @alberthli (maybe that belongs in a separate PR....)

let me know if you guys have any feedback or want me to break this down into multiple PRs

johnzhang-rai and others added 30 commits August 8, 2025 16:36
- Add return type annotation for micro_benchmark_onnx_inference function
- Fix unused loop variables by renaming to _trial
- Remove unused result variable assignments in benchmarking code
- Fix docstring formatting and add missing type annotations
- Implement PersistentThreadPool class with worker threads
- Add ThreadPoolManager singleton for lifecycle management
- Create persistent_cpp_rollout and persistent_onnx_interleave_rollout functions
- Update Python bindings to expose new persistent functions
- Add comprehensive benchmarking and testing scripts
- Achieve 1.43x speedup and 87% reduction in ONNX overhead
- Maintain perfect correctness with identity verification
- Add missing type annotations for all public functions
- Fix unused loop variables by replacing with underscores
- Add proper docstrings for public functions
- Use specific type hints for function parameters and return types
- Apply ruff-format code formatting
- All critical linting errors resolved while maintaining functionality
Copy link
Copy Markdown
Collaborator

@bhung-bdai bhung-bdai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly minor comments, but I'm wondering if we can reduce the code with a bit of lift. If not, I'm not opposed to applying tech debt and letting this get fixed in the future.

<joint name="yellow_chair_joint" type="free"/>
<inertial pos="0 0.06 0.26" mass="16.58" diaginertia="0.25 0.25 0.25"/>

<!-- <geom pos="0 0.06 0.26" name="z_axis" class="visual" type="cylinder" size="0.01 1"/>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comments?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -0,0 +1,20 @@
<mujoco model="yellow_chair">
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is public, I think it would be nice to have a reference to the actual chair itself. Could we find a link to it for sale?

Comment thread judo/optimizers/base.py Outdated
@@ -19,6 +20,7 @@ class OptimizerConfig(OverridableConfig):
num_nodes: int = 4
use_noise_ramp: bool = False
noise_ramp: float = 2.5
cutoff_time: float = 0.2 # Default for general use, Spot tasks may override
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A description of what this means, either here or in the README, and how it affects the performance would be very helpful for those who aren't familiar with the code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there isn't really a place that documents other optimizer settings. for now, I provided more detailed inline comments and added it in the change log. if we write more detailed docs for all the settings this setting should be included as well.

Comment thread judo/tasks/base.py
@@ -161,3 +182,83 @@ def get_joint_velocity_start_index(self, joint_name: str) -> int:
joint_name: The name of the joint to get the starting index in the state array of.
"""
return self.model.nq + self.model.jnt_dofadr[self.model.joint(joint_name).id]

def success(self, model: MjModel, data: MjData, config: ConfigT, metadata: dict[str, Any] | None = None) -> bool:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd consider doing something like this instead:

raise NotImplementedError("The failure criteria needs to be implemented by the child task.")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment thread judo/tasks/base.py
"""
return False

def failure(self, model: MjModel, data: MjData, config: ConfigT, metadata: dict[str, Any] | None = None) -> bool:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same down here:

raise NotImplementedError("The episode failure criteria needs to be implemented by the child task.")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

).sum(axis=-1)

# Compute l2 distance from torso pos. to object pos.
torso_proximity_reward = config.w_torso_proximity * np.linalg.norm(body_pos - object_pos, axis=-1).mean(-1)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to check, should this be a positive or a negative reward?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah. this should negative. fixed now.

Comment thread judo/utils/mujoco.py Outdated
assert controls.shape[-1] == nu
assert controls.shape[0] == full_states.shape[0]
assert processed_controls.ndim == 3
# assert processed_controls.shape[-1] == nu
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment thread judo_cpp/rollout.cpp
auto heap_buf = new std::vector<double>(std::move(buf));
// Create a capsule that will delete the vector when the array is gone:
py::capsule free_when_done(heap_buf, [](void *p) {
delete reinterpret_cast<std::vector<double>*>(p);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I kind of wish we just used smart pointers here instead of constant castings

Comment thread judo_cpp/spot_rollout.cpp
// SpotThreadPool Implementation
// =============================================================================

SpotThreadPool::SpotThreadPool(int num_threads)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a separate spot thread pool vs a normal thread pool?

Comment thread judo_cpp/spot_rollout.cpp
}
}

void SpotThreadPool::worker_thread() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really unsure about why we can't use the PersistentThreadPool object for this. The only main difference seems to be that you must spin down the PersistentThreadPool after you run the rollout.

@bhung-bdai
Copy link
Copy Markdown
Collaborator

We also likely need to think about how to manage the assets in a more lightweight way, but that's not totally related to this work.

Copy link
Copy Markdown
Collaborator

@jbruedigam-bdai jbruedigam-bdai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with Brandon's comments and have a few additional ones

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we rename this to spot_relic_policy
Since we may have different policies later on

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep. this is now fixed

Comment thread judo_cpp/spot_rollout.cpp
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High-level questions: is the rollout specific to Spot or does it work for any mj model and policy?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for now they are specific to the relic policy for the spot robot. we do have plans for making this more general in the future.

johnzhang3 and others added 20 commits October 23, 2025 10:41
Resolved conflicts:
- CHANGELOG.md: Merged unreleased changes with v0.0.5 release
- judo/app/simulation.py: Accepted deletion (refactored into simulation/ module)
- judo/controller/controller.py: Kept C++ backend support and optimizer_config
- judo/tasks/__init__.py: Updated to use Task.name pattern for all tasks
- judo/tasks/base.py: Kept task_to_sim_ctrl method and default_backend
- Added name class attribute to SpotBox, SpotYellowChair, and SpotYellowChairRamp
- Required by main's task registration refactoring that uses Task.name
- Fixes AttributeError when importing judo.app.dora modules
- Updated make_controller() to pass optimizer_config parameter
- Updated ControllerNode.update_task() to pass optimizer_config when recreating controller
- Fixes Controller instantiation after merge with main that kept spot_cpp's modified __init__ signature
- Updated version to 0.0.5
- Kept C++ build configuration and pixi tasks
- Resolved conflicts in pyproject.toml and pixi.lock
Remove build-wheels.yml workflow and pyproject-build.toml as we'll revisit
the wheel building setup later.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add type: ignore comments for MuJoCo data array attributes since they
return ndarray[float64] which pyright doesn't accept as ndarray[_AnyShape].
The types are correct at runtime.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Change from mypy-style type: ignore to pyright: ignore[reportArgumentType]
to suppress the covariance type errors in MujocoState instantiation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace pyright: ignore comment with explicit typing.cast() calls to convert
MuJoCo data arrays to generic np.ndarray type for MujocoState constructor.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants