Recent advances in vision transformers (ViTs) have demonstrated the advantage of global modeling capabilities, prompting widespread integration of large-kernel convolutions for enlarging the effective receptive field (ERF). However, the quadratic scaling of parameter count and computational complexity (FLOPs) with respect to kernel size poses significant efficiency and optimization challenges. This paper introduces RecConv, a recursive decomposition strategy that efficiently constructs multi-frequency representations using small-kernel convolutions. RecConv establishes a linear relationship between parameter growth and decomposing levels which determines the effective receptive field $k\times 2^\ell$ for a base kernel $k$ and $\ell$ levels of decomposition, while maintaining constant FLOPs regardless of the ERF expansion. Specifically, RecConv achieves a parameter expansion of only $\ell+2$ times and a maximum FLOPs increase of $5/3$ times, compared to the exponential growth ($4^\ell$) of standard and depthwise convolutions. RecNeXt-M3 outperforms RepViT-M1.1 by 1.9 $AP^{box}$ on COCO with similar FLOPs. This innovation provides a promising avenue towards designing efficient and compact networks across various modalities. Codes and models can be found at https://github.com/suous/RecNeXt.
2025/06/27: Added A series code and logs, replacing convolution with linear attention.
2025/03/19: Added more ablation study results, including using attention with RecConv design.
2025/01/02: Uploaded checkpoints and training logs of RecNeXt-M0.
2024/12/29: Uploaded checkpoints and training logs of RecNeXt-M1 - M5.
Model Usage
Image Classification
python
1from urllib.request import urlopen
2from PIL import Image
3import timm
4import torch
56img = Image.open(urlopen(7'https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/beignets-task-guide.png'8))910model = timm.create_model('recnext_a0', pretrained=True, distillation=False)11model = model.eval()1213# get model specific transforms (normalization, resize)14data_config = timm.data.resolve_model_data_config(model)15transforms = timm.data.create_transform(**data_config, is_training=False)1617output = model(transforms(img).unsqueeze(0))# unsqueeze single image into batch of 11819top5_probabilities, top5_class_indices = torch.topk(output.softmax(dim=1)*100, k=5)
Converting to Inference Mode
python
1import utils
23# Convert training-time model to inference structure, fuse batchnorms4utils.replace_batchnorm(model)
Model Comparison
Classification
We introduce two series of models: the A series uses linear attention and nearest interpolation, while the M series employs convolution and bilinear interpolation for simplicity and broader hardware compatibility (e.g., to address suboptimal nearest interpolation support in some iOS versions).
dist: distillation; base: without distillation (all models are trained over 300 epochs).
We present a simple architecture, the overall design follows LSNet. This framework centers around sharing channel features from the previous layers.
Our motivation for doing so is to reduce the computational cost of token mixers and minimize feature redundancy in the final stage.
The NPU latency is measured on an iPhone 13 with models compiled by Core ML Tools.
The CPU latency is accessed on a Quad-core ARM Cortex-A57 processor in ONNX format.
And the throughput is tested on an Nvidia RTX3090 with maximum power-of-two batch size that fits in memory.
Latency Measurement
The latency reported in RecNeXt for iPhone 13 (iOS 18) uses the benchmark tool from XCode 14.
Download and extract ImageNet train and val images from http://image-net.org/. The training and validation data are expected to be in the train folder and val folder respectively:
1# RecConv Variant A2# recursive decomposition on both spatial and channel dimensions3# downsample and upsample through group convolutions4classRecConv2d(nn.Module):5def__init__(self, in_channels, kernel_size=5, bias=False, level=2):6super().__init__()7 self.level = level
8 kwargs ={'kernel_size': kernel_size,'padding': kernel_size //2,'bias': bias}9 downs =[]10for l inrange(level):11 i_channels = in_channels //(2** l)12 o_channels = in_channels //(2**(l+1))13 downs.append(nn.Conv2d(in_channels=i_channels, out_channels=o_channels, groups=o_channels, stride=2,**kwargs))14 self.downs = nn.ModuleList(downs)1516 convs =[]17for l inrange(level+1):18 channels = in_channels //(2** l)19 convs.append(nn.Conv2d(in_channels=channels, out_channels=channels, groups=channels,**kwargs))20 self.convs = nn.ModuleList(reversed(convs))2122# this is the simplest modification, only support resoltions like 256, 384, etc23 kwargs['kernel_size']= kernel_size +124 ups =[]25for l inrange(level):26 i_channels = in_channels //(2**(l+1))27 o_channels = in_channels //(2** l)28 ups.append(nn.ConvTranspose2d(in_channels=i_channels, out_channels=o_channels, groups=i_channels, stride=2,**kwargs))29 self.ups = nn.ModuleList(reversed(ups))3031defforward(self, x):32 i = x
33 features =[]34for down in self.downs:35 x, s = down(x), x.shape[2:]36 features.append((x, s))3738 x =039for conv, up,(f, s)inzip(self.convs, self.ups,reversed(features)):40 x = up(conv(f + x))41return self.convs[self.level](i + x)
RecConv using channel-wise concatenation
python
1# recursive decomposition on both spatial and channel dimensions2# downsample using channel-wise split, followed by depthwise convolution with a stride of 23# upsample through channel-wise concatenation4classRecConv2d(nn.Module):5def__init__(self, in_channels, kernel_size=5, bias=False, level=2):6super().__init__()7 self.level = level
8 kwargs ={'kernel_size': kernel_size,'padding': kernel_size //2,'bias': bias}9 downs =[]10for l inrange(level):11 channels = in_channels //(2**(l+1))12 downs.append(nn.Conv2d(in_channels=channels, out_channels=channels, groups=channels, stride=2,**kwargs))13 self.downs = nn.ModuleList(downs)1415 convs =[]16for l inrange(level+1):17 channels = in_channels //(2** l)18 convs.append(nn.Conv2d(in_channels=channels, out_channels=channels, groups=channels,**kwargs))19 self.convs = nn.ModuleList(reversed(convs))2021.# this is the simplest modification, only support resoltions like 256, 384, etc22 kwargs['kernel_size']= kernel_size +123 ups =[]24for l inrange(level):25 channels = in_channels //(2**(l+1))26 ups.append(nn.ConvTranspose2d(in_channels=channels, out_channels=channels, groups=channels, stride=2,**kwargs))27 self.ups = nn.ModuleList(reversed(ups))2829defforward(self, x):30 features =[]31for down in self.downs:32 r, x = torch.chunk(x,2, dim=1)33 x, s = down(x), x.shape[2:]34 features.append((r, s))3536for conv, up,(r, s)inzip(self.convs, self.ups,reversed(features)):37 x = torch.cat([r, up(conv(x))], dim=1)38return self.convs[self.level](x)
RecConv Beyond
We apply RecConv to MLLA small variants, replacing linear attention and downsampling layers.
Result in higher throughput and less training memory usage.
RecNeXt exhibits the lowest throughput among models of comparable parameter size due to extensive use of bilinear interpolation, which can be mitigated by employing transposed convolution.
The recursive decomposition may introduce numerical instability during mixed precision training, which can be alleviated by using fixed-point or BFloat16 arithmetic.
Compatibility issues with bilinear interpolation and transposed convolution on certain iOS versions may also result in performance degradation.