Skip to content

Commit 563d441

Browse files
nikhilaravifacebook-github-bot
authored andcommitted
multigpu mesh rendering fixes
Summary: Small fix and updated tests for multigpu rendering case. This resolves the issue seen in: #401 Reviewed By: gkioxari Differential Revision: D24314681 fbshipit-source-id: 84c5a5359844c77518b48044001daa9a86f3c43a
1 parent 4d52f9f commit 563d441

File tree

3 files changed

+162
-66
lines changed

3 files changed

+162
-66
lines changed

pytorch3d/renderer/utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88

99
import numpy as np
1010
import torch
11+
import torch.nn as nn
1112

1213

13-
class TensorAccessor(object):
14+
class TensorAccessor(nn.Module):
1415
"""
1516
A helper class to be used with the __getitem__ method. This can be used for
1617
getting/setting the values for an attribute of a class at one particular
@@ -82,7 +83,7 @@ def __getattr__(self, name: str):
8283
BROADCAST_TYPES = (float, int, list, tuple, torch.Tensor, np.ndarray)
8384

8485

85-
class TensorProperties(object):
86+
class TensorProperties(nn.Module):
8687
"""
8788
A mix-in class for storing tensors as properties with helper methods.
8889
"""

tests/test_render_meshes.py

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,67 +1042,3 @@ def test_simple_sphere_outside_zfar(self):
10421042
)
10431043

10441044
self.assertClose(rgb, image_ref, atol=0.05)
1045-
1046-
def test_to(self):
1047-
# Test moving all the tensors in the renderer to a new device
1048-
# to support multigpu rendering.
1049-
device1 = torch.device("cpu")
1050-
1051-
R, T = look_at_view_transform(1500, 0.0, 0.0)
1052-
1053-
# Init shader settings
1054-
materials = Materials(device=device1)
1055-
lights = PointLights(device=device1)
1056-
lights.location = torch.tensor([0.0, 0.0, +1000.0], device=device1)[None]
1057-
1058-
raster_settings = RasterizationSettings(
1059-
image_size=256, blur_radius=0.0, faces_per_pixel=1
1060-
)
1061-
cameras = FoVPerspectiveCameras(
1062-
device=device1, R=R, T=T, aspect_ratio=1.0, fov=60.0, zfar=100
1063-
)
1064-
rasterizer = MeshRasterizer(cameras=cameras, raster_settings=raster_settings)
1065-
1066-
blend_params = BlendParams(
1067-
1e-4,
1068-
1e-4,
1069-
background_color=torch.zeros(3, dtype=torch.float32, device=device1),
1070-
)
1071-
1072-
shader = SoftPhongShader(
1073-
lights=lights,
1074-
cameras=cameras,
1075-
materials=materials,
1076-
blend_params=blend_params,
1077-
)
1078-
renderer = MeshRenderer(rasterizer=rasterizer, shader=shader)
1079-
1080-
def _check_props_on_device(renderer, device):
1081-
self.assertEqual(renderer.rasterizer.cameras.device, device)
1082-
self.assertEqual(renderer.shader.cameras.device, device)
1083-
self.assertEqual(renderer.shader.lights.device, device)
1084-
self.assertEqual(renderer.shader.lights.ambient_color.device, device)
1085-
self.assertEqual(renderer.shader.materials.device, device)
1086-
self.assertEqual(renderer.shader.materials.ambient_color.device, device)
1087-
1088-
mesh = ico_sphere(2, device1)
1089-
verts_padded = mesh.verts_padded()
1090-
textures = TexturesVertex(
1091-
verts_features=torch.ones_like(verts_padded, device=device1)
1092-
)
1093-
mesh.textures = textures
1094-
_check_props_on_device(renderer, device1)
1095-
1096-
# Test rendering on cpu
1097-
output_images = renderer(mesh)
1098-
self.assertEqual(output_images.device, device1)
1099-
1100-
# Move renderer and mesh to another device and re render
1101-
# This also tests that background_color is correctly moved to
1102-
# the new device
1103-
device2 = torch.device("cuda:0")
1104-
renderer.to(device2)
1105-
mesh = mesh.to(device2)
1106-
_check_props_on_device(renderer, device2)
1107-
output_images = renderer(mesh)
1108-
self.assertEqual(output_images.device, device2)

tests/test_render_multigpu.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
2+
3+
import unittest
4+
5+
import torch
6+
import torch.nn as nn
7+
from common_testing import TestCaseMixin, get_random_cuda_device
8+
from pytorch3d.renderer import (
9+
BlendParams,
10+
HardGouraudShader,
11+
Materials,
12+
MeshRasterizer,
13+
MeshRenderer,
14+
PointLights,
15+
RasterizationSettings,
16+
SoftPhongShader,
17+
TexturesVertex,
18+
)
19+
from pytorch3d.renderer.cameras import FoVPerspectiveCameras, look_at_view_transform
20+
from pytorch3d.structures.meshes import Meshes
21+
from pytorch3d.utils.ico_sphere import ico_sphere
22+
23+
24+
# Set the number of GPUS you want to test with
25+
NUM_GPUS = 3
26+
GPU_LIST = list({get_random_cuda_device() for _ in range(NUM_GPUS)})
27+
print("GPUs: %s" % ", ".join(GPU_LIST))
28+
29+
30+
class TestRenderMultiGPU(TestCaseMixin, unittest.TestCase):
31+
def _check_mesh_renderer_props_on_device(self, renderer, device):
32+
"""
33+
Helper function to check that all the properties of the mesh
34+
renderer have been moved to the correct device.
35+
"""
36+
# Cameras
37+
self.assertEqual(renderer.rasterizer.cameras.device, device)
38+
self.assertEqual(renderer.rasterizer.cameras.R.device, device)
39+
self.assertEqual(renderer.rasterizer.cameras.T.device, device)
40+
self.assertEqual(renderer.shader.cameras.device, device)
41+
self.assertEqual(renderer.shader.cameras.R.device, device)
42+
self.assertEqual(renderer.shader.cameras.T.device, device)
43+
44+
# Lights and Materials
45+
self.assertEqual(renderer.shader.lights.device, device)
46+
self.assertEqual(renderer.shader.lights.ambient_color.device, device)
47+
self.assertEqual(renderer.shader.materials.device, device)
48+
self.assertEqual(renderer.shader.materials.ambient_color.device, device)
49+
50+
def test_mesh_renderer_to(self):
51+
"""
52+
Test moving all the tensors in the mesh renderer to a new device.
53+
"""
54+
55+
device1 = torch.device("cpu")
56+
57+
R, T = look_at_view_transform(1500, 0.0, 0.0)
58+
59+
# Init shader settings
60+
materials = Materials(device=device1)
61+
lights = PointLights(device=device1)
62+
lights.location = torch.tensor([0.0, 0.0, +1000.0], device=device1)[None]
63+
64+
raster_settings = RasterizationSettings(
65+
image_size=256, blur_radius=0.0, faces_per_pixel=1
66+
)
67+
cameras = FoVPerspectiveCameras(
68+
device=device1, R=R, T=T, aspect_ratio=1.0, fov=60.0, zfar=100
69+
)
70+
rasterizer = MeshRasterizer(cameras=cameras, raster_settings=raster_settings)
71+
72+
blend_params = BlendParams(
73+
1e-4,
74+
1e-4,
75+
background_color=torch.zeros(3, dtype=torch.float32, device=device1),
76+
)
77+
78+
shader = SoftPhongShader(
79+
lights=lights,
80+
cameras=cameras,
81+
materials=materials,
82+
blend_params=blend_params,
83+
)
84+
renderer = MeshRenderer(rasterizer=rasterizer, shader=shader)
85+
86+
mesh = ico_sphere(2, device1)
87+
verts_padded = mesh.verts_padded()
88+
textures = TexturesVertex(
89+
verts_features=torch.ones_like(verts_padded, device=device1)
90+
)
91+
mesh.textures = textures
92+
self._check_mesh_renderer_props_on_device(renderer, device1)
93+
94+
# Test rendering on cpu
95+
output_images = renderer(mesh)
96+
self.assertEqual(output_images.device, device1)
97+
98+
# Move renderer and mesh to another device and re render
99+
# This also tests that background_color is correctly moved to
100+
# the new device
101+
device2 = torch.device("cuda:0")
102+
renderer.to(device2)
103+
mesh = mesh.to(device2)
104+
self._check_mesh_renderer_props_on_device(renderer, device2)
105+
output_images = renderer(mesh)
106+
self.assertEqual(output_images.device, device2)
107+
108+
def test_render_meshes(self):
109+
test = self
110+
111+
class Model(nn.Module):
112+
def __init__(self):
113+
super(Model, self).__init__()
114+
mesh = ico_sphere(3)
115+
self.register_buffer("faces", mesh.faces_padded())
116+
self.renderer = self.init_render()
117+
118+
def init_render(self):
119+
120+
cameras = FoVPerspectiveCameras()
121+
raster_settings = RasterizationSettings(
122+
image_size=128, blur_radius=0.0, faces_per_pixel=1
123+
)
124+
lights = PointLights(
125+
ambient_color=((1.0, 1.0, 1.0),),
126+
diffuse_color=((0, 0.0, 0),),
127+
specular_color=((0.0, 0, 0),),
128+
location=((0.0, 0.0, 1e5),),
129+
)
130+
renderer = MeshRenderer(
131+
rasterizer=MeshRasterizer(
132+
cameras=cameras, raster_settings=raster_settings
133+
),
134+
shader=HardGouraudShader(cameras=cameras, lights=lights),
135+
)
136+
return renderer
137+
138+
def forward(self, verts, texs):
139+
batch_size = verts.size(0)
140+
self.renderer.to(verts.device)
141+
tex = TexturesVertex(verts_features=texs)
142+
faces = self.faces.expand(batch_size, -1, -1).to(verts.device)
143+
mesh = Meshes(verts, faces, tex).to(verts.device)
144+
145+
test._check_mesh_renderer_props_on_device(self.renderer, verts.device)
146+
img_render = self.renderer(mesh)
147+
return img_render[:, :, :, :3]
148+
149+
# DataParallel requires every input tensor be provided
150+
# on the first device in its device_ids list.
151+
verts = ico_sphere(3).verts_padded()
152+
texs = verts.new_ones(verts.shape)
153+
model = Model()
154+
model = nn.DataParallel(model, device_ids=GPU_LIST)
155+
model.to(f"cuda:{model.device_ids[0]}")
156+
157+
# Test a few iterations
158+
for _ in range(100):
159+
model(verts, texs)

0 commit comments

Comments
 (0)