Skip to content

Commit 16b8f4c

Browse files
committed
Expand ONNX export test coverage to 12 network architectures
The ONNX export tests only covered UNet (2D) and SegResNet (3D), leaving regressions in exportability for widely used architectures undetected. Add parameterized ONNX export tests for 10 additional networks: DynUNet, AttentionUnet, BasicUNet, BasicUNetPlusPlus, VNet, HighResNet, DenseNet, ResNet (resnet10), SEResNet50, UNETR, and FullyConnectedNet. All tests use small model configurations and tiny inputs to keep CI fast. Fixes #9072 Signed-off-by: chhayankjain <chhayank44@gmail.com>
1 parent 605611b commit 16b8f4c

1 file changed

Lines changed: 348 additions & 1 deletion

File tree

tests/networks/test_convert_to_onnx.py

Lines changed: 348 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,21 @@
1919
from parameterized import parameterized
2020

2121
from monai.networks import convert_to_onnx
22-
from monai.networks.nets import SegResNet, UNet
22+
from monai.networks.nets import (
23+
UNETR,
24+
AttentionUnet,
25+
BasicUNet,
26+
BasicUNetPlusPlus,
27+
DenseNet,
28+
DynUNet,
29+
FullyConnectedNet,
30+
HighResNet,
31+
SegResNet,
32+
SEResNet50,
33+
UNet,
34+
VNet,
35+
resnet10,
36+
)
2337
from tests.test_utils import SkipIfNoModule, optional_import, skip_if_quick
2438

2539
onnx, _ = optional_import("onnx")
@@ -32,6 +46,7 @@
3246

3347
TESTS = list(itertools.product(TORCH_DEVICE_OPTIONS, [True, False], [True, False]))
3448
TESTS_ORT = list(itertools.product(TORCH_DEVICE_OPTIONS, [True]))
49+
TESTS_TRACE = list(itertools.product(TORCH_DEVICE_OPTIONS, [True, False]))
3550

3651
ON_AARCH64 = platform.machine() == "aarch64"
3752
if ON_AARCH64:
@@ -40,6 +55,21 @@
4055
rtol, atol = 1e-2, 1e-2
4156

4257

58+
def _check_ort_available(test_case):
59+
"""Skip the test if onnxruntime is not installed.
60+
61+
Args:
62+
test_case: the ``unittest.TestCase`` instance to call ``skipTest`` on
63+
when onnxruntime is unavailable.
64+
65+
Raises:
66+
unittest.SkipTest: when onnxruntime cannot be imported.
67+
"""
68+
_, has_onnxruntime = optional_import("onnxruntime")
69+
if not has_onnxruntime:
70+
test_case.skipTest("onnxruntime is not installed probably due to python version >= 3.11.")
71+
72+
4373
@SkipIfNoModule("onnx")
4474
@skip_if_quick
4575
class TestConvertToOnnx(unittest.TestCase):
@@ -103,6 +133,323 @@ def test_seg_res_net(self, device, use_ort):
103133
)
104134
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
105135

136+
@parameterized.expand(TESTS_TRACE)
137+
def test_dynunet(self, device, use_ort):
138+
"""Test converting DynUNet to ONNX with and without ORT verification.
139+
140+
Args:
141+
device: torch device string (e.g. ``"cpu"``).
142+
use_ort: if ``True``, verify via onnxruntime; if ``False``, verify
143+
via ``onnx.reference.ReferenceEvaluator``. Skipped when
144+
onnxruntime is unavailable.
145+
"""
146+
if use_ort:
147+
_check_ort_available(self)
148+
model = DynUNet(
149+
spatial_dims=3,
150+
in_channels=1,
151+
out_channels=2,
152+
kernel_size=[3, 3, 3],
153+
strides=[1, 2, 2],
154+
upsample_kernel_size=[2, 2],
155+
)
156+
onnx_model = convert_to_onnx(
157+
model=model,
158+
inputs=[torch.randn((1, 1, 32, 32, 32), requires_grad=False)],
159+
input_names=["x"],
160+
output_names=["y"],
161+
verify=True,
162+
device=device,
163+
use_ort=use_ort,
164+
use_trace=True,
165+
rtol=rtol,
166+
atol=atol,
167+
)
168+
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
169+
170+
@parameterized.expand(TESTS_TRACE)
171+
def test_attention_unet(self, device, use_ort):
172+
"""Test converting AttentionUnet to ONNX with and without ORT verification.
173+
174+
Args:
175+
device: torch device string (e.g. ``"cpu"``).
176+
use_ort: if ``True``, verify via onnxruntime; if ``False``, verify
177+
via ``onnx.reference.ReferenceEvaluator``. Skipped when
178+
onnxruntime is unavailable.
179+
"""
180+
if use_ort:
181+
_check_ort_available(self)
182+
model = AttentionUnet(spatial_dims=3, in_channels=1, out_channels=2, channels=(16, 32, 64), strides=(2, 2))
183+
onnx_model = convert_to_onnx(
184+
model=model,
185+
inputs=[torch.randn((1, 1, 32, 32, 32), requires_grad=False)],
186+
input_names=["x"],
187+
output_names=["y"],
188+
verify=True,
189+
device=device,
190+
use_ort=use_ort,
191+
use_trace=True,
192+
rtol=rtol,
193+
atol=atol,
194+
)
195+
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
196+
197+
@parameterized.expand(TESTS_TRACE)
198+
def test_basic_unet(self, device, use_ort):
199+
"""Test converting BasicUNet to ONNX with and without ORT verification.
200+
201+
Args:
202+
device: torch device string (e.g. ``"cpu"``).
203+
use_ort: if ``True``, verify via onnxruntime; if ``False``, verify
204+
via ``onnx.reference.ReferenceEvaluator``. Skipped when
205+
onnxruntime is unavailable.
206+
"""
207+
if use_ort:
208+
_check_ort_available(self)
209+
model = BasicUNet(spatial_dims=3, in_channels=1, out_channels=2, features=(8, 8, 16, 32, 64, 8))
210+
onnx_model = convert_to_onnx(
211+
model=model,
212+
inputs=[torch.randn((1, 1, 32, 32, 32), requires_grad=False)],
213+
input_names=["x"],
214+
output_names=["y"],
215+
verify=True,
216+
device=device,
217+
use_ort=use_ort,
218+
use_trace=True,
219+
rtol=rtol,
220+
atol=atol,
221+
)
222+
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
223+
224+
@parameterized.expand(TESTS_TRACE)
225+
def test_basic_unet_plus_plus(self, device, use_ort):
226+
"""Test converting BasicUNetPlusPlus to ONNX with and without ORT verification.
227+
228+
Args:
229+
device: torch device string (e.g. ``"cpu"``).
230+
use_ort: if ``True``, verify via onnxruntime; if ``False``, verify
231+
via ``onnx.reference.ReferenceEvaluator``. Skipped when
232+
onnxruntime is unavailable.
233+
"""
234+
if use_ort:
235+
_check_ort_available(self)
236+
model = BasicUNetPlusPlus(
237+
spatial_dims=3, in_channels=1, out_channels=2, features=(8, 8, 16, 32, 64, 8), deep_supervision=False
238+
)
239+
onnx_model = convert_to_onnx(
240+
model=model,
241+
inputs=[torch.randn((1, 1, 32, 32, 32), requires_grad=False)],
242+
input_names=["x"],
243+
output_names=["y"],
244+
verify=True,
245+
device=device,
246+
use_ort=use_ort,
247+
use_trace=True,
248+
rtol=rtol,
249+
atol=atol,
250+
)
251+
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
252+
253+
@parameterized.expand(TESTS_TRACE)
254+
def test_vnet(self, device, use_ort):
255+
"""Test converting VNet to ONNX with and without ORT verification.
256+
257+
Args:
258+
device: torch device string (e.g. ``"cpu"``).
259+
use_ort: if ``True``, verify via onnxruntime; if ``False``, verify
260+
via ``onnx.reference.ReferenceEvaluator``. Skipped when
261+
onnxruntime is unavailable.
262+
"""
263+
if use_ort:
264+
_check_ort_available(self)
265+
model = VNet(spatial_dims=3, in_channels=1, out_channels=1)
266+
onnx_model = convert_to_onnx(
267+
model=model,
268+
inputs=[torch.randn((1, 1, 32, 32, 32), requires_grad=False)],
269+
input_names=["x"],
270+
output_names=["y"],
271+
verify=True,
272+
device=device,
273+
use_ort=use_ort,
274+
use_trace=True,
275+
rtol=rtol,
276+
atol=atol,
277+
)
278+
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
279+
280+
@parameterized.expand(TESTS_TRACE)
281+
def test_highresnet(self, device, use_ort):
282+
"""Test converting HighResNet to ONNX with and without ORT verification.
283+
284+
Args:
285+
device: torch device string (e.g. ``"cpu"``).
286+
use_ort: if ``True``, verify via onnxruntime; if ``False``, verify
287+
via ``onnx.reference.ReferenceEvaluator``. Skipped when
288+
onnxruntime is unavailable.
289+
"""
290+
if use_ort:
291+
_check_ort_available(self)
292+
model = HighResNet(spatial_dims=3, in_channels=1, out_channels=2)
293+
onnx_model = convert_to_onnx(
294+
model=model,
295+
inputs=[torch.randn((1, 1, 16, 16, 16), requires_grad=False)],
296+
input_names=["x"],
297+
output_names=["y"],
298+
verify=True,
299+
device=device,
300+
use_ort=use_ort,
301+
use_trace=True,
302+
rtol=rtol,
303+
atol=atol,
304+
)
305+
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
306+
307+
@parameterized.expand(TESTS_TRACE)
308+
def test_densenet(self, device, use_ort):
309+
"""Test converting DenseNet to ONNX with and without ORT verification.
310+
311+
Args:
312+
device: torch device string (e.g. ``"cpu"``).
313+
use_ort: if ``True``, verify via onnxruntime; if ``False``, verify
314+
via ``onnx.reference.ReferenceEvaluator``. Skipped when
315+
onnxruntime is unavailable.
316+
"""
317+
if use_ort:
318+
_check_ort_available(self)
319+
model = DenseNet(
320+
spatial_dims=3, in_channels=1, out_channels=2, init_features=16, growth_rate=8, block_config=(2, 2, 2, 2)
321+
)
322+
onnx_model = convert_to_onnx(
323+
model=model,
324+
inputs=[torch.randn((1, 1, 32, 32, 32), requires_grad=False)],
325+
input_names=["x"],
326+
output_names=["y"],
327+
verify=True,
328+
device=device,
329+
use_ort=use_ort,
330+
use_trace=True,
331+
rtol=rtol,
332+
atol=atol,
333+
)
334+
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
335+
336+
@parameterized.expand(TESTS_TRACE)
337+
def test_resnet(self, device, use_ort):
338+
"""Test converting ResNet to ONNX with and without ORT verification.
339+
340+
Args:
341+
device: torch device string (e.g. ``"cpu"``).
342+
use_ort: if ``True``, verify via onnxruntime; if ``False``, verify
343+
via ``onnx.reference.ReferenceEvaluator``. Skipped when
344+
onnxruntime is unavailable.
345+
"""
346+
if use_ort:
347+
_check_ort_available(self)
348+
model = resnet10(pretrained=False, spatial_dims=3, n_input_channels=1, num_classes=2)
349+
onnx_model = convert_to_onnx(
350+
model=model,
351+
inputs=[torch.randn((1, 1, 32, 32, 32), requires_grad=False)],
352+
input_names=["x"],
353+
output_names=["y"],
354+
verify=True,
355+
device=device,
356+
use_ort=use_ort,
357+
use_trace=True,
358+
rtol=rtol,
359+
atol=atol,
360+
)
361+
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
362+
363+
@parameterized.expand(TESTS_TRACE)
364+
def test_seresnet(self, device, use_ort):
365+
"""Test converting SEResNet50 to ONNX with and without ORT verification.
366+
367+
Args:
368+
device: torch device string (e.g. ``"cpu"``).
369+
use_ort: if ``True``, verify via onnxruntime; if ``False``, verify
370+
via ``onnx.reference.ReferenceEvaluator``. Skipped when
371+
onnxruntime is unavailable.
372+
"""
373+
if use_ort:
374+
_check_ort_available(self)
375+
model = SEResNet50(layers=(1, 1, 1, 1), spatial_dims=3, in_channels=1, num_classes=2)
376+
onnx_model = convert_to_onnx(
377+
model=model,
378+
inputs=[torch.randn((1, 1, 32, 32, 32), requires_grad=False)],
379+
input_names=["x"],
380+
output_names=["y"],
381+
verify=True,
382+
device=device,
383+
use_ort=use_ort,
384+
use_trace=True,
385+
rtol=rtol,
386+
atol=atol,
387+
)
388+
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
389+
390+
@parameterized.expand(TESTS_TRACE)
391+
def test_unetr(self, device, use_ort):
392+
"""Test converting UNETR to ONNX with and without ORT verification.
393+
394+
Args:
395+
device: torch device string (e.g. ``"cpu"``).
396+
use_ort: if ``True``, verify via onnxruntime; if ``False``, verify
397+
via ``onnx.reference.ReferenceEvaluator``. Skipped when
398+
onnxruntime is unavailable.
399+
"""
400+
if use_ort:
401+
_check_ort_available(self)
402+
model = UNETR(
403+
in_channels=1,
404+
out_channels=2,
405+
img_size=(32, 32, 32),
406+
feature_size=8,
407+
hidden_size=128,
408+
mlp_dim=256,
409+
num_heads=8,
410+
spatial_dims=3,
411+
)
412+
onnx_model = convert_to_onnx(
413+
model=model,
414+
inputs=[torch.randn((1, 1, 32, 32, 32), requires_grad=False)],
415+
input_names=["x"],
416+
output_names=["y"],
417+
verify=True,
418+
device=device,
419+
use_ort=use_ort,
420+
use_trace=True,
421+
rtol=rtol,
422+
atol=atol,
423+
)
424+
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
425+
426+
@parameterized.expand(TESTS_TRACE)
427+
def test_fully_connected_net(self, device, use_ort):
428+
"""Test converting FullyConnectedNet to ONNX with and without ORT verification.
429+
430+
Args:
431+
device: torch device string (e.g. ``"cpu"``).
432+
use_ort: if ``True``, verify via onnxruntime; if ``False``, verify
433+
via ``onnx.reference.ReferenceEvaluator``. Skipped when
434+
onnxruntime is unavailable.
435+
"""
436+
if use_ort:
437+
_check_ort_available(self)
438+
model = FullyConnectedNet(in_channels=10, out_channels=2, hidden_channels=[20, 10])
439+
onnx_model = convert_to_onnx(
440+
model=model,
441+
inputs=[torch.randn((4, 10), requires_grad=False)],
442+
input_names=["x"],
443+
output_names=["y"],
444+
verify=True,
445+
device=device,
446+
use_ort=use_ort,
447+
use_trace=True,
448+
rtol=rtol,
449+
atol=atol,
450+
)
451+
self.assertTrue(isinstance(onnx_model, onnx.ModelProto))
452+
106453

107454
if __name__ == "__main__":
108455
unittest.main()

0 commit comments

Comments
 (0)