Skip to content

Commit 60fd130

Browse files
authored
[MISC] Fix unit tests failing on GPU. (Genesis-Embodied-AI#1580)
1 parent 2dc294d commit 60fd130

File tree

8 files changed

+52
-37
lines changed

8 files changed

+52
-37
lines changed

.github/workflows/production.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
bash -c "
6868
pip install --extra-index-url https://pypi.nvidia.com/ omniverse-kit && \
6969
pip install -e '.[dev,render,usd]' && \
70-
pytest -v --dev --forked ./tests
70+
pytest -v --backend gpu --dev --forked ./tests
7171
"
7272
7373
- name: Run benchmarks

genesis/sensors/imu.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ class IMUSharedMetadata(SharedSensorMetadata):
7070

7171
solver: RigidSolver | None = None
7272
links_idx: list[int] = field(default_factory=list)
73-
offsets_pos: torch.Tensor = torch.tensor([])
74-
offsets_quat: torch.Tensor = torch.tensor([])
75-
acc_bias: torch.Tensor = torch.tensor([])
76-
ang_bias: torch.Tensor = torch.tensor([])
73+
offsets_pos: torch.Tensor = field(default_factory=lambda: torch.tensor([], dtype=gs.tc_float, device=gs.device))
74+
offsets_quat: torch.Tensor = field(default_factory=lambda: torch.tensor([], dtype=gs.tc_float, device=gs.device))
75+
acc_bias: torch.Tensor = field(default_factory=lambda: torch.tensor([], dtype=gs.tc_float, device=gs.device))
76+
ang_bias: torch.Tensor = field(default_factory=lambda: torch.tensor([], dtype=gs.tc_float, device=gs.device))
7777

7878

7979
@register_sensor(IMUOptions, IMUSharedMetadata)
@@ -89,19 +89,28 @@ def build(self):
8989

9090
self._shared_metadata.links_idx.append(self._options.entity_idx + self._options.link_idx_local)
9191
self._shared_metadata.offsets_pos = torch.cat(
92-
[self._shared_metadata.offsets_pos, torch.tensor([self._options.pos_offset], dtype=gs.tc_float)]
92+
[
93+
self._shared_metadata.offsets_pos,
94+
torch.tensor([self._options.pos_offset], dtype=gs.tc_float, device=gs.device),
95+
]
9396
)
9497

95-
quat_tensor = torch.tensor(euler_to_quat(self._options.euler_offset), dtype=gs.tc_float).unsqueeze(0)
98+
quat_tensor = torch.tensor(euler_to_quat([self._options.euler_offset]), dtype=gs.tc_float, device=gs.device)
9699
if self._shared_metadata.solver.n_envs > 0:
97100
quat_tensor = quat_tensor.unsqueeze(0).expand((self._manager._sim._B, 1, 4))
98101
self._shared_metadata.offsets_quat = torch.cat([self._shared_metadata.offsets_quat, quat_tensor], dim=-2)
99102

100103
self._shared_metadata.acc_bias = torch.cat(
101-
[self._shared_metadata.acc_bias, torch.tensor([self._options.accelerometer_bias], dtype=gs.tc_float)]
104+
[
105+
self._shared_metadata.acc_bias,
106+
torch.tensor([self._options.accelerometer_bias], dtype=gs.tc_float, device=gs.device),
107+
]
102108
)
103109
self._shared_metadata.ang_bias = torch.cat(
104-
[self._shared_metadata.ang_bias, torch.tensor([self._options.gyroscope_bias], dtype=gs.tc_float)]
110+
[
111+
self._shared_metadata.ang_bias,
112+
torch.tensor([self._options.gyroscope_bias], dtype=gs.tc_float, device=gs.device),
113+
]
105114
)
106115

107116
def _get_return_format(self) -> dict[str, tuple[int, ...]]:

genesis/utils/geom.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ def _np_xyz_to_quat(xyz: np.ndarray, rpy: bool = False, out: np.ndarray | None =
493493
"""
494494
assert xyz.ndim >= 1
495495
if out is None:
496-
out_ = np.empty((*xyz.shape[:-1], 4))
496+
out_ = np.empty((*xyz.shape[:-1], 4), dtype=xyz.dtype)
497497
else:
498498
assert out.shape == (*xyz.shape[:-1], 4)
499499
out_ = out
@@ -532,12 +532,13 @@ def _tc_xyz_to_quat(xyz: torch.Tensor, rpy: bool = False, *, out: torch.Tensor |
532532

533533

534534
def xyz_to_quat(xyz, rpy=False, degrees=False):
535-
if degrees:
536-
xyz = xyz * (math.pi / 180.0)
537-
538535
if isinstance(xyz, torch.Tensor):
536+
if degrees:
537+
xyz = torch.deg2rad(xyz)
539538
return _tc_xyz_to_quat(xyz, rpy)
540539
elif isinstance(xyz, np.ndarray):
540+
if degrees:
541+
xyz = np.deg2rad(xyz)
541542
return _np_xyz_to_quat(xyz, rpy)
542543
else:
543544
gs.raise_exception(f"the input must be either torch.Tensor or np.ndarray. got: {type(xyz)=}")
@@ -703,12 +704,14 @@ def _tc_quat_to_xyz(quat, rpy=False, out=None):
703704
def quat_to_xyz(quat, rpy=False, degrees=False):
704705
if isinstance(quat, torch.Tensor):
705706
rpy = _tc_quat_to_xyz(quat, rpy)
707+
if degrees:
708+
rpy = torch.rad2deg(rpy)
706709
elif isinstance(quat, np.ndarray):
707710
rpy = _np_quat_to_xyz(quat, rpy)
711+
if degrees:
712+
rpy = np.rad2deg(rpy)
708713
else:
709714
gs.raise_exception(f"the input must be either torch.Tensor or np.ndarray. got: {type(quat)=}")
710-
if degrees:
711-
rpy *= 180.0 / math.pi
712715
return rpy
713716

714717

@@ -1233,13 +1236,14 @@ def _tc_z_up_to_R(z, up=None, out=None):
12331236

12341237
# Handle zero x norm cases
12351238
zero_x_mask = x_norm[..., 0] < gs.EPS
1236-
if zero_x_mask.any():
1239+
zero_x_num = zero_x_mask.sum()
1240+
if zero_x_num:
12371241
# For zero x norm, set identity matrix
1238-
R[zero_x_mask] = torch.eye(3, device=z.device, dtype=z.dtype)
1242+
R[zero_x_mask] = torch.eye(3, device=z.device, dtype=z.dtype).unsqueeze(0).expand((zero_x_num, 3, 3))
12391243

12401244
# Continue with non-zero cases
12411245
valid_mask = ~zero_x_mask
1242-
if valid_mask.any():
1246+
if zero_x_num < zero_x_mask.numel():
12431247
z_valid = z[valid_mask]
12441248
x_valid = x[valid_mask]
12451249
y[valid_mask] = torch.cross(z_valid, x_valid, dim=-1)
@@ -1324,7 +1328,7 @@ def _np_euler_to_R(rpy: np.ndarray, out: np.ndarray | None = None) -> np.ndarray
13241328

13251329

13261330
def euler_to_R(euler_xyz):
1327-
return _np_euler_to_R(np.asarray(euler_xyz) * (math.pi / 180.0))
1331+
return _np_euler_to_R(np.deg2rad(euler_xyz))
13281332

13291333

13301334
@nb.jit(nopython=True, cache=True)

genesis/utils/misc.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import os
1111
from dataclasses import dataclass
1212
from collections import OrderedDict
13-
from typing import Any, Type, NoReturn
13+
from typing import Any, Type, NoReturn, Optional
1414

1515
import numpy as np
1616
import cpuinfo
@@ -175,12 +175,13 @@ def get_platform():
175175
assert False, f"Unknown platform name {name}"
176176

177177

178-
def get_device(backend: gs_backend):
178+
def get_device(backend: gs_backend, device_idx: Optional[int] = None):
179179
if backend == gs_backend.cuda:
180180
if not torch.cuda.is_available():
181181
gs.raise_exception("torch cuda not available")
182182

183-
device_idx = torch.cuda.current_device()
183+
if device_idx is None:
184+
device_idx = torch.cuda.current_device()
184185
device = torch.device("cuda", device_idx)
185186
device_property = torch.cuda.get_device_properties(device)
186187
device_name = device_property.name
@@ -192,13 +193,14 @@ def get_device(backend: gs_backend):
192193

193194
# on mac, cpu and gpu are in the same device
194195
_, device_name, total_mem, _ = get_device(gs_backend.cpu)
195-
device = torch.device("mps", 0)
196+
device = torch.device("mps", device_idx)
196197

197198
elif backend == gs_backend.vulkan:
198199
if torch.cuda.is_available():
199200
device, device_name, total_mem, _ = get_device(gs_backend.cuda)
200201
elif torch.xpu.is_available(): # pytorch 2.5+ supports Intel XPU device
201-
device_idx = torch.xpu.current_device()
202+
if device_idx is None:
203+
device_idx = torch.xpu.current_device()
202204
device = torch.device("xpu", device_idx)
203205
device_property = torch.xpu.get_device_properties(device_idx)
204206
device_name = device_property.name
@@ -220,7 +222,7 @@ def get_device(backend: gs_backend):
220222
else:
221223
device_name = cpuinfo.get_cpu_info()["brand_raw"]
222224
total_mem = psutil.virtual_memory().total / 1024**3
223-
device = torch.device("cpu")
225+
device = torch.device("cpu", device_idx)
224226

225227
return device, device_name, total_mem, backend
226228

tests/conftest.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ def pytest_xdist_auto_num_workers(config):
9595
physical_core_count = psutil.cpu_count(logical=config.option.logical)
9696
_, _, ram_memory, _ = gs.utils.get_device(gs.cpu)
9797
_, _, vram_memory, backend = gs.utils.get_device(gs.gpu)
98+
num_gpus = len(_get_gpu_indices())
99+
vram_memory *= num_gpus
98100
if backend == gs.cpu:
99101
# Ignore VRAM if no GPU is available
100102
vram_memory = float("inf")
@@ -124,7 +126,7 @@ def pytest_xdist_auto_num_workers(config):
124126
num_cpu_per_gpu = 4
125127
num_workers = min(
126128
num_workers,
127-
len(_get_gpu_indices()),
129+
num_gpus,
128130
max(int(physical_core_count / num_cpu_per_gpu), 1),
129131
)
130132

tests/test_fem.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ def test_fem_articulated(fem_material_linear_corotated_soft, show_viewer):
589589
assert_allclose(
590590
min_pos_z,
591591
-1.0e-3, # FIXME: Compute desired penetration analytically
592-
atol=1e-4,
592+
atol=2e-4,
593593
err_msg=f"Sphere minimum Z position {min_pos_z} is not close to -1.0e-3.",
594594
)
595595
assert_allclose(
@@ -667,9 +667,7 @@ def test_implicit_hard_vertex_constraint(fem_material_linear_corotated, show_vie
667667
tol=gs.EPS,
668668
err_msg="Vertices should stay at initial target positions with hard constraints",
669669
)
670-
new_target_poss = initial_target_poss + gs.tensor(
671-
[[0.1, 0.1, 0.1]],
672-
)
670+
new_target_poss = initial_target_poss + 0.1
673671
cube.update_constraint_targets(verts_idx=verts_idx, target_poss=new_target_poss)
674672
if show_viewer:
675673
scene.clear_debug_object(sphere)
@@ -689,7 +687,7 @@ def test_implicit_hard_vertex_constraint(fem_material_linear_corotated, show_vie
689687
if show_viewer:
690688
scene.clear_debug_object(sphere)
691689

692-
for _ in range(100):
690+
for _ in range(70):
693691
scene.step()
694692

695693
state = cube.get_state()
@@ -703,7 +701,7 @@ def test_implicit_hard_vertex_constraint(fem_material_linear_corotated, show_vie
703701

704702
velocity = state.vel.mean(axis=(0, 1))
705703
assert_allclose(
706-
velocity, 0.0, atol=1e-5, err_msg=f"Cube velocity {velocity} should be close to zero after settling."
704+
velocity, 0.0, atol=4e-5, err_msg=f"Cube velocity {velocity} should be close to zero after settling."
707705
)
708706

709707
# The contact requires some penetration to generate enough contact force to cancel out gravity

tests/test_rigid_physics.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2269,7 +2269,7 @@ def test_gravity(show_viewer, tol):
22692269

22702270

22712271
@pytest.mark.required
2272-
@pytest.mark.parametrize("backend", [gs.cpu])
2272+
@pytest.mark.parametrize("backend", [gs.cpu, gs.gpu])
22732273
def test_scene_saver_franka(show_viewer, tol):
22742274
scene1 = gs.Scene(
22752275
show_viewer=show_viewer,
@@ -2290,7 +2290,7 @@ def test_scene_saver_franka(show_viewer, tol):
22902290
target_pose = np.array([0.3, -0.8, 0.4, -1.6, 0.5, 1.0, -0.6, 0.03, 0.03], dtype=float)
22912291
franka1.control_dofs_position(target_pose, dof_idx)
22922292

2293-
for _ in range(400):
2293+
for _ in range(100):
22942294
scene1.step()
22952295

22962296
pose_ref = franka1.get_dofs_position(dof_idx)
@@ -2307,7 +2307,7 @@ def test_scene_saver_franka(show_viewer, tol):
23072307

23082308
pose_loaded = franka2.get_dofs_position(dof_idx)
23092309

2310-
assert_allclose(pose_ref, pose_loaded, tol=tol)
2310+
assert_allclose(pose_ref, pose_loaded, tol=2e-7)
23112311

23122312

23132313
@pytest.mark.required

tests/test_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def test_utils_geom_taichi_vs_tensor_consistency(batch_shape):
166166

167167
@pytest.mark.required
168168
@pytest.mark.parametrize("batch_shape", [(10, 40, 25), ()])
169-
def test_utils_geom_numpy_vs_tensor_consistency(batch_shape):
169+
def test_utils_geom_numpy_vs_tensor_consistency(batch_shape, tol):
170170
for py_func, shapes_in, shapes_out in (
171171
(gu.z_up_to_R, [[3], [3], [3, 3]], [[3, 3]]),
172172
(gu.pos_lookat_up_to_T, [[3], [3], [3]], [[4, 4]]),
@@ -194,7 +194,7 @@ def test_utils_geom_numpy_vs_tensor_consistency(batch_shape):
194194
tc_outs = tuple(map(tensor_to_array, tc_outs))
195195

196196
for np_out, tc_out in zip(np_outs, tc_outs):
197-
np.testing.assert_allclose(np_out, tc_out, atol=gs.EPS)
197+
assert_allclose(np_out, tc_out, tol=tol)
198198

199199

200200
@pytest.mark.required

0 commit comments

Comments
 (0)