@@ -5470,9 +5470,9 @@ def _interpolate_scale_factor_helper(
54705470 scale_factor : Sequence [float ] | float ,
54715471 mode : str = "nearest" ,
54725472) -> TensorLike :
5473- if mode not in ("nearest" , "nearest-exact" ):
5473+ if mode not in ("nearest" , "nearest-exact" , "bilinear" ):
54745474 raise ValueError (
5475- f"_interpolate_scale_factor_helper expected mode to be 'nearest' or 'nearest-exact', but got { mode } "
5475+ f"_interpolate_scale_factor_helper expected mode to be 'nearest', 'nearest-exact' or 'bilinear ', but got { mode } "
54765476 )
54775477
54785478 # a is assumed to be at least 3D.
@@ -5493,66 +5493,117 @@ def _interpolate_scale_factor_helper(
54935493 f"a sequence of strictly positive floating point numbers of length { dim } " ,
54945494 )
54955495
5496- # perform nearest up/down-sampling
5497- def nearest_sampler (
5498- t : TensorLike ,
5499- input_dim : int ,
5500- output_dim : int ,
5501- * ,
5502- scale : float ,
5503- dim : int ,
5504- exact : bool ,
5505- ) -> TensorLike :
5506- # It is expected that output_dim = int(input_dim * scale).
5507- # Indices [0, ..., output_dim - 1] are mapped to [0, ..., input_dim - 1]
5508- # with the rule i -> int(i * scale) or i -> round((i + 0.5) * scale - 0.5),
5509- # corresponding to modes 'nearest' or 'nearest-exact', respectively.
5510- # Values at these indices is the result.
5511- # References https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/native/UpSample.h
5512- selected_idx = arange (0 , output_dim , device = a .device )
5513- selected_idx = to ((selected_idx + exact * 0.5 ) * scale , selected_idx .dtype )
5514- return clang .take (t , selected_idx , dim = dim )
5515-
5516- def dim_expander (t , dim , n_repeats ):
5517- t = unsqueeze (t , dim + 1 )
5518- t = expand (t , t .shape [: dim + 1 ] + (n_repeats ,) + t .shape [dim + 2 :])
5519- return t
5520-
5521- res_output_spatial_dims = []
5522-
5523- for k , (scale , input_dim ) in enumerate (zip (reversed (scale_factor ), reversed (spatial_dims ))):
5524- output_dim = int (scale * input_dim )
5496+ if mode == "bilinear" :
5497+
5498+ def _bilinear_sampler_2d (
5499+ t : TensorLike ,
5500+ in_h : int ,
5501+ in_w : int ,
5502+ out_h : int ,
5503+ out_w : int ,
5504+ * ,
5505+ dim_h : int = 2 ,
5506+ dim_w : int = 3 ,
5507+ ) -> TensorLike :
5508+ # The X pass
5509+ x_dst = arange (out_w , device = t .device )
5510+ scale_w = to (in_w / out_w , a .dtype )
5511+ # The 0.5s come from the fact that we treat each element as a pixel,
5512+ # that has its centerpoint at x0 + 0.5. With the formula below we
5513+ # make sure that the centerpoints of these "images" align, because align_corners
5514+ # is not yet implemented
5515+ x_src_f = to ((x_dst + 0.5 ) * scale_w - 0.5 , a .dtype )
5516+ x0 = clamp (clang .floor (x_src_f ), 0 , in_w - 1 ).to (to_dtype (torch .int64 ))
5517+ x1 = clamp ((x0 + 1 ), max = in_w - 1 )
5518+ wx = unsqueeze (unsqueeze ((x_src_f - x0 .to (x_src_f .dtype )), 0 ), 0 )
5519+
5520+ v0 = clang .take (t , x0 , dim = dim_w )
5521+ v1 = clang .take (t , x1 , dim = dim_w )
5522+ # Linear interpolation in the width-direction
5523+ t_x = v0 * (1 - wx ) + v1 * wx
5524+
5525+ # The Y pass
5526+ y_dst = arange (out_h , device = t .device )
5527+ scale_h = to (in_h / out_h , a .dtype )
5528+ y_src_f = to ((y_dst + 0.5 ) * scale_h - 0.5 , a .dtype )
5529+ y0 = clamp (clang .floor (y_src_f ), 0 , in_h - 1 ).to (to_dtype (torch .int64 ))
5530+ y1 = clamp ((y0 + 1 ), max = in_h - 1 )
5531+ wy = unsqueeze (unsqueeze (unsqueeze ((y_src_f - y0 .to (y_src_f .dtype )), 0 ), 0 ), - 1 )
5532+
5533+ v0 = clang .take (t_x , y0 , dim = dim_h )
5534+ v1 = clang .take (t_x , y1 , dim = dim_h )
5535+ return v0 * (1 - wy ) + v1 * wy
5536+
55255537 utils .check (
5526- output_dim > 0 ,
5527- lambda : f"provided scale_factor value { scale } results in a zero length output at dimension { k + 2 } " ,
5538+ len ( spatial_dims ) == 2 ,
5539+ lambda : f"bilinear interpolation supports exactly two spatial dims, got { len ( spatial_dims ) } " ,
55285540 )
5529- res_output_spatial_dims .append (output_dim )
5530-
5531- # k iterates from the end, and we skip the first 2
5532- # dimenions corresponding to batches and channels.
5533- curr_dim = 2 + (len (spatial_dims ) - k - 1 )
5534-
5535- exact : bool = mode == "nearest-exact"
5536- if output_dim <= input_dim :
5537- if output_dim <= input_dim // 2 :
5538- # scale_factor <= 1 (i.e. output_dim <= input_dim) implies simple slice
5539- # when output_dim <= input_dim // 2.
5540- stride = input_dim // output_dim
5541- end = input_dim - (input_dim % output_dim )
5542- a = clang .slice_in_dim (a , 0 , end , stride = stride , dim = curr_dim )
5543- else :
5544- # In this case slice will not do and explicit downsample is needed.
5545- a = nearest_sampler (a , input_dim , output_dim , scale = 1.0 / scale , dim = curr_dim , exact = exact )
5546- else :
5547- if output_dim % input_dim == 0 :
5548- # In this case we can just expand dim.
5549- n_repeats = output_dim // input_dim
5550- a = dim_expander (a , curr_dim , n_repeats )
5541+ in_h , in_w = spatial_dims
5542+ out_h = int (in_h * scale_factor [0 ])
5543+ out_w = int (in_w * scale_factor [1 ])
5544+ utils .check (out_h > 0 and out_w > 0 , lambda : f"scale_factor leads to zero-size output ({ out_h } x{ out_w } )" )
5545+ return _bilinear_sampler_2d (a , in_h , in_w , out_h , out_w )
5546+ elif mode == "nearest" or mode == "nearest-exact" :
5547+ # perform nearest up/down-sampling
5548+ def nearest_sampler (
5549+ t : TensorLike ,
5550+ input_dim : int ,
5551+ output_dim : int ,
5552+ * ,
5553+ scale : float ,
5554+ dim : int ,
5555+ exact : bool ,
5556+ ) -> TensorLike :
5557+ # It is expected that output_dim = int(input_dim * scale).
5558+ # Indices [0, ..., output_dim - 1] are mapped to [0, ..., input_dim - 1]
5559+ # with the rule i -> int(i * scale) or i -> round((i + 0.5) * scale - 0.5),
5560+ # corresponding to modes 'nearest' or 'nearest-exact', respectively.
5561+ # Values at these indices is the result.
5562+ # References https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/native/UpSample.h
5563+ selected_idx = arange (0 , output_dim , device = a .device )
5564+ selected_idx = to ((selected_idx + exact * 0.5 ) * scale , selected_idx .dtype )
5565+ return clang .take (t , selected_idx , dim = dim )
5566+
5567+ def dim_expander (t , dim , n_repeats ):
5568+ t = unsqueeze (t , dim + 1 )
5569+ t = expand (t , t .shape [: dim + 1 ] + (n_repeats ,) + t .shape [dim + 2 :])
5570+ return t
5571+
5572+ res_output_spatial_dims = []
5573+
5574+ for k , (scale , input_dim ) in enumerate (zip (reversed (scale_factor ), reversed (spatial_dims ))):
5575+ output_dim = int (scale * input_dim )
5576+ utils .check (
5577+ output_dim > 0 ,
5578+ lambda : f"provided scale_factor value { scale } results in a zero length output at dimension { k + 2 } " ,
5579+ )
5580+ res_output_spatial_dims .append (output_dim )
5581+
5582+ # k iterates from the end, and we skip the first 2
5583+ # dimenions corresponding to batches and channels.
5584+ curr_dim = 2 + (len (spatial_dims ) - k - 1 )
5585+
5586+ exact : bool = mode == "nearest-exact"
5587+ if output_dim <= input_dim :
5588+ if output_dim <= input_dim // 2 :
5589+ # scale_factor <= 1 (i.e. output_dim <= input_dim) implies simple slice
5590+ # when output_dim <= input_dim // 2.
5591+ stride = input_dim // output_dim
5592+ end = input_dim - (input_dim % output_dim )
5593+ a = clang .slice_in_dim (a , 0 , end , stride = stride , dim = curr_dim )
5594+ else :
5595+ # In this case slice will not do and explicit downsample is needed.
5596+ a = nearest_sampler (a , input_dim , output_dim , scale = 1.0 / scale , dim = curr_dim , exact = exact )
55515597 else :
5552- # In this case expand will not cut it and explicit upsampling is needed.
5553- a = nearest_sampler (a , input_dim , output_dim , scale = 1.0 / scale , dim = curr_dim , exact = exact )
5598+ if output_dim % input_dim == 0 :
5599+ # In this case we can just expand dim.
5600+ n_repeats = output_dim // input_dim
5601+ a = dim_expander (a , curr_dim , n_repeats )
5602+ else :
5603+ # In this case expand will not cut it and explicit upsampling is needed.
5604+ a = nearest_sampler (a , input_dim , output_dim , scale = 1.0 / scale , dim = curr_dim , exact = exact )
55545605
5555- output_shape = [batch , channels ] + res_output_spatial_dims [::- 1 ]
5606+ output_shape = [batch , channels ] + res_output_spatial_dims [::- 1 ]
55565607 return reshape (a , output_shape )
55575608
55585609
@@ -5596,8 +5647,8 @@ def interpolate(
55965647 antialias : bool = False ,
55975648) -> TensorLike :
55985649 utils .check (
5599- (mode == "nearest" or mode == "nearest-exact" ),
5600- lambda : f"only modes 'nearest' and 'nearest-exact' are supported at the moment, but got { mode = } " ,
5650+ (mode == "nearest" or mode == "nearest-exact" or mode == "bilinear" ),
5651+ lambda : f"only modes 'nearest', 'nearest-exact' and 'bilinear ' are supported at the moment, but got { mode = } " ,
56015652 exception_type = NotImplementedError ,
56025653 )
56035654
0 commit comments