-
Notifications
You must be signed in to change notification settings - Fork 6
[FEATURE] remote policy with server/client #310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
b65f8cb to
f542ea6
Compare
alexmillane
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for putting this together.
Most of my comments are minor syntactic things that we can easily address.
I have one major comment on the design. At the moment, the server-client split point leaves half of gr00t policy running on the client and half (the model itself) running on the server. To me it would make sense to move the split point such that the whole policy is launched on the server. That way, in the remote inference case, we remove all gr00t details from the client side. It also simplifies the server side - the server just launches the same policy (called Gr00tClosedloopPolicy) that is launched in the local case.
This diagram explains (a simplified version) of the current design and the alternative that I'm proposing.
| #!/bin/bash | ||
| set -euo pipefail | ||
|
|
||
| # Script to install GR00T policy dependencies | ||
| # This script is called from the GR00T server Dockerfile | ||
|
|
||
| : "${GROOT_DEPS_GROUP:=base}" | ||
| : "${WORKDIR:=/workspace}" | ||
|
|
||
| echo "Installing GR00T with dependency group: $GROOT_DEPS_GROUP" | ||
|
|
||
| # CUDA environment variables for GR00T installation. | ||
| # In the PyTorch base image, CUDA is already configured, so we only | ||
| # set variables if CUDA_HOME exists. | ||
| if [ -d "/usr/local/cuda" ]; then | ||
| export CUDA_HOME=${CUDA_HOME:-/usr/local/cuda} | ||
| export PATH=${CUDA_HOME}/bin:${PATH} | ||
| export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:${LD_LIBRARY_PATH:-} | ||
| fi | ||
|
|
||
| echo "CUDA environment variables:" | ||
| echo "CUDA_HOME=${CUDA_HOME:-unset}" | ||
| echo "PATH=$PATH" | ||
| echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-unset}" | ||
|
|
||
| # Install system-level media libraries (no sudo in container) | ||
| echo "Installing system-level media libraries..." | ||
| apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| # Upgrade packaging tools | ||
| echo "Upgrading packaging tools..." | ||
| python -m pip install --upgrade setuptools packaging wheel | ||
|
|
||
| # Install Isaac-GR00T with the specified dependency group | ||
| echo "Installing Isaac-GR00T with dependency group: $GROOT_DEPS_GROUP" | ||
| python -m pip install --no-build-isolation --use-pep517 \ | ||
| -e "${WORKDIR}/submodules/Isaac-GR00T/[$GROOT_DEPS_GROUP]" | ||
|
|
||
| # Install flash-attn (optional, keep same version as Arena Dockerfile) | ||
| echo "Installing flash-attn..." | ||
| python -m pip install --no-build-isolation --use-pep517 flash-attn==2.7.1.post4 || \ | ||
| echo "flash-attn install failed, continue without it" | ||
|
|
||
| echo "GR00T dependencies installation completed successfully" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for putting this together.
This script seems to differ only very slightly from the install_gr00t_deps.sh. Is the only difference the python command used? I.e. python vs /isaac-sim/python.sh.
Could we combine these two scripts into a single script that takes an argument(s)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remove this to install_gr00t_deps.sh and add a arguement on install_gr00t_deps.s
docker/run_gr00t_server.sh
Outdated
| # REQUIRED: host models directory (must be set by user) | ||
| if [[ -z "${MODELS_DIR:-}" ]]; then | ||
| echo "ERROR: MODELS_DIR is not set." | ||
| echo "Please export MODELS_DIR to your host models directory, e.g.:" | ||
| echo " export MODELS_DIR=/path/to/your/models" | ||
| echo "Then run:" | ||
| echo " bash ./docker/run_gr00t_server.sh" | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion to used the same thing used in run_docker.sh:
Models are by default expected on the host at $HOME/models but this can be changed with the -d flag to the script.
See default and the optional override
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please check again ,I remove this part and add similar code as run_docker.sh
| --timeout_ms "${TIMEOUT_MS}" \ | ||
| --policy_type "${POLICY_TYPE}" \ | ||
| --policy_config_yaml_path "${POLICY_CONFIG_YAML_PATH}" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not strictly related to this MR, but I'm wondering if we should move all the gr00t related stuff. I.e. all the gr00t docker stuff and the gr00t related tests out of the core framework and into the isaaclab_arena_gr00t package. Actually this is basically certainly a good idea. Ideally the core framework would make no mention of gr00t.
It would probably make sense to do this before merging this MR. So we can get everything gr00t related in the right package, before expanding what we can do there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have now made the change I suggested above. All gr00t-related code lives in isaaclab_arena_gr00t
| num_steps = policy.get_trajectory_length(policy.get_trajectory_index()) | ||
|
|
||
| elif args.policy_type == "gr00t_closedloop": | ||
| from pathlib import Path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion to move to the top of the file.
We have some imports not at the top of the file if they require special dependencies that are conditionally not required. But prefer to put imports at the top.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| remote_group.add_argument( | ||
| "--remote_api_token", | ||
| type=str, | ||
| default=None, | ||
| help="Optional API token for remote policy server.", | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is an API token (optionally) required? Suggestion to expand the help string here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I add a detail description ,please check
| # if options is not None: | ||
| # payload["options"] = options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Intentional? options is currently unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove options,right now it is not used. When I was developing this, I considered that if some special information needed to be transmitted, an extra options field might be necessary. Since it is not needed at the moment, I have removed it.
| if isinstance(resp, dict) and "action" in resp: | ||
| return resp["action"] | ||
| return resp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have this optional unwrapping? Can we assume one form of the response? Either the action dict, or a dict containing the action dict? Is there a reason to accept both?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also removed
| if not Path(self.model_path).exists(): | ||
| warnings.warn( | ||
| "[GR00TConfig] model_path does not exist: " | ||
| f"{self.model_path}. No model checkpoint was found. " | ||
| "If this is the client side of a remote policy, this warning can be ignored. " | ||
| "However, if you are running a local policy or the server side of a remote policy, " | ||
| "the program will fail to load the model and cannot run correctly." | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion to remove this warning altogether. From the message the warning will certainly fire in cases where nothing is wrong. That's pretty confusing for the user, I feel, so I'd suggest that we remove this check here. Perhaps we could move the check to a more appropriate place? For example in the local policy where we know we need a valid model path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK ,I remove it now,we can add it once we have fully separated the policy and the client.
| POLICY_REGISTRY: dict[str, str] = { | ||
| # policy_type: "module_path:ClassName" | ||
| "gr00t_closedloop": "isaaclab_arena_gr00t.gr00t_remote_policy:Gr00tRemoteModelPolicy", | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking we should generalize and use our registry class used for Assets, Devices, and Retargetters for Policies too.
But I agree with the idea here: let's have a policy registry to allow users to register policies, to free the core code from directly depending on policy code. I think that this will be critical to getting policy (currently gr00t) code out of the core framework.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I add a registry class in remote_policy folder .please check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for doing that. Unfortunately we've double-done the work 🥲 . I added a new registry as part of moving the gr00t code out of the core package in #316 . I would suggest that you use my registry (which utilizes the same machinery we use for registering assets etc.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion to rename to remote_policy_config.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
f542ea6 to
698c2bf
Compare
698c2bf to
f9296d8
Compare

Hi Team,
I added a remote server/client mechanism for IsaacLab-Arena that allows the IsaacLab environment and the policy model environment to run separately. Alex and I wrote a design document(design doc) for this feature, which includes our discussions about the design. Below is an overview of this PR.
1. Functionality
Previously, the IsaacLab-Arena pipeline ran entirely in a single process:
IsaacLab env → Env policy class (e.g., Gr00tClosedloopPolicy) → local policy model (e.g., Gr00tPolicy), all in one environment.
This PR adds remote policy/server–client support and decouples the env policy class from the local policy model. The new pipeline becomes:
Client: IsaacLab env → Env policy class (e.g., Gr00tClosedloopPolicy)
Server: local policy model (e.g., Gr00tPolicy)
The client and server exchange observations and actions via sockets.
The server/client implementation lives in
isaaclab_arena/remote_policyinside IsaacLab-Arena. Users can copy this directory and import it on the server side directly, without needing to install it as a separate package.2. How to use it
On the client side, you still use
isaaclab_arena/examples/policy_runner.py, but you now pass a few extra arguments. Example:On the server side, there is a Python entry point
isaaclab_arena.remote_policy.remote_policy_server_runner.py. You specify host/port/etc., select the policy type, and provide the policy config file:3. Current example
Right now there is a working example for GR00T. On a given machine, the steps are:
Start the server
This uses the following defaults inside the script:
If needed, you can override these via command-line flags, for example:
Start the client
Set up the IsaacLab Docker container following
IsaacLab Arena docker documentation(no GR00T installation is needed inside this container).
Inside the container, run:
With this setup, you can obtain results for the GR1 open microwave task using a remote GR00T policy server.
and I can get the result:
Metrics: {'success_rate': 0.57, 'door_moved_rate': 0.935, 'num_episodes': 200}4. What remains to be done? Future work
At the moment, documentation for this feature has not been updated. I’d like the team to review this PR first and confirm that the usage and interface look reasonable.
Planned future work includes: