correct
26_ShuffleNet.py
started
2026-05-05T02:37:54Z
timingkernelbench.timing
kernel mean / median / std
8.38 / — / 0.20 μs
ref mean / median / std
8.75 / — / 1.55 μs
numerical precisioneval_kernel_against_ref
gpu memoryextended_metrics.measure_memory
kernel launches / fusiontorch.profiler
fusion ratio (ref / kernel)
1.11x
energyNVML (pynvml)
ratio (ref / kernel)
1.25x
SOL (speed-of-light)heuristic_from_speedup
roofline / occupancyheuristic
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
import torch.nn.functional as F
class ShuffleNetUnit(nn.Module):
def __init__(self, in_channels, out_channels, groups=3):
"""
ShuffleNet unit implementation.
:param in_channels: Number of input channels.
:param out_channels: Number of output channels.
:param groups: Number of groups for group convolution.
"""
super(ShuffleNetUnit, self).__init__()
# Ensure the output channels are divisible by groups
assert out_channels % 4 == 0
mid_channels = out_channels // 4
# First 1x1 group convolution
self.conv1 = nn.Conv2d(in_channels, mid_channels, kernel_size=1, stride=1, padding=0, groups=groups, bias=False)
self.bn1 = nn.BatchNorm2d(mid_channels)
# Depthwise 3x3 convolution
self.conv2 = nn.Conv2d(mid_channels, mid_channels, kernel_size=3, stride=1, padding=1, groups=mid_channels, bias=False)
self.bn2 = nn.BatchNorm2d(mid_channels)
# Second 1x1 group convolution
self.conv3 = nn.Conv2d(mid_channels, out_channels, kernel_size=1, stride=1, padding=0, groups=groups, bias=False)
self.bn3 = nn.BatchNorm2d(out_channels)
# Shuffle operation
self.shuffle = ChannelShuffle(groups)
# Shortcut connection if input and output channels are the same
if in_channels == out_channels:
self.shortcut = nn.Sequential()
else:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(out_channels)
)
def forward(self, x):
"""
Forward pass for ShuffleNet unit.
:param x: Input tensor, shape (batch_size, in_channels, height, width)
:return: Output tensor, shape (batch_size, out_channels, height, width)
"""
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
out = self.shuffle(out)
out = F.relu(self.bn3(self.conv3(out)))
out += self.shortcut(x)
return out
class ChannelShuffle(nn.Module):
def __init__(self, groups):
"""
Channel shuffle operation.
:param groups: Number of groups for shuffling.
"""
super(ChannelShuffle, self).__init__()
self.groups = groups
def forward(self, x):
"""
Forward pass for channel shuffle.
:param x: Input tensor, shape (batch_size, channels, height, width)
:return: Output tensor, shape (batch_size, channels, height, width)
"""
batch_size, channels, height, width = x.size()
channels_per_group = channels // self.groups
# Reshape
x = x.view(batch_size, self.groups, channels_per_group, height, width)
# Transpose
x = x.transpose(1, 2).contiguous()
# Flatten
x = x.view(batch_size, -1, height, width)
return x
class Model(nn.Module):
def __init__(self, num_classes=1000, groups=3, stages_repeats=[3, 7, 3], stages_out_channels=[24, 240, 480, 960]):
"""
ShuffleNet architecture.
:param num_classes: Number of output classes.
:param groups: Number of groups for group convolution.
:param stages_repeats: List of ints specifying the number of repeats for each stage.
:param stages_out_channels: List of ints specifying the output channels for each stage.
"""
super(Model, self).__init__()
self.conv1 = nn.Conv2d(3, stages_out_channels[0], kernel_size=3, stride=2, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(stages_out_channels[0])
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.stage2 = self._make_stage(stages_out_channels[0], stages_out_channels[1], stages_repeats[0], groups)
self.stage3 = self._make_stage(stages_out_channels[1], stages_out_channels[2], stages_repeats[1], groups)
self.stage4 = self._make_stage(stages_out_channels[2], stages_out_channels[3], stages_repeats[2], groups)
self.conv5 = nn.Conv2d(stages_out_channels[3], 1024, kernel_size=1, stride=1, padding=0, bias=False)
self.bn5 = nn.BatchNorm2d(1024)
self.fc = nn.Linear(1024, num_classes)
def _make_stage(self, in_channels, out_channels, repeats, groups):
"""
Helper function to create a stage of ShuffleNet units.
:param in_channels: Number of input channels.
:param out_channels: Number of output channels.
:param repeats: Number of ShuffleNet units in the stage.
:param groups: Number of groups for group convolution.
:return: nn.Sequential containing the stage.
"""
layers = []
layers.append(ShuffleNetUnit(in_channels, out_channels, groups))
for _ in range(1, repeats):
layers.append(ShuffleNetUnit(out_channels, out_channels, groups))
return nn.Sequential(*layers)
def forward(self, x):
"""
Forward pass for ShuffleNet.
:param x: Input tensor, shape (batch_size, 3, height, width)
:return: Output tensor, shape (batch_size, num_classes)
"""
x = F.relu(self.bn1(self.conv1(x)))
x = self.maxpool(x)
x = self.stage2(x)
x = self.stage3(x)
x = self.stage4(x)
x = F.relu(self.bn5(self.conv5(x)))
x = F.adaptive_avg_pool2d(x, (1, 1))
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
# Test code
batch_size = 10
input_channels = 3
height = 224
width = 224
num_classes = 1000
def get_inputs():
return [torch.rand(batch_size, input_channels, height, width)]
def get_init_inputs():
return [num_classes]turn 0 · FINAL · 1 tool call(s) · 81.7s turn · 81.7s LLM · 12,622 chars (click to expand)
turn 081.7s turn · 81.7s LLM · 0.0s tools · FINAL
reasoning
**Implementing channel shuffle**
I need to implement an extension with two functions. The first function, `channel_shuffle`, takes two inputs: `x` and `groups`. The input has a shape of N, C, H, W, and we calculate `cpg` as C divided by groups. For the output channel, I’m referencing the operation `x.view` to reshape it, then using `transpose` and `flatten`. I’m also trying to determine how to calculate the output channel, `c_out`, with the formula `cp*g + gidx`. Let's clarify those details!
**Evaluating stage units and performance**
I'm noticing a potential issue with the stage units. We define ChannelShuffleFast, but it seems the state_dict keys match, which is confusing. The reference has self.shuffle with no parameters, and I think I need to include the helper function _make_stage. I’m also considering whether I could reduce Python overhead by using manual forward loops. Additionally, there's a question of whether a custom shuffle kernel is slower due to launch overhead compared to transpose contiguous.
**Evaluating instantiation methods**
I'm considering whether I should instantiate separately for comparison. It seems like in tasks, there's usually a copying of reference weights to new instances. If I don't do this, I might not be able to match randomly due to the same seed. If I instantiate after referencing, the RNG gets consumed, making it different. I also wonder if I need to set maxpool to ceil? It might be a good idea to default to false. Maybe using nn.ReLU, or F.relu could work better. Time to submit!
call → submit_kernel
callsubmit_kernel
{
"kernel_code": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\n\n_cpp_src = r'''\n#include <torch/extension.h>\n\ntorch::Tensor channel_shuffle(torch::Tensor x, int64_t groups);\ntorch::Tensor dw_bn_shuffle(torch::Tensor x, torch::Tensor weight, torch::Tensor gamma,\n torch::Tensor beta, torch::Tensor mean, torch::Tensor var,\n double eps, int64_t groups);\n'''\n\n_cuda_src = r'''\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n__global__ void channel_shuffle_kernel(const float* __restrict__ x,\n float* __restrict__ y,\n int total, int C, int HW, int groups) {\n int cpg = C / groups;\n int stride = blockDim.x * gridDim.x;\n for (int idx = blockIdx.x * blockDim.x + threadIdx.x; idx < total; idx += stride) {\n int widx = idx % HW;\n int t = idx / HW;\n int co = t % C;\n int n = t / C;\n int cp = co / groups;\n int g = co - cp * groups;\n int ci = g * cpg + cp;\n y[(n * C + co) * HW + widx] = x[(n * C + ci) * HW + widx];\n }\n}\n\n__global__ void dw_bn_shuffle_kernel(const float* __restrict__ x,\n const float* __restrict__ w,\n const float* __restrict__ gamma,\n const float* __restrict__ beta,\n const float* __restrict__ mean,\n const float* __restrict__ var,\n float* __restrict__ y,\n int total, int N, int C, int H, int Wd,\n int groups, float eps) {\n int HW = H * Wd;\n int cpg = C / groups;\n int stride = blockDim.x * gridDim.x;\n for (int idx = blockIdx.x * blockDim.x + threadIdx.x; idx < total; idx += stride) {\n int pix = idx % HW;\n int h = pix / Wd;\n int col = pix - h * Wd;\n int t = idx / HW;\n int co = t % C;\n int n = t / C;\n int cp = co / groups;\n int g = co - cp * groups;\n int ci = g * cpg + cp;\n\n const float* xbase = x + (n * C + ci) * HW;\n const float* wbase = w + ci * 9;\n float acc = 0.0f;\n\n int ih = h - 1;\n if (ih >= 0) {\n int row = ih * Wd;\n int iw = col - 1;\n if (iw >= 0) acc += xbase[row + iw] * wbase[0];\n acc += xbase[row + col] * wbase[1];\n iw = col + 1;\n if (iw < Wd) acc += xbase[row + iw] * wbase[2];\n }\n ih = h;\n {\n int row = ih * Wd;\n int iw = col - 1;\n if (iw >= 0) acc += xbase[row + iw] * wbase[3];\n acc += xbase[row + col] * wbase[4];\n iw = col + 1;\n if (iw < Wd) acc += xbase[row + iw] * wbase[5];\n }\n ih = h + 1;\n if (ih < H) {\n int row = ih * Wd;\n int iw = col - 1;\n if (iw >= 0) acc += xbase[row + iw] * wbase[6];\n acc += xbase[row + col] * wbase[7];\n iw = col + 1;\n if (iw < Wd) acc += xbase[row + iw] * wbase[8];\n }\n\n float out = (acc - mean[ci]) * (gamma[ci] * rsqrtf(var[ci] + eps)) + beta[ci];\n y[(n * C + co) * HW + pix] = out;\n }\n}\n\ntorch::Tensor channel_shuffle(torch::Tensor x, int64_t groups) {\n TORCH_CHECK(x.is_cuda(), \"x must be CUDA\");\n TORCH_CHECK(x.scalar_type() == torch::kFloat32, \"only float32 supported\");\n TORCH_CHECK(x.dim() == 4, \"x must be NCHW\");\n auto y = torch::empty_like(x);\n int N = (int)x.size(0);\n int C = (int)x.size(1);\n int H = (int)x.size(2);\n int Wd = (int)x.size(3);\n int total = N * C * H * Wd;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n if (blocks > 4096) blocks = 4096;\n channel_shuffle_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(\n x.data_ptr<float>(), y.data_ptr<float>(), total, C, H * Wd, (int)groups);\n return y;\n}\n\ntorch::Tensor dw_bn_shuffle(torch::Tensor x, torch::Tensor weight, torch::Tensor gamma,\n torch::Tensor beta, torch::Tensor mean, torch::Tensor var,\n double eps, int64_t groups) {\n TORCH_CHECK(x.is_cuda(), \"x must be CUDA\");\n TORCH_CHECK(x.scalar_type() == torch::kFloat32, \"only float32 supported\");\n auto y = torch::empty_like(x);\n int N = (int)x.size(0);\n int C = (int)x.size(1);\n int H = (int)x.size(2);\n int Wd = (int)x.size(3);\n int total = N * C * H * Wd;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n if (blocks > 4096) blocks = 4096;\n dw_bn_shuffle_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(\n x.data_ptr<float>(), weight.data_ptr<float>(), gamma.data_ptr<float>(), beta.data_ptr<float>(),\n mean.data_ptr<float>(), var.data_ptr<float>(), y.data_ptr<float>(), total, N, C, H, Wd,\n (int)groups, (float)eps);\n return y;\n}\n'''\n\n_ext = load_inline(\n name=\"shufflenet_fused_ext_v1\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"channel_shuffle\", \"dw_bn_shuffle\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n verbose=False,\n)\n\n\nclass ChannelShuffle(nn.Module):\n def __init__(self, groups):\n super(ChannelShuffle, self).__init__()\n self.groups = groups\n\n def forward(self, x):\n if x.is_cuda and x.dtype == torch.float32 and x.is_contiguous():\n return _ext.channel_shuffle(x, self.groups)\n batch_size, channels, height, width = x.size()\n channels_per_group = channels // self.groups\n x = x.view(batch_size, self.groups, channels_per_group, height, width)\n x = x.transpose(1, 2).contiguous()\n x = x.view(batch_size, -1, height, width)\n return x\n\n\nclass ShuffleNetUnit(nn.Module):\n def __init__(self, in_channels, out_channels, groups=3):\n super(ShuffleNetUnit, self).__init__()\n assert out_channels % 4 == 0\n mid_channels = out_channels // 4\n self.groups = groups\n self.conv1 = nn.Conv2d(in_channels, mid_channels, kernel_size=1, stride=1, padding=0, groups=groups, bias=False)\n self.bn1 = nn.BatchNorm2d(mid_channels)\n self.conv2 = nn.Conv2d(mid_channels, mid_channels, kernel_size=3, stride=1, padding=1, groups=mid_channels, bias=False)\n self.bn2 = nn.BatchNorm2d(mid_channels)\n self.conv3 = nn.Conv2d(mid_channels, out_channels, kernel_size=1, stride=1, padding=0, groups=groups, bias=False)\n self.bn3 = nn.BatchNorm2d(out_channels)\n self.shuffle = ChannelShuffle(groups)\n if in_channels == out_channels:\n self.shortcut = nn.Sequential()\n else:\n self.shortcut = nn.Sequential(\n nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False),\n nn.BatchNorm2d(out_channels)\n )\n\n def forward(self, x):\n out = F.relu(self.bn1(self.conv1(x)))\n # In inference mode, fuse depthwise 3x3 + BatchNorm + channel shuffle into one CUDA kernel.\n # In training mode, keep PyTorch BatchNorm semantics exactly (batch statistics and running updates).\n if (not self.training) and out.is_cuda and out.dtype == torch.float32 and out.is_contiguous():\n out = _ext.dw_bn_shuffle(out, self.conv2.weight, self.bn2.weight, self.bn2.bias,\n self.bn2.running_mean, self.bn2.running_var, self.bn2.eps, self.groups)\n else:\n out = self.bn2(self.conv2(out))\n out = self.shuffle(out)\n out = F.relu(self.bn3(self.conv3(out)))\n out = out + self.shortcut(x)\n return out\n\n\nclass ModelNew(nn.Module):\n def __init__(self, num_classes=1000, groups=3, stages_repeats=[3, 7, 3], stages_out_channels=[24, 240, 480, 960]):\n super(ModelNew, self).__init__()\n self.conv1 = nn.Conv2d(3, stages_out_channels[0], kernel_size=3, stride=2, padding=1, bias=False)\n self.bn1 = nn.BatchNorm2d(stages_out_channels[0])\n self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)\n self.stage2 = self._make_stage(stages_out_channels[0], stages_out_channels[1], stages_repeats[0], groups)\n self.stage3 = self._make_stage(stages_out_channels[1], stages_out_channels[2], stages_repeats[1], groups)\n self.stage4 = self._make_stage(stages_out_channels[2], stages_out_channels[3], stages_repeats[2], groups)\n self.conv5 = nn.Conv2d(stages_out_channels[3], 1024, kernel_size=1, stride=1, padding=0, bias=False)\n self.bn5 = nn.BatchNorm2d(1024)\n self.fc = nn.Linear(1024, num_classes)\n\n def _make_stage(self, in_channels, out_channels, repeats, groups):\n layers = []\n layers.append(ShuffleNetUnit(in_channels, out_channels, groups))\n for _ in range(1, repeats):\n layers.append(ShuffleNetUnit(out_channels, out_channels, groups))\n return nn.Sequential(*layers)\n\n def forward(self, x):\n x = F.relu(self.bn1(self.conv1(x)))\n x = self.maxpool(x)\n x = self.stage2(x)\n x = self.stage3(x)\n x = self.stage4(x)\n x = F.relu(self.bn5(self.conv5(x)))\n x = F.adaptive_avg_pool2d(x, (1, 1))\n x = x.view(x.size(0), -1)\n x = self.fc(x)\n return x\n"
}
result ← submit_kernel (ok)
oksubmit_kernel
submit_kernel PASSED: (5 / 5) correctness trials all passed.
Kernel runtime: 8.38 μs
Runtime stats: mean=8.38μs median=?μs std=0.20μs
Numerical precision: max_abs_err=0.00e+00 mean_abs_err=0.00e+00 max_rel_err=0.00e+00
Memory: 495.76 MB peak (ref: 495.76 MB, ratio: 1.0x)
Kernel launches: 791 (ref: 882, fusion ratio: 1.115)
Energy: 966.0 mJ/run (ref: 1207.96 mJ, ratio: 1.2505x)
SOL score: 0.1044
final submitted kernel (232 lines, 9,500 chars) — full source, untruncated
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.cpp_extension import load_inline
_cpp_src = r'''
#include <torch/extension.h>
torch::Tensor channel_shuffle(torch::Tensor x, int64_t groups);
torch::Tensor dw_bn_shuffle(torch::Tensor x, torch::Tensor weight, torch::Tensor gamma,
torch::Tensor beta, torch::Tensor mean, torch::Tensor var,
double eps, int64_t groups);
'''
_cuda_src = r'''
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda.h>
#include <cuda_runtime.h>
__global__ void channel_shuffle_kernel(const float* __restrict__ x,
float* __restrict__ y,
int total, int C, int HW, int groups) {
int cpg = C / groups;
int stride = blockDim.x * gridDim.x;
for (int idx = blockIdx.x * blockDim.x + threadIdx.x; idx < total; idx += stride) {
int widx = idx % HW;
int t = idx / HW;
int co = t % C;
int n = t / C;
int cp = co / groups;
int g = co - cp * groups;
int ci = g * cpg + cp;
y[(n * C + co) * HW + widx] = x[(n * C + ci) * HW + widx];
}
}
__global__ void dw_bn_shuffle_kernel(const float* __restrict__ x,
const float* __restrict__ w,
const float* __restrict__ gamma,
const float* __restrict__ beta,
const float* __restrict__ mean,
const float* __restrict__ var,
float* __restrict__ y,
int total, int N, int C, int H, int Wd,
int groups, float eps) {
int HW = H * Wd;
int cpg = C / groups;
int stride = blockDim.x * gridDim.x;
for (int idx = blockIdx.x * blockDim.x + threadIdx.x; idx < total; idx += stride) {
int pix = idx % HW;
int h = pix / Wd;
int col = pix - h * Wd;
int t = idx / HW;
int co = t % C;
int n = t / C;
int cp = co / groups;
int g = co - cp * groups;
int ci = g * cpg + cp;
const float* xbase = x + (n * C + ci) * HW;
const float* wbase = w + ci * 9;
float acc = 0.0f;
int ih = h - 1;
if (ih >= 0) {
int row = ih * Wd;
int iw = col - 1;
if (iw >= 0) acc += xbase[row + iw] * wbase[0];
acc += xbase[row + col] * wbase[1];
iw = col + 1;
if (iw < Wd) acc += xbase[row + iw] * wbase[2];
}
ih = h;
{
int row = ih * Wd;
int iw = col - 1;
if (iw >= 0) acc += xbase[row + iw] * wbase[3];
acc += xbase[row + col] * wbase[4];
iw = col + 1;
if (iw < Wd) acc += xbase[row + iw] * wbase[5];
}
ih = h + 1;
if (ih < H) {
int row = ih * Wd;
int iw = col - 1;
if (iw >= 0) acc += xbase[row + iw] * wbase[6];
acc += xbase[row + col] * wbase[7];
iw = col + 1;
if (iw < Wd) acc += xbase[row + iw] * wbase[8];
}
float out = (acc - mean[ci]) * (gamma[ci] * rsqrtf(var[ci] + eps)) + beta[ci];
y[(n * C + co) * HW + pix] = out;
}
}
torch::Tensor channel_shuffle(torch::Tensor x, int64_t groups) {
TORCH_CHECK(x.is_cuda(), "x must be CUDA");
TORCH_CHECK(x.scalar_type() == torch::kFloat32, "only float32 supported");
TORCH_CHECK(x.dim() == 4, "x must be NCHW");
auto y = torch::empty_like(x);
int N = (int)x.size(0);
int C = (int)x.size(1);
int H = (int)x.size(2);
int Wd = (int)x.size(3);
int total = N * C * H * Wd;
int threads = 256;
int blocks = (total + threads - 1) / threads;
if (blocks > 4096) blocks = 4096;
channel_shuffle_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(
x.data_ptr<float>(), y.data_ptr<float>(), total, C, H * Wd, (int)groups);
return y;
}
torch::Tensor dw_bn_shuffle(torch::Tensor x, torch::Tensor weight, torch::Tensor gamma,
torch::Tensor beta, torch::Tensor mean, torch::Tensor var,
double eps, int64_t groups) {
TORCH_CHECK(x.is_cuda(), "x must be CUDA");
TORCH_CHECK(x.scalar_type() == torch::kFloat32, "only float32 supported");
auto y = torch::empty_like(x);
int N = (int)x.size(0);
int C = (int)x.size(1);
int H = (int)x.size(2);
int Wd = (int)x.size(3);
int total = N * C * H * Wd;
int threads = 256;
int blocks = (total + threads - 1) / threads;
if (blocks > 4096) blocks = 4096;
dw_bn_shuffle_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(
x.data_ptr<float>(), weight.data_ptr<float>(), gamma.data_ptr<float>(), beta.data_ptr<float>(),
mean.data_ptr<float>(), var.data_ptr<float>(), y.data_ptr<float>(), total, N, C, H, Wd,
(int)groups, (float)eps);
return y;
}
'''
_ext = load_inline(
name="shufflenet_fused_ext_v1",
cpp_sources=_cpp_src,
cuda_sources=_cuda_src,
functions=["channel_shuffle", "dw_bn_shuffle"],
extra_cflags=["-O3"],
extra_cuda_cflags=["-O3"],
verbose=False,
)
class ChannelShuffle(nn.Module):
def __init__(self, groups):
super(ChannelShuffle, self).__init__()
self.groups = groups
def forward(self, x):
if x.is_cuda and x.dtype == torch.float32 and x.is_contiguous():
return _ext.channel_shuffle(x, self.groups)
batch_size, channels, height, width = x.size()
channels_per_group = channels // self.groups
x = x.view(batch_size, self.groups, channels_per_group, height, width)
x = x.transpose(1, 2).contiguous()
x = x.view(batch_size, -1, height, width)
return x
class ShuffleNetUnit(nn.Module):
def __init__(self, in_channels, out_channels, groups=3):
super(ShuffleNetUnit, self).__init__()
assert out_channels % 4 == 0
mid_channels = out_channels // 4
self.groups = groups
self.conv1 = nn.Conv2d(in_channels, mid_channels, kernel_size=1, stride=1, padding=0, groups=groups, bias=False)
self.bn1 = nn.BatchNorm2d(mid_channels)
self.conv2 = nn.Conv2d(mid_channels, mid_channels, kernel_size=3, stride=1, padding=1, groups=mid_channels, bias=False)
self.bn2 = nn.BatchNorm2d(mid_channels)
self.conv3 = nn.Conv2d(mid_channels, out_channels, kernel_size=1, stride=1, padding=0, groups=groups, bias=False)
self.bn3 = nn.BatchNorm2d(out_channels)
self.shuffle = ChannelShuffle(groups)
if in_channels == out_channels:
self.shortcut = nn.Sequential()
else:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(out_channels)
)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
# In inference mode, fuse depthwise 3x3 + BatchNorm + channel shuffle into one CUDA kernel.
# In training mode, keep PyTorch BatchNorm semantics exactly (batch statistics and running updates).
if (not self.training) and out.is_cuda and out.dtype == torch.float32 and out.is_contiguous():
out = _ext.dw_bn_shuffle(out, self.conv2.weight, self.bn2.weight, self.bn2.bias,
self.bn2.running_mean, self.bn2.running_var, self.bn2.eps, self.groups)
else:
out = self.bn2(self.conv2(out))
out = self.shuffle(out)
out = F.relu(self.bn3(self.conv3(out)))
out = out + self.shortcut(x)
return out
class ModelNew(nn.Module):
def __init__(self, num_classes=1000, groups=3, stages_repeats=[3, 7, 3], stages_out_channels=[24, 240, 480, 960]):
super(ModelNew, self).__init__()
self.conv1 = nn.Conv2d(3, stages_out_channels[0], kernel_size=3, stride=2, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(stages_out_channels[0])
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.stage2 = self._make_stage(stages_out_channels[0], stages_out_channels[1], stages_repeats[0], groups)
self.stage3 = self._make_stage(stages_out_channels[1], stages_out_channels[2], stages_repeats[1], groups)
self.stage4 = self._make_stage(stages_out_channels[2], stages_out_channels[3], stages_repeats[2], groups)
self.conv5 = nn.Conv2d(stages_out_channels[3], 1024, kernel_size=1, stride=1, padding=0, bias=False)
self.bn5 = nn.BatchNorm2d(1024)
self.fc = nn.Linear(1024, num_classes)
def _make_stage(self, in_channels, out_channels, repeats, groups):
layers = []
layers.append(ShuffleNetUnit(in_channels, out_channels, groups))
for _ in range(1, repeats):
layers.append(ShuffleNetUnit(out_channels, out_channels, groups))
return nn.Sequential(*layers)
def forward(self, x):
x = F.relu(self.bn1(self.conv1(x)))
x = self.maxpool(x)
x = self.stage2(x)
x = self.stage3(x)
x = self.stage4(x)
x = F.relu(self.bn5(self.conv5(x)))
x = F.adaptive_avg_pool2d(x, (1, 1))
x = x.view(x.size(0), -1)
x = self.fc(x)
return x