Skip to content

Commit 296f11b

Browse files
fix padding for onnx, convert shape values to int64 for onnx, fix stft for onnx, convert boolean fill_value to int for onnx
1 parent 1e98224 commit 296f11b

1 file changed

Lines changed: 89 additions & 34 deletions

File tree

returnn/torch/frontend/_backend.py

Lines changed: 89 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@
2929
_TT = Tensor[torch.Tensor]
3030

3131

32+
def _shape_dim_values(dims: Sequence[Dim]) -> List[Union[int, torch.Tensor]]:
33+
"""Return dim values suitable as a PyTorch shape."""
34+
return _shape_values([d.get_dim_value() for d in dims])
35+
36+
37+
def _shape_values(shape: List[Union[int, torch.Tensor]]) -> List[Union[int, torch.Tensor]]:
38+
"""Return shape values with ONNX-compatible tensor dtypes."""
39+
if torch.onnx.is_in_onnx_export():
40+
# ONNX shape tensors must be int64. Dynamic dim sizes in RETURNN are often int32.
41+
shape = [dim.long() if isinstance(dim, torch.Tensor) else dim for dim in shape]
42+
return shape
43+
44+
3245
# Ignore this warning until we really expect that we implemented everything.
3346
# noinspection PyAbstractClass
3447
class TorchBackend(Backend[torch.Tensor]):
@@ -295,7 +308,7 @@ def merge_dims(
295308
dtype=source.dtype,
296309
sparse_dim=source.sparse_dim,
297310
)
298-
out_shape = [d.get_dim_value() for d in out.dims]
311+
out_shape = _shape_dim_values(out.dims)
299312
out.raw_tensor = torch.reshape(source.raw_tensor, out_shape)
300313
if source.feature_dim and source.feature_dim in dims:
301314
out.feature_dim = out_dim
@@ -314,7 +327,15 @@ def split_dims(
314327
assert pad_to_multiples in (None, False) # not implemented
315328
axis_ = source.get_axis_from_description(axis)
316329
out_dims = source.dims[:axis_] + tuple(dims) + source.dims[axis_ + 1 :]
317-
out_shape = [d.get_dim_value() for d in out_dims]
330+
if torch.onnx.is_in_onnx_export():
331+
out_shape = (
332+
list(source.raw_tensor.shape[:axis_])
333+
+ [d.get_dim_value() for d in dims]
334+
+ list(source.raw_tensor.shape[axis_ + 1 :])
335+
)
336+
out_shape = _shape_values(out_shape)
337+
else:
338+
out_shape = _shape_dim_values(out_dims)
318339
out_raw = torch.reshape(source.raw_tensor, out_shape)
319340
return Tensor(
320341
"split_dims",
@@ -341,7 +362,20 @@ def reshape(source: Tensor, in_dims: Sequence[Dim], out_dims: Sequence[Dim]) ->
341362
out = Tensor("reshape", dims=dims, dtype=source.dtype, sparse_dim=source.sparse_dim)
342363
if source.feature_dim and source.feature_dim not in in_dims:
343364
out.feature_dim = source.feature_dim
344-
out.raw_tensor = torch.reshape(source.placeholder, [d.get_dim_value() for d in dims])
365+
if torch.onnx.is_in_onnx_export():
366+
out_dim_values = [
367+
source.raw_tensor.shape[source.dims.index(d)] if d in source.dims else d.get_dim_value()
368+
for d in out_dims
369+
]
370+
out_shape = (
371+
list(source.raw_tensor.shape[:insert_axis])
372+
+ out_dim_values
373+
+ list(source.raw_tensor.shape[insert_axis + len(in_dims) :])
374+
)
375+
out_shape = _shape_values(out_shape)
376+
else:
377+
out_shape = _shape_dim_values(dims)
378+
out.raw_tensor = torch.reshape(source.placeholder, out_shape)
345379
return out
346380

347381
@staticmethod
@@ -874,7 +908,7 @@ def create_parameter_raw(tensor: rf.Parameter, *, device: Optional[str] = None)
874908
:return: parameter
875909
"""
876910
data = torch.zeros(
877-
[d.get_dim_value() for d in tensor.dims],
911+
_shape_dim_values(tensor.dims),
878912
dtype=TorchBackend.as_dtype_raw(tensor.dtype),
879913
device=device or rf.get_default_device(),
880914
)
@@ -1048,6 +1082,8 @@ def convert_to_tensor(
10481082
if isinstance(value, (bool, int, float, bool, complex, numpy.number)):
10491083
# torch.full avoids a device sync.
10501084
# https://github.com/pytorch/pytorch/issues/120996#issuecomment-2319976284
1085+
if torch.onnx.is_in_onnx_export() and dtype == "bool" and isinstance(value, (bool, numpy.bool_)):
1086+
value = int(value)
10511087
value = torch.full(
10521088
(), value, dtype=TorchBackend.as_dtype_raw(dtype), device=device or rf.get_default_device()
10531089
)
@@ -1078,6 +1114,8 @@ def full(
10781114
# onnx::ConstantOfShape (via torch.full) must get shape as int64.
10791115
# https://github.com/rwth-i6/returnn/issues/1333#issuecomment-1607236783
10801116
shape = [dim.long() if isinstance(dim, torch.Tensor) else dim for dim in shape]
1117+
if dtype == "bool" and isinstance(fill_value, (bool, numpy.bool_)):
1118+
fill_value = int(fill_value)
10811119
raw_tensor = torch.full(
10821120
shape, fill_value, dtype=TorchBackend.as_dtype_raw(dtype), device=device or rf.get_default_device()
10831121
)
@@ -1173,7 +1211,7 @@ def gather(source: Tensor, *, indices: Union[Tensor, int], axis: Dim, clip_to_va
11731211
elif len(index_own_dims) == 0:
11741212
out_raw = out_raw.squeeze(axis_int)
11751213
else:
1176-
out_raw = out_raw.reshape([d.get_dim_value() for d in out.dims])
1214+
out_raw = out_raw.reshape(_shape_dim_values(out.dims))
11771215
out.raw_tensor = out_raw
11781216
elif axis_int == 0 and indices.batch_ndim == 0:
11791217
out.raw_tensor = source.raw_tensor[indices.raw_tensor]
@@ -1250,7 +1288,7 @@ def scatter(
12501288
check_dtype=False,
12511289
)
12521290
out_dims = batch_dims + [out_flat_dim] + feature_dims
1253-
out_shape = [d.get_dim_value() for d in out_dims]
1291+
out_shape = _shape_dim_values(out_dims)
12541292
if mode == "sum" and isinstance(fill_value, (int, float)) and fill_value == 0:
12551293
out_raw = torch.zeros(out_shape, dtype=source.raw_tensor.dtype, device=source.raw_tensor.device)
12561294
out_raw.scatter_add_(dim=len(batch_dims), index=indices.raw_tensor.to(torch.int64), src=source.raw_tensor)
@@ -1326,6 +1364,11 @@ def slice(
13261364
size = end - start
13271365
else:
13281366
raise TypeError(f"slice: unsupported type for size: {type(size)}")
1367+
if torch.onnx.is_in_onnx_export():
1368+
if isinstance(start, torch.Tensor):
1369+
start = start.long()
1370+
if isinstance(size, torch.Tensor):
1371+
size = size.long()
13291372
out.raw_tensor = torch.narrow(source.raw_tensor, dim=axis_int, start=start, length=size)
13301373
return out
13311374

@@ -1793,7 +1836,7 @@ def random(
17931836
"""
17941837
random. See `rf.random` for details.
17951838
"""
1796-
shape = [d.get_dim_value() for d in dims]
1839+
shape = _shape_dim_values(dims)
17971840
dtype_ = TorchBackend.as_dtype_raw(dtype)
17981841
if out is None:
17991842
out = Tensor(
@@ -1898,9 +1941,7 @@ def random_choice_with_replacement(dims: Sequence[Dim], *, probs: Tensor, axis:
18981941
if len(common_dims) >= 2:
18991942
probs, flat_common_dim = rf.merge_dims(probs, dims=common_dims)
19001943
out_raw = torch.multinomial(probs.raw_tensor, num_samples=num_samples, replacement=True)
1901-
out_raw = out_raw.reshape(
1902-
[d.get_dim_value() for d in common_dims] + [d.get_dim_value() for d in non_common_dims]
1903-
)
1944+
out_raw = out_raw.reshape(_shape_dim_values(common_dims) + _shape_dim_values(non_common_dims))
19041945
out = rf.convert_to_tensor(out_raw, dims=common_dims + non_common_dims, sparse_dim=axis)
19051946
out = out.copy_transpose(dims)
19061947
out.name = "random_choice_with_replacement"
@@ -1928,7 +1969,7 @@ def masked_select(
19281969
in_raw = tensor.copy_compatible_to_dims_raw(tensor_templ_dims)
19291970
if any([in_raw.shape[i] == 1 < d.get_dim_value() for i, d in enumerate(dims)]):
19301971
# unbroadcast
1931-
in_raw = in_raw.expand([d.get_dim_value() for d in tensor_templ_dims])
1972+
in_raw = in_raw.expand(_shape_dim_values(tensor_templ_dims))
19321973
if mask.raw_tensor.device.type == "meta":
19331974
# This is not supported, but also, we would anyway not know the out shape.
19341975
# However, instead of erroring, just assume some dummy mask.
@@ -1976,7 +2017,7 @@ def masked_scatter(
19762017
source_raw = source.copy_compatible_to_dims_raw(source_templ_dims)
19772018

19782019
out_dims = tuple(dims) + tuple(remaining_dims)
1979-
out_shape = [d.get_dim_value() for d in out_dims]
2020+
out_shape = _shape_dim_values(out_dims)
19802021
if backup is None:
19812022
out_raw = torch.zeros(out_shape, dtype=source_raw.dtype, device=source_raw.device)
19822023
else:
@@ -2099,7 +2140,7 @@ def conv(
20992140
src_raw = torch.reshape(
21002141
source.raw_tensor,
21012142
# potentially merge batch dims all together
2102-
[-1, in_dim.get_dim_value()] + [d.get_dim_value() for d in in_spatial_dims],
2143+
_shape_values([-1, in_dim.get_dim_value()] + [d.get_dim_value() for d in in_spatial_dims]),
21032144
)
21042145
use_striding = strides and (strides > 1 if isinstance(strides, int) else any(s > 1 for s in strides))
21052146
if padding == "same" and not use_striding and all(d.dimension % 2 == 1 for d in filter_size):
@@ -2191,7 +2232,7 @@ def conv(
21912232
if len(batch_dims) == 1:
21922233
out.raw_tensor = out_raw
21932234
else:
2194-
out.raw_tensor = torch.reshape(out_raw, [d.get_dim_value() for d in out.dims])
2235+
out.raw_tensor = torch.reshape(out_raw, _shape_dim_values(out.dims))
21952236
out.feature_dim = out_dim
21962237
return out, out_spatial_dims
21972238

@@ -2235,7 +2276,7 @@ def transposed_conv(
22352276
src_raw = torch.reshape(
22362277
source.raw_tensor,
22372278
# potentially merge batch dims all together
2238-
[-1, in_dim.get_dim_value()] + [d.get_dim_value() for d in in_spatial_dims],
2279+
_shape_values([-1, in_dim.get_dim_value()] + [d.get_dim_value() for d in in_spatial_dims]),
22392280
)
22402281
if padding == "same":
22412282
raise NotImplementedError("transposed_conv with padding='same' not implemented")
@@ -2289,7 +2330,7 @@ def transposed_conv(
22892330
if len(batch_dims) == 1:
22902331
out.raw_tensor = out_raw
22912332
else:
2292-
out.raw_tensor = torch.reshape(out_raw, [d.get_dim_value() for d in out.dims])
2333+
out.raw_tensor = torch.reshape(out_raw, _shape_dim_values(out.dims))
22932334
out.feature_dim = out_dim
22942335
return out, out_spatial_dims
22952336

@@ -2319,21 +2360,28 @@ def pool(
23192360
# batch_dims would actually cover the channel-dim (C) as well,
23202361
# as it does not really matter to differentiate it from other batch dims.
23212362
source = source.copy_transpose(batch_dims + list(in_spatial_dims))
2322-
src_raw = torch.reshape(
2323-
source.raw_tensor,
2363+
if torch.onnx.is_in_onnx_export():
2364+
src_shape = source.raw_tensor.shape
2365+
pool_shape = [-1, src_shape[len(batch_dims) - 1] if batch_dims else 1] + list(src_shape[len(batch_dims) :])
2366+
else:
23242367
# Potentially merge batch dims all together.
23252368
# Keep the last as the channel-dim, but not sure if this is really relevant.
2326-
[-1, batch_dims[-1].get_dim_value() if batch_dims else 1] + [d.get_dim_value() for d in in_spatial_dims],
2327-
)
2369+
pool_shape = [-1, batch_dims[-1].get_dim_value() if batch_dims else 1] + [
2370+
d.get_dim_value() for d in in_spatial_dims
2371+
]
2372+
src_raw = torch.reshape(source.raw_tensor, _shape_values(pool_shape))
23282373
assert isinstance(strides, (list, tuple)) and len(strides) == len(in_spatial_dims) == len(pool_size)
23292374
if isinstance(padding, str) and padding.lower() == "same":
23302375
# padding='same' is not quite the same as ceil_mode=True, so we explicitly pad here.
2331-
padding = []
2332-
for i, s in enumerate(pool_size):
2333-
# See comment in conv.
2334-
# I'm a bit unsure here... https://github.com/pytorch/pytorch/issues/148123
2335-
pad = s - 1 - (src_raw.shape[2 + i] - 1) % strides[i]
2336-
padding.append(pad // 2)
2376+
if torch.onnx.is_in_onnx_export():
2377+
padding = [(s - 1) // 2 for s in pool_size]
2378+
else:
2379+
padding = []
2380+
for i, s in enumerate(pool_size):
2381+
# See comment in conv.
2382+
# I'm a bit unsure here... https://github.com/pytorch/pytorch/issues/148123
2383+
pad = s - 1 - (src_raw.shape[2 + i] - 1) % strides[i]
2384+
padding.append(pad // 2)
23372385
ceil_mode = True
23382386
elif isinstance(padding, str) and padding.lower() == "valid":
23392387
padding = 0
@@ -2355,7 +2403,7 @@ def pool(
23552403
kwargs["count_include_pad"] = False
23562404
out_raw = func(src_raw, kernel_size=pool_size, stride=strides, ceil_mode=ceil_mode, padding=padding, **kwargs)
23572405
out = Tensor("pool", dims=batch_dims + list(out_spatial_dims), dtype=source.dtype)
2358-
out.raw_tensor = torch.reshape(out_raw, [d.get_dim_value() for d in out.dims])
2406+
out.raw_tensor = torch.reshape(out_raw, _shape_dim_values(out.dims))
23592407
if source.feature_dim and source.feature_dim in out.dims:
23602408
out.feature_dim = source.feature_dim
23612409
return out, out_spatial_dims
@@ -2377,7 +2425,7 @@ def stft(
23772425
"""stft"""
23782426
batch_dims = [d for d in x.dims if d != in_spatial_dim]
23792427
x = x.copy_transpose(batch_dims + [in_spatial_dim])
2380-
x_raw = torch.reshape(x.raw_tensor, [-1, in_spatial_dim.get_dim_value()])
2428+
x_raw = torch.reshape(x.raw_tensor, _shape_values([-1, in_spatial_dim.get_dim_value()]))
23812429

23822430
# TF code: y = tf.signal.stft(x, frame_length=frame_size, frame_step=frame_shift, fft_length=fft_size)
23832431
# This is similar to what SciPy will also return.
@@ -2398,8 +2446,12 @@ def stft(
23982446

23992447
if frame_length > x_raw.shape[1]:
24002448
# Torch does not really support the empty case.
2401-
y = Tensor("stft", dims=batch_dims + [out_dim, out_spatial_dim], feature_dim=out_dim, dtype="complex64")
2402-
y.raw_tensor = torch.zeros([d.get_dim_value() for d in y.dims], dtype=torch.complex64)
2449+
if torch.onnx.is_in_onnx_export():
2450+
y = Tensor("stft", dims=batch_dims + [out_dim, out_spatial_dim], feature_dim=out_dim, dtype=x.dtype)
2451+
y.raw_tensor = torch.zeros(_shape_dim_values(y.dims), dtype=x_raw.dtype, device=x_raw.device)
2452+
else:
2453+
y = Tensor("stft", dims=batch_dims + [out_dim, out_spatial_dim], feature_dim=out_dim, dtype="complex64")
2454+
y.raw_tensor = torch.zeros(_shape_dim_values(y.dims), dtype=torch.complex64, device=x_raw.device)
24032455
return y
24042456

24052457
if window_enforce_even:
@@ -2420,6 +2472,7 @@ def stft(
24202472
# https://github.com/pytorch/pytorch/issues/117844
24212473
# (Check back later here whether that's still the case...)
24222474
x_raw = x_raw.to(torch.float32)
2475+
in_onnx_export = torch.onnx.is_in_onnx_export()
24232476
y_raw = torch.stft(
24242477
x_raw,
24252478
n_fft=fft_length,
@@ -2429,11 +2482,13 @@ def stft(
24292482
win_length=fft_length,
24302483
window=window_pt,
24312484
center=False,
2432-
return_complex=True,
2485+
return_complex=not in_onnx_export,
24332486
)
2487+
if in_onnx_export:
2488+
y_raw = torch.sqrt(torch.clamp_min(torch.sum(y_raw * y_raw, dim=-1), 0.0))
24342489
y = Tensor("stft", dims=batch_dims + [out_dim, out_spatial_dim], dtype=TorchBackend.get_dtype_name_raw(y_raw))
24352490
y.feature_dim = out_dim
2436-
y.raw_tensor = torch.reshape(y_raw, [d.get_dim_value() for d in y.dims])
2491+
y.raw_tensor = torch.reshape(y_raw, _shape_dim_values(y.dims))
24372492
return y
24382493

24392494
@staticmethod
@@ -2548,8 +2603,8 @@ def lstm(
25482603
out_raw,
25492604
[spatial_dim.get_dim_value()] + [d.get_dim_value() for d in batch_dims] + [out_dim.get_dim_value()],
25502605
)
2551-
new_state_h_raw = torch.reshape(new_state_h_raw, [d.get_dim_value() for d in state_h.dims])
2552-
new_state_c_raw = torch.reshape(new_state_c_raw, [d.get_dim_value() for d in state_c.dims])
2606+
new_state_h_raw = torch.reshape(new_state_h_raw, _shape_dim_values(state_h.dims))
2607+
new_state_c_raw = torch.reshape(new_state_c_raw, _shape_dim_values(state_c.dims))
25532608

25542609
out = source.copy_template_replace_dim_tag(axis=-1, new_dim_tag=out_dim, name="lstm")
25552610
out.feature_dim = out_dim

0 commit comments

Comments
 (0)