Unverified Commit ff6c9657 authored by Mayank Mittal's avatar Mayank Mittal Committed by GitHub

Fixes issue with asset deinitialization due to torch > 2.1 (#640)

# Description

Ever since we upgraded the torch version from 2.0.1 (in Isaac Sim
2023.1.1) to 2.2 (in Isaac Sim 4.0), we have seen some of the tests
(such as `test_articulation.py`) fail due to improper deinitialization
of the created class.

This MR fixes this issue by creating a dummy tensor inside the
`SimulationContext` in Isaac Lab. While this is a hack, it is effective
and helps alleviate the problem. It is hard to figure out the root cause
without really understanding what changed on the torch side.

Fixes #639

## Type of change

- Bug fix (non-breaking change which fixes an issue)
- This change requires a documentation update

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
parent 08ebac7a
......@@ -232,9 +232,9 @@ x11_cleanup() {
}
submit_job() {
echo "[INFO] Arguments passed to job script ${@}"
case $CLUSTER_JOB_SCHEDULER in
"SLURM")
CMD=sbatch
......
......@@ -7,6 +7,7 @@ import builtins
import enum
import numpy as np
import sys
import torch
import traceback
import weakref
from collections.abc import Iterator
......@@ -195,6 +196,12 @@ class SimulationContext(_SimulationContext):
# note: we do it once here because it reads the VERSION file from disk and is not expected to change.
self._isaacsim_version = get_version()
# create a tensor for gravity
# note: this line is needed to create a "tensor" in the device to avoid issues with torch 2.1 onwards.
# the issue is with some heap memory corruption when torch tensor is created inside the asset class.
# you can reproduce the issue by commenting out this line and running the test `test_articulation.py`.
self._gravity_tensor = torch.tensor(self.cfg.gravity, dtype=torch.float32, device=self.cfg.device)
# add callback to deal the simulation app when simulation is stopped.
# this is needed because physics views go invalid once we stop the simulation
if not builtins.ISAAC_LAUNCHED_FROM_TERMINAL:
......
......@@ -52,6 +52,7 @@ class TestArticulation(unittest.TestCase):
# stop simulation
self.sim.stop()
# clear the stage
self.sim.clear_all_callbacks()
self.sim.clear_instance()
"""
......@@ -75,8 +76,12 @@ class TestArticulation(unittest.TestCase):
# Play sim
self.sim.reset()
# Check that boundedness of articulation is correct
self.assertEqual(ctypes.c_long.from_address(id(robot)).value, 1)
# Check if robot is initialized
self.assertTrue(robot._is_initialized)
self.assertTrue(robot.is_initialized)
# Check that floating base
self.assertFalse(robot.is_fixed_base)
# Check buffers that exists and have correct shapes
......@@ -114,8 +119,12 @@ class TestArticulation(unittest.TestCase):
# Play sim
self.sim.reset()
# Check that boundedness of articulation is correct
self.assertEqual(ctypes.c_long.from_address(id(robot)).value, 1)
# Check if robot is initialized
self.assertTrue(robot._is_initialized)
self.assertTrue(robot.is_initialized)
# Check that floating base
self.assertFalse(robot.is_fixed_base)
# Check buffers that exists and have correct shapes
......@@ -153,8 +162,12 @@ class TestArticulation(unittest.TestCase):
# Play sim
self.sim.reset()
# Check that boundedness of articulation is correct
self.assertEqual(ctypes.c_long.from_address(id(robot)).value, 1)
# Check if robot is initialized
self.assertTrue(robot._is_initialized)
self.assertTrue(robot.is_initialized)
# Check that fixed base
self.assertTrue(robot.is_fixed_base)
# Check buffers that exists and have correct shapes
......@@ -207,8 +220,12 @@ class TestArticulation(unittest.TestCase):
# Play sim
self.sim.reset()
# Check that boundedness of articulation is correct
self.assertEqual(ctypes.c_long.from_address(id(robot)).value, 1)
# Check if robot is initialized
self.assertTrue(robot._is_initialized)
self.assertTrue(robot.is_initialized)
# Check that fixed base
self.assertTrue(robot.is_fixed_base)
# Check buffers that exists and have correct shapes
......@@ -247,8 +264,12 @@ class TestArticulation(unittest.TestCase):
# Play sim
self.sim.reset()
# Check that boundedness of articulation is correct
self.assertEqual(ctypes.c_long.from_address(id(robot)).value, 1)
# Check if robot is initialized
self.assertTrue(robot._is_initialized)
self.assertTrue(robot.is_initialized)
# Check that fixed base
self.assertTrue(robot.is_fixed_base)
# Check buffers that exists and have correct shapes
......@@ -281,8 +302,12 @@ class TestArticulation(unittest.TestCase):
# Play sim
self.sim.reset()
# Check that boundedness of articulation is correct
self.assertEqual(ctypes.c_long.from_address(id(robot)).value, 1)
# Check if robot is initialized
self.assertTrue(robot._is_initialized)
self.assertTrue(robot.is_initialized)
# Check that floating base
self.assertTrue(robot.is_fixed_base)
# Check buffers that exists and have correct shapes
......@@ -327,8 +352,12 @@ class TestArticulation(unittest.TestCase):
# Play sim
self.sim.reset()
# Check that boundedness of articulation is correct
self.assertEqual(ctypes.c_long.from_address(id(robot)).value, 1)
# Check if robot is initialized
self.assertTrue(robot._is_initialized)
self.assertTrue(robot.is_initialized)
# Check that fixed base
self.assertFalse(robot.is_fixed_base)
# Check buffers that exists and have correct shapes
......@@ -372,7 +401,7 @@ class TestArticulation(unittest.TestCase):
# Play sim
self.sim.reset()
# Check if robot is initialized
self.assertFalse(robot._is_initialized)
self.assertFalse(robot.is_initialized)
def test_out_of_range_default_joint_vel(self):
"""Test that the default joint velocity from configuration is out of range."""
......@@ -390,7 +419,7 @@ class TestArticulation(unittest.TestCase):
# Play sim
self.sim.reset()
# Check if robot is initialized
self.assertFalse(robot._is_initialized)
self.assertFalse(robot.is_initialized)
def test_external_force_on_single_body(self):
"""Test application of external force on the base of the robot."""
......
......@@ -102,7 +102,7 @@ class TestRigidObject(unittest.TestCase):
sim.reset()
# Check if object is initialized
self.assertTrue(cube_object._is_initialized)
self.assertTrue(cube_object.is_initialized)
self.assertEqual(len(cube_object.body_names), 1)
# Check buffers that exists and have correct shapes
......@@ -133,7 +133,7 @@ class TestRigidObject(unittest.TestCase):
sim.reset()
# Check if object is initialized
self.assertTrue(cube_object._is_initialized)
self.assertTrue(cube_object.is_initialized)
self.assertEqual(len(cube_object.body_names), 1)
# Check buffers that exists and have correct shapes
......@@ -166,7 +166,7 @@ class TestRigidObject(unittest.TestCase):
sim.reset()
# Check if object is initialized
self.assertFalse(cube_object._is_initialized)
self.assertFalse(cube_object.is_initialized)
def test_external_force_on_single_body(self):
"""Test application of external force on the base of the object.
......
# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This script demonstrates reference count of the robot view in Isaac Sim.
When we make a class instance, the reference count of the class instance should always be 1.
However, in this script, the reference count of the robot view is 2 after the class is created.
This causes a memory leak in the Isaac Sim simulator and the robot view is not garbage collected.
The issue is observed with torch 2.2 and Isaac Sim 4.0. It works fine with torch 2.0.1 and Isaac Sim 2023.1.
It can be resolved by uncommenting the line that creates a dummy tensor in the main function.
To reproduce the issue, run this script and check the reference count of the robot view.
For more details, please check: https://github.com/isaac-sim/IsaacLab/issues/639
"""
"""Launch Isaac Sim Simulator first."""
import contextlib
with contextlib.suppress(ModuleNotFoundError):
import isaacsim # noqa: F401
from omni.isaac.kit import SimulationApp
# launch omniverse app
simulation_app = SimulationApp({"headless": True})
"""Rest everything follows."""
import ctypes
import gc
import torch # noqa: F401
import carb
try:
import omni.isaac.nucleus as nucleus_utils
except ModuleNotFoundError:
import omni.isaac.core.utils.nucleus as nucleus_utils
import omni.isaac.core.utils.prims as prim_utils
from omni.isaac.core.articulations import ArticulationView
from omni.isaac.core.simulation_context import SimulationContext
from omni.isaac.core.utils.carb import set_carb_setting
# check nucleus connection
if nucleus_utils.get_assets_root_path() is None:
msg = (
"Unable to perform Nucleus login on Omniverse. Assets root path is not set.\n"
"\tPlease check: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/overview.html#omniverse-nucleus"
)
carb.log_error(msg)
raise RuntimeError(msg)
ISAAC_NUCLEUS_DIR = f"{nucleus_utils.get_assets_root_path()}/Isaac"
"""Path to the `Isaac` directory on the NVIDIA Nucleus Server."""
ISAACLAB_NUCLEUS_DIR = f"{ISAAC_NUCLEUS_DIR}/IsaacLab"
"""Path to the `Isaac/IsaacLab` directory on the NVIDIA Nucleus Server."""
"""
Classes
"""
class AnymalArticulation:
"""Anymal articulation class."""
def __init__(self):
"""Initialize the Anymal articulation class."""
# resolve asset
usd_path = f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-C/anymal_c.usd"
# add asset
print("Loading robot from: ", usd_path)
prim_utils.create_prim("/World/Robot", usd_path=usd_path, translation=(0.0, 0.0, 0.6))
# Resolve robot prim paths
root_prim_path = "/World/Robot/base"
# Setup robot
self.view = ArticulationView(root_prim_path, name="ANYMAL")
def __del__(self):
"""Delete the Anymal articulation class."""
print("Deleting the Anymal view.")
self.view = None
def initialize(self):
"""Initialize the Anymal view."""
self.view.initialize()
"""
Main
"""
def main():
"""Spawns the ANYmal robot and clones it using Isaac Sim Cloner API."""
# Load kit helper
sim = SimulationContext(physics_dt=0.005, rendering_dt=0.005, backend="torch", device="cuda:0")
# Enable hydra scene-graph instancing
# this is needed to visualize the scene when flatcache is enabled
set_carb_setting(sim._settings, "/persistent/omnihydra/useSceneGraphInstancing", True)
# Create a dummy tensor for testing
# Uncommenting the following line will yield a reference count of 1 for the robot (as desired)
# dummy_tensor = torch.zeros(1, device="cuda:0")
# Robot
robot = AnymalArticulation()
print("Reference count of the robot view: ", ctypes.c_long.from_address(id(robot)).value)
print("Referrers of the robot view: ", gc.get_referrers(robot))
print("---" * 10)
# Play the simulator
sim.reset()
print("Reference count of the robot view: ", ctypes.c_long.from_address(id(robot)).value)
print("Referrers of the robot view: ", gc.get_referrers(robot))
print("---" * 10)
robot.initialize()
print("Reference count of the robot view: ", ctypes.c_long.from_address(id(robot)).value)
print("Referrers of the robot view: ", gc.get_referrers(robot))
print("---" * 10)
# Stop the simulator
sim.stop()
print("Reference count of the robot view: ", ctypes.c_long.from_address(id(robot)).value)
print("Referrers of the robot view: ", gc.get_referrers(robot))
print("---" * 10)
# Clean up
sim.clear()
print("Reference count of the robot view: ", ctypes.c_long.from_address(id(robot)).value)
print("Referrers of the robot view: ", gc.get_referrers(robot))
print("---" * 10)
if __name__ == "__main__":
# run the main function
main()
# close sim app
simulation_app.close()
......@@ -94,7 +94,7 @@ class TestCamera(unittest.TestCase):
# Play sim
self.sim.reset()
# Check if camera is initialized
self.assertTrue(camera._is_initialized)
self.assertTrue(camera.is_initialized)
# Check if camera prim is set correctly and that it is a camera prim
self.assertEqual(camera._sensor_prims[0].GetPath().pathString, self.camera_cfg.prim_path)
self.assertIsInstance(camera._sensor_prims[0], UsdGeom.Camera)
......
......@@ -106,7 +106,7 @@ class TestWarpCamera(unittest.TestCase):
# Play sim
self.sim.reset()
# Check if camera is initialized
self.assertTrue(camera._is_initialized)
self.assertTrue(camera.is_initialized)
# Simulate for a few steps
# note: This is a workaround to ensure that the textures are loaded.
# Check "Known Issues" section in the documentation for more details.
......
......@@ -84,7 +84,7 @@ class TestTiledCamera(unittest.TestCase):
# Play sim
self.sim.reset()
# Check if camera is initialized
self.assertTrue(camera._is_initialized)
self.assertTrue(camera.is_initialized)
# Check if camera prim is set correctly and that it is a camera prim
self.assertEqual(camera._sensor_prims[0].GetPath().pathString, self.camera_cfg.prim_path)
self.assertIsInstance(camera._sensor_prims[0], UsdGeom.Camera)
......@@ -133,7 +133,7 @@ class TestTiledCamera(unittest.TestCase):
# Play sim
self.sim.reset()
# Check if camera is initialized
self.assertTrue(camera._is_initialized)
self.assertTrue(camera.is_initialized)
# Check if camera prim is set correctly and that it is a camera prim
self.assertEqual(camera._sensor_prims[1].GetPath().pathString, "/World/Origin_01/CameraSensor")
self.assertIsInstance(camera._sensor_prims[0], UsdGeom.Camera)
......@@ -184,7 +184,7 @@ class TestTiledCamera(unittest.TestCase):
# Play sim
self.sim.reset()
# Check if camera is initialized
self.assertTrue(camera._is_initialized)
self.assertTrue(camera.is_initialized)
# Check if camera prim is set correctly and that it is a camera prim
self.assertEqual(camera._sensor_prims[1].GetPath().pathString, "/World/Origin_01/CameraSensor")
self.assertIsInstance(camera._sensor_prims[0], UsdGeom.Camera)
......@@ -232,7 +232,7 @@ class TestTiledCamera(unittest.TestCase):
# Play sim
self.sim.reset()
# Check if camera is initialized
self.assertTrue(camera._is_initialized)
self.assertTrue(camera.is_initialized)
# Check if camera prim is set correctly and that it is a camera prim
self.assertEqual(camera._sensor_prims[1].GetPath().pathString, "/World/Origin_01/CameraSensor")
self.assertIsInstance(camera._sensor_prims[0], UsdGeom.Camera)
......
......@@ -64,7 +64,7 @@ class TestValidEntitiesConfigs(unittest.TestCase):
sim.reset()
# check asset is initialized successfully
self.assertTrue(entity._is_initialized)
self.assertTrue(entity.is_initialized)
if __name__ == "__main__":
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment