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
3447class 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