| name | deploy-stack |
| description | Guide for deploying the Embodied AI Platform CDK stacks (IsaacGr00tBatchStack + IsaacLabDcvStack) from scratch, including prerequisites, deployment, bootstrap monitoring, SSH setup via SSM, verification, training job submission, W&B visualization, and model evaluation. Also covers tearing down stacks and cleaning up retained resources. Use this skill whenever someone wants to deploy, redeploy, tear down the CDK infrastructure, visualize training metrics, submit training jobs, or run model evaluations — even if they just say "set up the stack", "deploy to AWS", "get DCV running", "view training loss", "run evals", or "clean up AWS resources".
|
Deploy Embodied AI Platform CDK Stacks
Autonomous execution: Execute all phases sequentially from Phase 1 through Phase 8c
without pausing for user input, except where a phase says "Prompt the user" — pause
and ask before proceeding with those optional phases. Only stop if a phase fails or
requires information not available in this document. When a phase depends on a
long-running process (e.g. CodeBuild, training job), poll until completion then proceed
to the next phase automatically.
This skill walks through deploying the two CDK stacks that make up the platform's AWS
infrastructure, setting up SSH access to the GPU workstation, submitting training jobs,
visualizing metrics, and running model evaluations.
The two stacks are:
- IsaacGr00tBatchStack — VPC, EFS, ECR, CodeBuild, AWS Batch compute environment and job queue
- IsaacLabDcvStack — GPU-accelerated DCV workstation (EC2 instance, accessed via SSM)
The DCV stack depends on the Batch stack (shares its VPC and EFS), so deploy order matters:
Batch first, DCV second. Destroy order is reversed: DCV first, Batch second.
Phase 1: Prerequisites
Before deploying, verify these are in place.
Region selection: Deploy in a region with g6e GPU instances for training
(e.g. us-west-2, us-east-1). The DCV instance uses g6 family by default.
Set your region: export AWS_DEFAULT_REGION=us-west-2 or use --profile
with a configured AWS CLI profile.
aws sts get-caller-identity --query '[Account, Arn]' --output text
npx cdk --version
jq --version
REPO_ROOT=$(git rev-parse --show-toplevel)
ls -la "$REPO_ROOT/.venv/bin/python"
cd "$REPO_ROOT" && python3 -m venv .venv
source .venv/bin/activate
pip install -r training/gr00t/infra/requirements.txt
pip install -r workstation/requirements.txt
Also confirm CDK has been bootstrapped in the target account/region:
aws cloudformation describe-stacks --stack-name CDKToolkit --query 'Stacks[0].StackStatus' --output text
If that fails, run npx cdk bootstrap from training/gr00t/infra/.
Phase 2: Validate with CDK Synth
The default app.py deploys with IsaacSim 5.1.0 / IsaacLab v2.3.0.
Always synth before deploying — it catches version mismatches, missing dependencies, and
code errors at zero cost (no AWS resources created).
cd training/gr00t/infra
npx cdk synth --quiet
Expected output: Successfully synthesized to .../cdk.out listing both stack names.
If synth fails, fix the error before proceeding. Common issues:
- Missing Python dependencies ->
pip install -r requirements.txt
- Version validation errors -> check
workstation/versions.py for supported IsaacSim versions
Phase 2.5: Probe GPU Instance Capacity
--dry-run does not test actual instance capacity — it only validates IAM permissions.
The only reliable check is a real launch + immediate terminate. Run this after
IsaacGr00tBatchStack deploys (so its VPC subnets exist), before deploying IsaacLabDcvStack.
UBUNTU_AMI=$(aws ec2 describe-images --owners amazon \
--filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" \
"Name=state,Values=available" \
--query 'sort_by(Images, &CreationDate)[-1].ImageId' --output text)
SUBNETS=$(aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$VpcId" "Name=mapPublicIpOnLaunch,Values=true" \
--query 'Subnets[].{AZ:AvailabilityZone,SubnetId:SubnetId}' --output json)
FOUND=false
for ITYPE in g6.4xlarge g6.2xlarge g5.2xlarge; do
while IFS= read -r row; do
AZ=$(echo "$row" | jq -r '.AZ')
SUBNET=$(echo "$row" | jq -r '.SubnetId')
RESULT=$(aws ec2 run-instances \
--image-id "$UBUNTU_AMI" --instance-type "$ITYPE" \
--subnet-id "$SUBNET" --count 1 \
--no-associate-public-ip-address \
--query 'Instances[0].InstanceId' --output text 2>&1)
if [[ "$RESULT" == i-* ]]; then
aws ec2 terminate-instances --instance-ids "$RESULT" > /dev/null
echo "✅ $ITYPE in $AZ — available. Set in app.py:"
FOUND=; 2
< <( | jq -c )
||
Update training/gr00t/infra/app.py with the printed availability_zone and instance_type
values before running Step 2 below.
Phase 3: Deploy Stacks
No context parameters needed — the stacks auto-create VPC, EFS, and ECR.
N1.6 build target: The BatchStack deploy automatically triggers a CodeBuild run.
Pass --context build_target=n16 so it builds N16/Dockerfile (not the N1.5 default).
cd training/gr00t/infra
npx cdk deploy IsaacGr00tBatchStack --require-approval=never --context build_target=n16
npx cdk deploy IsaacLabDcvStack --require-approval=never
Parallel monitoring during Step 2: Open a second terminal and poll CloudFormation
events directly — avoids stdout buffering delays from CDK:
while true; do
STATUS=$(aws cloudformation describe-stacks --stack-name IsaacLabDcvStack \
--query 'Stacks[0].StackStatus' --output text 2>/dev/null || echo "CREATING")
printf '\n=== %s [%s] ===\n' "$(date +%H:%M:%S)" "$STATUS"
aws cloudformation describe-stack-events --stack-name IsaacLabDcvStack \
--query 'StackEvents[0:8].[Timestamp,ResourceStatus,LogicalResourceId]' \
--output table 2>/dev/null
[[ "$STATUS" =~ (COMPLETE|FAILED) ]] && break
sleep 20
done
After both deploys complete, capture the stack outputs as shell variables for later phases.
The jq approach handles values with spaces safely (unlike eval-based approaches):
BATCH_OUTPUTS=$(aws cloudformation describe-stacks --stack-name IsaacGr00tBatchStack \
--query 'Stacks[0].Outputs' --output json)
export EcrImageUri=$(echo "$BATCH_OUTPUTS" | jq -r '.[] | select(.OutputKey=="EcrImageUri") | .OutputValue')
export EFSFileSystemId=$(echo "$BATCH_OUTPUTS" | jq -r '.[] | select(.OutputKey=="EFSFileSystemId") | .OutputValue')
export EFSSecurityGroupId=$(echo "$BATCH_OUTPUTS" | jq -r '.[] | select(.OutputKey=="EFSSecurityGroupId") | .OutputValue')
export VpcId=$(echo "$BATCH_OUTPUTS" | jq -r '.[] | select(.OutputKey=="VpcId") | .OutputValue')
export CodeBuildProjectName=$(echo "$BATCH_OUTPUTS" | jq -r '.[] | select(.OutputKey=="CodeBuildProjectName") | .OutputValue // empty')
export CheckpointS3UploadUri=$(echo "$BATCH_OUTPUTS" | jq -r '.[] | select(.OutputKey=="CheckpointS3UploadUri") | .OutputValue // empty')
DCV_OUTPUTS=$(aws cloudformation describe-stacks --stack-name IsaacLabDcvStack \
--query 'Stacks[0].Outputs' --output json)
export InstanceId=$(echo "$DCV_OUTPUTS" | jq -r '.[] | select(.OutputKey | startswith("DCVInstanceId")) | .OutputValue')
InstancePublicIP=$( | jq -r )
DCVWebURL=$( | jq -r )
DCVCredentials=$( | jq -r )
The variable names match the CDK CfnOutput keys exactly (PascalCase). These are used
in later phases as $InstanceId, $InstancePublicIP, $EcrImageUri, etc.
Phase 4: Monitor Bootstrap Completion
The DCV instance runs a bootstrap that installs NVIDIA drivers, Docker, pulls the IsaacLab
container from NGC, installs DCV, and mounts EFS. CloudFormation waits for a cfn-signal
before marking CREATE_COMPLETE — so the Phase 3 CDK deploy does not return until bootstrap
finishes. Phase 4 monitoring is for a parallel terminal to track mid-progress.
while true; do
CMD_ID=$(aws ssm send-command \
--instance-ids $InstanceId \
--document-name AWS-RunShellScript \
--parameters 'commands=["cat /var/log/dcv-bootstrap.summary 2>/dev/null || echo BOOTSTRAP_NOT_STARTED"]' \
--output text --query 'Command.CommandId')
sleep 5
OUTPUT=$(aws ssm get-command-invocation \
--command-id $CMD_ID --instance-id $InstanceId \
--query 'StandardOutputContent' --output text)
printf '\n=== %s ===\n%s\n' "$(date +%H:%M:%S)" "$OUTPUT"
echo "$OUTPUT" | grep -q "STEP_FAIL" && echo "Bootstrap FAILED" && break
sleep 55
done
Steps appear as STEP_OK entries. Total time: ~15-20 minutes. You may see
STEP_WARN:nvidia-driver-loaded — this is expected on first boot (the NVIDIA kernel
module loads after a reboot).
If any step shows STEP_FAIL, check the detailed log via SSM:
sudo grep -A 50 "== START: <step-name> ==" /var/log/dcv-bootstrap.log
Phase 5: Set Up SSH via SSM
SSH through SSM tunnels through AWS Session Manager — it doesn't require port 22 to be
open in the security group.
5a. Verify Session Manager plugin is installed locally
session-manager-plugin --version
If missing: Ubuntu/Debian sudo dpkg -i session-manager-plugin.deb, macOS brew install --cask session-manager-plugin.
5b. Push SSH public key to the instance
test -f ~/.ssh/id_ed25519.pub || ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
PUBKEY=$(cat ~/.ssh/id_ed25519.pub)
aws ssm send-command \
--instance-ids $InstanceId \
--document-name AWS-RunShellScript \
--parameters "commands=[\"mkdir -p /home/ubuntu/.ssh && echo '$PUBKEY' > /home/ubuntu/.ssh/authorized_keys && chmod 700 /home/ubuntu/.ssh && chmod 600 /home/ubuntu/.ssh/authorized_keys && chown -R ubuntu:ubuntu /home/ubuntu/.ssh\"]" \
--output text --query 'Command.CommandId'
5c. Configure SSH config
Add or update this entry in ~/.ssh/config:
Host dcv-isaac
HostName <$InstanceId>
User ubuntu
IdentityFile ~/.ssh/id_ed25519
ProxyCommand aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p' --region <$AWS_DEFAULT_REGION>
Replace <$InstanceId> and <$AWS_DEFAULT_REGION> with the values from Phase 3.
5d. Test the connection
ssh -o StrictHostKeyChecking=accept-new dcv-isaac "echo 'SSH OK'"
Phase 5e: Wait for Auto-Reboot
The bootstrap automatically reboots the instance after sending cfn-signal. This loads the
NVIDIA kernel module (which can't load on the same boot that installs the driver). Wait
~2 minutes after cdk deploy returns, then verify:
ssh dcv-isaac "nvidia-smi --query-gpu=name --format=csv,noheader"
If nvidia-smi fails, the reboot may still be in progress. Wait another minute and retry.
Phase 6: Verify Everything Works
ssh dcv-isaac "cat /var/log/dcv-bootstrap.summary"
ssh dcv-isaac "mount | grep efs"
ssh dcv-isaac "docker images | grep isaac-lab"
ssh dcv-isaac "test -x /usr/local/bin/run-isaaclab.sh && echo 'Helper script OK'"
ssh dcv-isaac "nvidia-smi --query-gpu=name --format=csv,noheader"
The DCV web console is available at https://<elastic-ip>:8443 (accept the
self-signed certificate warning). DCV is password-protected (credentials in stack outputs).
If you deployed with --context public_dcv_access=false (SSM-only mode), ports
8443/8080 are not open. Use SSH port forwarding instead:
ssh -f -N -L 8443:localhost:8443 -L 8080:localhost:8080 dcv-isaac
Then open https://localhost:8443 for DCV or http://localhost:8080 for W&B.
Claude Code does not need these port forwards — it accesses everything via SSH commands.
Phase 6a: Container and LeIsaac Testing
6a.1 Launch IsaacLab Container
The helper script wraps docker run with GPU access, X11 forwarding, cache volumes, and
persistent package mounts. On first launch, it auto-installs leisaac and downloads scene
assets (~60 seconds total).
ssh dcv-isaac
run-isaaclab.sh
The container Python is at /workspace/isaaclab/_isaac_sim/python.sh (an Isaac Sim
wrapper). All Python commands below use this wrapper.
6a.2 Verify Inside Container
/workspace/isaaclab/_isaac_sim/python.sh --version
nvidia-smi
Exit the container with exit or Ctrl-D.
6a.3 Verify Policy Client N1.6 Patch
The run-isaaclab.sh helper patches policy_inference.py for headless keyboard
support on first launch. Confirm the patch applied:
ssh dcv-isaac "grep -q 'self._appwindow is not None' /home/ubuntu/leisaac-repo/scripts/evaluation/policy_inference.py && echo 'Keyboard patch OK' || echo 'MISSING — re-run run-isaaclab.sh'"
If missing, re-run run-isaaclab.sh -c 'echo patched' and check again.
Phase 7: Submit Training Job
7a. Verify container image is ready
The Batch stack triggers a CodeBuild project that builds and pushes the training container
to ECR. If you deployed with --context build_target=n16 in Phase 3, the N1.6 container
is already building. This takes ~15 minutes for N1.6 (PyTorch3D compilation). Note: Redeploying
BatchStack auto-triggers a new CodeBuild run that may fail due to transient Docker Hub
rate limits — check for a recent successful build, not just the latest build status:
aws codebuild batch-get-projects --names "$CodeBuildProjectName" \
--query 'projects[0].lastSuccessfulBuild.endTime' --output text
BUILD_ID=$(aws codebuild list-builds-for-project --project-name "$CodeBuildProjectName" \
--query 'ids[0]' --output text)
aws codebuild batch-get-builds --ids "$BUILD_ID" \
--query 'builds[0].[currentPhase, buildStatus]' --output text
Do not submit a Batch job until the build shows SUCCEEDED.
7b. Submit the training job
JOB_ID=$(aws batch submit-job \
--job-name "IsaacGr00tFinetuning" \
--job-queue "IsaacGr00tJobQueue" \
--job-definition "IsaacGr00tJobDefinition" \
--query 'jobId' --output text)
echo "Job submitted: $JOB_ID"
Monitor progress:
aws batch describe-jobs --jobs $JOB_ID --query 'jobs[0].status' --output text
aws logs tail /aws/batch/job --follow \
--log-stream-names "$(aws batch describe-jobs --jobs $JOB_ID \
--query 'jobs[0].container.logStreamName' --output text)"
Default: 6000 steps (~2 hours on g6e.4xlarge). Checkpoints saved every 2000 steps
at /mnt/efs/gr00t/checkpoints/$JOB_ID/.
Phase 7a: Visualize Training Metrics (W&B)
Prompt the user: "Would you like to visualize the training loss curves? This uses
a local W&B server on the DCV instance — no W&B license or cloud account required."
Only proceed with this phase if the user says yes; otherwise skip to Phase 8.
Training logs to W&B in offline mode — run data persists on EFS after the container exits.
ssh dcv-isaac "docker run -d --name wandb-local -p 8080:8080 -v wandb-data:/vol wandb/local:latest"
Access the W&B dashboard:
- If deployed with default
public_dcv_access=true: open http://<$InstancePublicIP>:8080
- If deployed with
--context public_dcv_access=false: use SSH port forward first:
ssh -f -N -L 8080:localhost:8080 dcv-isaac, then open http://localhost:8080
Create an account on the local instance and generate an API key from Settings → API Keys.
This is entirely local — no external W&B service is contacted.
ssh dcv-isaac "bash -l -c '
export WANDB_BASE_URL=http://localhost:8080
export WANDB_API_KEY=<your-local-api-key>
source /home/ubuntu/.venv/bin/activate
wandb sync /mnt/efs/gr00t/checkpoints/$JOB_ID/wandb/offline-run-*
'"
View loss curves in the W&B dashboard. Stop/restart the server anytime — data persists
in the wandb-data Docker volume.
Phase 8: Start Policy Server
All evaluation phases (8a, 8b, 8c) require a policy server serving the trained checkpoint.
Start it once here; it stays running until cleanup.
Serves the checkpoint as a ZMQ policy server on TCP port 5555. Do not use
--use-sim-policy-wrapper — the Gr00t16ServicePolicyClient in LeIsaac sends
observations in the nested format the server expects natively, and the wrapper would
cause a key mismatch error.
CHECKPOINT=/mnt/efs/gr00t/checkpoints/$JOB_ID/checkpoint-6000
ssh dcv-isaac "aws ecr get-login-password --region $AWS_DEFAULT_REGION | \
docker login --username AWS --password-stdin ${EcrImageUri%%/*}"
ssh dcv-isaac "docker pull $EcrImageUri"
ssh dcv-isaac "docker run --gpus all -d \
--name gr00t-policy-server \
--shm-size=8g \
--network host \
--entrypoint /bin/sh \
-v /mnt/efs:/mnt/efs \
$EcrImageUri \
-c 'cd /workspace/gr00t-repo && python3 -m gr00t.eval.run_gr00t_server \
--model-path $CHECKPOINT \
--embodiment-tag NEW_EMBODIMENT \
--port 5555'"
Use --entrypoint /bin/sh because the container's default entrypoint invokes a
uv-managed Python that can't be exec'd directly. The server module is
gr00t.eval.run_gr00t_server which wraps Gr00tPolicy in a PolicyServer (ZMQ).
Allow ~60 seconds for model loading before the server begins accepting connections.
Verify the server is listening:
ssh dcv-isaac "docker logs gr00t-policy-server 2>&1 | tail -5"
ssh dcv-isaac "ss -tlnp | grep 5555"
For a direct inference test (without IsaacSim), see ../references/policy-server-test.md.
For observation/response format details, see ../references/eval-format.md.
Phase 8a: Open-Loop Evaluation (Optional)
Prompt the user: "Would you like to run open-loop evaluation? This computes MSE/MAE
against a dataset to measure action prediction quality. You can use the included sample
dataset or provide your own."
If the user declines, skip to Phase 8b.
Ask for dataset: "Which dataset should we use for evaluation?
- Sample dataset (default) — the included
training/sample_dataset/ (57 episodes)
- Custom dataset — provide a local path or an EFS path (e.g.
/mnt/efs/gr00t/my_dataset/)
Custom datasets must have the same LeRobot format with data/, meta/, and videos/
directories, plus meta/modality.json and meta/stats.json."
Prepare dataset on EFS
The training job uses a dataset baked into the container during CodeBuild, but open-loop
evaluation runs on the DCV instance and needs the dataset on EFS.
If using the sample dataset:
REPO_ROOT=$(git rev-parse --show-toplevel)
cd "$REPO_ROOT" && git lfs pull --include="training/sample_dataset/"
ssh dcv-isaac "sudo mkdir -p /mnt/efs/gr00t/sample_dataset && sudo chown -R ubuntu:ubuntu /mnt/efs/gr00t"
rsync -avz "$REPO_ROOT/training/sample_dataset/" dcv-isaac:/mnt/efs/gr00t/sample_dataset/
DATASET_PATH=/mnt/efs/gr00t/sample_dataset
If using a custom local dataset:
ssh dcv-isaac "sudo mkdir -p /mnt/efs/gr00t/<dataset-name> && sudo chown -R ubuntu:ubuntu /mnt/efs/gr00t"
rsync -avz "<local-dataset-path>/" dcv-isaac:/mnt/efs/gr00t/<dataset-name>/
DATASET_PATH=/mnt/efs/gr00t/<dataset-name>
If the dataset is already on EFS, just set DATASET_PATH to its path.
Generate metadata files (if not already present)
ssh dcv-isaac "cat > $DATASET_PATH/meta/modality.json << 'EOF'
{
\"action\": {
\"single_arm\": {\"start\": 0, \"end\": 5},
\"gripper\": {\"start\": 5, \"end\": 6}
},
\"state\": {
\"single_arm\": {\"start\": 0, \"end\": 5},
\"gripper\": {\"start\": 5, \"end\": 6}
},
\"annotation\": {
\"human.task_description\": {
\"original_key\": \"task_index\"
}
},
\"video\": {
\"front\": {
\"original_key\": \"observation.images.front\",
\"shape\": [480, 640, 3]
},
\"wrist\": {
\"original_key\": \"observation.images.wrist\",
\"shape\": [480, 640, 3]
}
}
}
EOF"
ssh dcv-isaac "docker run --gpus all --rm \
-v /mnt/efs:/mnt/efs \
--entrypoint /bin/sh \
$EcrImageUri \
-c 'cd /workspace/gr00t-repo && python3 -m gr00t.data.stats \
--dataset-path $DATASET_PATH \
--embodiment-tag NEW_EMBODIMENT'"
modality.json maps the dataset's column names to the model's expected modality keys
(video cameras, state joints, action joints, language annotations). stats.json
contains per-feature normalization statistics. Both are required by open_loop_eval.
Expected warning: The stats command may print KeyError: 'new_embodiment' from
generate_rel_stats — this is non-blocking. The main stats.json file is still
written successfully. Verify with: ssh dcv-isaac "ls -la $DATASET_PATH/meta/stats.json"
Run the evaluation
ssh dcv-isaac "docker run --gpus all --rm \
--network host \
-v /mnt/efs:/mnt/efs:ro \
--shm-size=8g \
--entrypoint /bin/sh \
$EcrImageUri \
-c 'cd /workspace/gr00t-repo && python3 -m gr00t.eval.open_loop_eval \
--host 127.0.0.1 \
--port 5555 \
--model-path None \
--embodiment-tag NEW_EMBODIMENT \
--dataset-path $DATASET_PATH \
--modality-keys single_arm gripper'"
Note: The module is gr00t.eval.open_loop_eval (not robot_eval). Use
--entrypoint /bin/sh because the container's default entrypoint invokes a uv-managed
Python that can't be exec'd directly. The --embodiment-tag is case-sensitive and must
be NEW_EMBODIMENT (uppercase). --model-path None tells the eval to use the running
policy server (--host/--port) instead of loading the model directly.
Phase 8b: Closed-Loop Smoke Test (Headless)
This runs a single headless simulation episode to verify that IsaacSim launches, connects
to the policy server, and completes without crashing. It is a smoke test only — success
rate is irrelevant at this stage.
Important: Do NOT use run-isaaclab.sh for automated evaluation — it launches an
interactive bash shell and routes arguments through runheadless.sh (the Kit launcher),
which does not execute the Python eval script. Use the direct docker run command below
with --entrypoint set to python.sh.
run-isaaclab.sh handles leisaac package install, scene asset download, and repo clone
on first launch. Run it once interactively to set up prerequisites if they haven't been
installed yet:
ssh dcv-isaac "run-isaaclab.sh -c 'echo prerequisites installed'"
Run one episode headlessly via SSH:
DCV_DISPLAY=$(ssh dcv-isaac "ls /tmp/.X11-unix/ | sed 's/X/:/'" | head -1)
echo "Using DISPLAY=$DCV_DISPLAY"
ssh dcv-isaac "docker run --gpus all --rm --network host \
-e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y \
-e PYTHONPATH=/workspace/isaaclab-pkgs \
-e PYTHONUNBUFFERED=1 \
-e DISPLAY=$DCV_DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix:ro \
-v /home/ubuntu/isaaclab-pkgs:/workspace/isaaclab-pkgs:rw \
-v /home/ubuntu/leisaac-repo/scripts:/workspace/scripts:ro \
-v /home/ubuntu/leisaac-assets:/assets:ro \
-v /mnt/efs:/mnt/efs \
-e LEISAAC_ASSETS_ROOT=/assets \
--shm-size=8g \
--entrypoint /workspace/isaaclab/_isaac_sim/python.sh \
nvcr.io/nvidia/isaac-lab:2.3.0 \
/workspace/scripts/evaluation/policy_inference.py \
--task=LeIsaac-SO101-PickOrange-v0 \
--eval_rounds=1 \
--policy_type=gr00tn1.6 \
--policy_host=localhost \
--policy_port=5555 \
--policy_action_horizon=16 \
--policy_language_instruction='Pick up the orange and place it on the plate' \
--device=cuda \
--enable_cameras 2>&1 | tee /mnt/efs/gr00t/eval-results.log"
DISPLAY and the X11 socket mount are required because --enable_cameras uses
Vulkan rendering for camera frames, which needs an active display. DCV picks
display :0 or :1 depending on what's available at boot — the detection
command above reads it from /tmp/.X11-unix/. Without these flags, the render
loop blocks silently with 0% GPU utilization despite allocating VRAM.
PYTHONUNBUFFERED=1 ensures eval output streams in real time over SSH.
First run takes ~5 minutes for IsaacSim shader compilation before the episode begins.
Expected: The episode completes (success or timeout) without errors. If it runs to
completion, the simulation pipeline is working correctly. Proceed to Phase 8c for visual
inspection.
For eval parameters and format details, see ../references/eval-format.md.
Phase 8c: Visual Policy Inspection (GUI)
Prompt the user: "Would you like to visually inspect the trained policy in the
simulator? This opens the DCV desktop where you can watch the robot arm execute actions
in real time — the best way to qualitatively evaluate policy performance."
This is the primary way to evaluate whether the policy has learned useful behavior.
The DCV desktop renders the full IsaacSim environment so you can watch the robot arm
interact with objects in real time.
Connect to the DCV desktop
- If deployed with default
public_dcv_access=true: open https://<$InstancePublicIP>:8443
(accept the self-signed certificate warning)
- If deployed with
--context public_dcv_access=false: start an SSH port forward first:
ssh -f -N -L 8443:localhost:8443 dcv-isaac, then open https://localhost:8443
Log in with the DCV credentials from the stack outputs ($DCVCredentials).
Run the evaluation from the DCV terminal
- Open a terminal in the DCV desktop
- Launch the IsaacLab container:
run-isaaclab.sh
- Inside the container, run the evaluation:
/workspace/isaaclab/_isaac_sim/python.sh \
/workspace/scripts/evaluation/policy_inference.py \
--task=LeIsaac-SO101-PickOrange-v0 \
--eval_rounds=1 \
--policy_type=gr00tn1.6 \
--policy_host=localhost \
--policy_port=5555 \
--policy_action_horizon=16 \
--policy_language_instruction='Pick up the orange and place it on the plate' \
--device=cuda \
--enable_cameras
First GUI run takes ~5 minutes for IsaacSim shader compilation. These shaders are
cached in the Docker volumes mounted by run-isaaclab.sh, so subsequent runs start
much faster.
A simulation window will appear showing the robot arm, the table scene, and the orange.
Watch the arm's behavior to judge whether the policy has learned the intended task.
Increase --eval_rounds to run multiple episodes for a more thorough assessment.
Cleanup: Tearing Down Stacks
Step 1: Stop running containers
ssh dcv-isaac "docker rm -f gr00t-policy-server wandb-local 2>/dev/null; echo 'Containers stopped'"
Step 2: Destroy stacks (DCV first, then Batch)
aws ec2 modify-instance-attribute --instance-id $InstanceId --no-disable-api-termination
cd training/gr00t/infra
npx cdk destroy IsaacLabDcvStack --force
npx cdk destroy IsaacGr00tBatchStack --force
Step 3: Clean up retained resources
Three resources have RemovalPolicy.RETAIN and survive stack deletion:
BUCKET=<bucket-name-from-stack-outputs>
aws s3 rb s3://$BUCKET --force
aws ecr delete-repository --repository-name gr00t-finetune --force --region $AWS_DEFAULT_REGION
aws efs delete-file-system --file-system-id $EFSFileSystemId
If EFS deletion fails with "FileSystemInUse", delete lingering mount targets first:
aws efs describe-mount-targets --file-system-id $EFSFileSystemId --query 'MountTargets[].MountTargetId' --output text | xargs -n1 aws efs delete-mount-target --mount-target-id
See ../references/troubleshooting.md for common issues and fixes.