padding same
PyTorch
padding conversion
deep learning
computer vision

padding'same' conversion to PyTorch padding

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When you port a convolutional model from TensorFlow to PyTorch, padding='same' is one of the first details that can change numerical results. The idea sounds simple, but the exact padding depends on kernel size, stride, and dilation, and older PyTorch code often needs manual padding.

What same Padding Means

In TensorFlow, same padding tries to preserve the spatial size for stride 1, and for larger strides it chooses enough padding so the output follows TensorFlow's shape rule. The important point is that the framework may use asymmetric padding, which means the top and bottom, or left and right, do not always receive the same value.

In PyTorch, there are three common cases:

  1. Modern PyTorch can accept padding="same" directly in many convolution layers.
  2. Older code uses an integer padding value such as padding=1.
  3. Model conversion pipelines use torch.nn.functional.pad or nn.ZeroPad2d before a convolution.

If your model has an odd kernel, stride 1, and dilation 1, the conversion is easy. A 3 x 3 kernel usually maps to padding=1, and a 5 x 5 kernel maps to padding=2.

Simple Conversion For Common Cases

For symmetric, odd-sized kernels, a direct integer often works:

python
1import torch
2import torch.nn as nn
3
4conv = nn.Conv2d(
5    in_channels=32,
6    out_channels=64,
7    kernel_size=3,
8    stride=1,
9    padding=1,
10)
11
12x = torch.randn(8, 32, 128, 128)
13y = conv(x)
14print(y.shape)  # torch.Size([8, 64, 128, 128])

This matches TensorFlow same behavior for the most common image-model setup. The trouble starts when the stride is greater than 1, the kernel is even-sized, or dilation expands the effective kernel width.

Computing TensorFlow-Style Padding Explicitly

When you need exact TensorFlow behavior, compute the padding from the input size and apply it yourself:

python
1import math
2import torch
3import torch.nn.functional as F
4from torch import nn
5
6
7def tf_same_pad_2d(x, kernel_size, stride=1, dilation=1):
8    if isinstance(kernel_size, int):
9        kernel_size = (kernel_size, kernel_size)
10    if isinstance(stride, int):
11        stride = (stride, stride)
12    if isinstance(dilation, int):
13        dilation = (dilation, dilation)
14
15    in_h, in_w = x.shape[-2:]
16    eff_kh = (kernel_size[0] - 1) * dilation[0] + 1
17    eff_kw = (kernel_size[1] - 1) * dilation[1] + 1
18
19    out_h = math.ceil(in_h / stride[0])
20    out_w = math.ceil(in_w / stride[1])
21
22    pad_h = max((out_h - 1) * stride[0] + eff_kh - in_h, 0)
23    pad_w = max((out_w - 1) * stride[1] + eff_kw - in_w, 0)
24
25    pad_top = pad_h // 2
26    pad_bottom = pad_h - pad_top
27    pad_left = pad_w // 2
28    pad_right = pad_w - pad_left
29
30    return F.pad(x, (pad_left, pad_right, pad_top, pad_bottom))
31
32
33x = torch.randn(1, 3, 224, 224)
34conv = nn.Conv2d(3, 16, kernel_size=4, stride=2, padding=0)
35
36x_padded = tf_same_pad_2d(x, kernel_size=4, stride=2)
37y = conv(x_padded)
38print(y.shape)

This pattern is reliable during model conversion because it mirrors the shape rule rather than guessing a single integer padding value.

Wrapping The Logic In A Reusable Module

If you need the same behavior in several places, wrap it in a small module:

python
1import torch
2from torch import nn
3
4
5class Conv2dSame(nn.Module):
6    def __init__(self, in_channels, out_channels, kernel_size, stride=1, dilation=1):
7        super().__init__()
8        self.kernel_size = kernel_size
9        self.stride = stride
10        self.dilation = dilation
11        self.conv = nn.Conv2d(
12            in_channels,
13            out_channels,
14            kernel_size=kernel_size,
15            stride=stride,
16            dilation=dilation,
17            padding=0,
18        )
19
20    def forward(self, x):
21        x = tf_same_pad_2d(x, self.kernel_size, self.stride, self.dilation)
22        return self.conv(x)

This makes converted code easier to read and keeps shape handling close to the layer that depends on it.

Common Pitfalls

The biggest mistake is assuming padding = kernel_size // 2 always matches TensorFlow same. That only holds in simple symmetric cases. With even kernels or larger strides, TensorFlow may pad one side more than the other.

Another issue is forgetting dilation. Dilation changes the effective kernel size, so the required padding grows even if the original kernel shape stays the same.

Version differences also matter. Recent PyTorch releases support padding="same" in many places, but exported or legacy code may still need manual padding. If exact parity matters, test shapes and outputs against the original model instead of assuming the built-in option is identical in every deployment path.

Finally, verify padding order when calling F.pad. For 2D images, PyTorch expects (left, right, top, bottom). Reversing that order gives wrong feature alignment while still producing a tensor of plausible size.

Summary

  • TensorFlow same padding may be asymmetric, especially with larger strides or even kernels.
  • For odd kernels with stride 1, integer padding like padding=1 often matches.
  • For exact TensorFlow behavior, compute padding from input shape, stride, kernel size, and dilation.
  • 'F.pad plus a padding=0 convolution is a dependable conversion pattern.'
  • Always validate both output shape and numerical alignment after conversion.

Course illustration
Course illustration

All Rights Reserved.