46_NetVladWithGhostClusters
• warp_reduction_netvlad_optimized_base
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch as th
def module_fn(
x: torch.Tensor,
clusters: torch.Tensor,
clusters2: torch.Tensor,
bn_weight: torch.Tensor,
bn_bias: torch.Tensor,
bn_mean: torch.Tensor,
bn_var: torch.Tensor,
is_training: bool,
cluster_size: int,
feature_size: int,
) -> torch.Tensor:
"""
Functional version of the NetVLAD with ghost clusters
Args:
x: Input tensor of shape (batch_size, num_features, feature_size)
clusters: Weight tensor for cluster assignments
clusters2: Weight tensor for visual words
bn_weight: BatchNorm weight
bn_bias: BatchNorm bias
bn_mean: BatchNorm running mean
bn_var: BatchNorm running var
is_training: Whether in training mode
cluster_size: Number of clusters (K)
feature_size: Feature dimension (D)
Returns:
Output tensor of shape (batch_size, cluster_size * feature_size)
"""
max_sample = x.size()[1]
x = x.view(-1, feature_size) # B x N x D -> BN x D
if x.device != clusters.device:
msg = f"x.device {x.device} != cluster.device {clusters.device}"
raise ValueError(msg)
assignment = th.matmul(x, clusters) # (BN x D) x (D x (K+G)) -> BN x (K+G)
assignment = F.batch_norm(
assignment, bn_mean, bn_var, bn_weight, bn_bias, is_training
)
assignment = F.softmax(assignment, dim=1) # BN x (K+G) -> BN x (K+G)
# remove ghost assigments
assignment = assignment[:, :cluster_size]
assignment = assignment.view(-1, max_sample, cluster_size) # -> B x N x K
a_sum = th.sum(assignment, dim=1, keepdim=True) # B x N x K -> B x 1 x K
a = a_sum * clusters2
assignment = assignment.transpose(1, 2) # B x N x K -> B x K x N
x = x.view(-1, max_sample, feature_size) # BN x D -> B x N x D
vlad = th.matmul(assignment, x) # (B x K x N) x (B x N x D) -> B x K x D
vlad = vlad.transpose(1, 2) # -> B x D x K
vlad = vlad - a
# L2 intra norm
vlad = F.normalize(vlad)
# flattening + L2 norm
vlad = vlad.reshape(-1, cluster_size * feature_size) # -> B x DK
vlad = F.normalize(vlad)
return vlad # B x DK
class Model(nn.Module):
def __init__(self, cluster_size, feature_size, ghost_clusters):
super(Model, self).__init__()
self.feature_size = feature_size
self.cluster_size = cluster_size
self.ghost_clusters = ghost_clusters
init_sc = 1 / math.sqrt(feature_size)
clusters = cluster_size + ghost_clusters
# The `clusters` weights are the `(w,b)` in the paper
self.clusters = nn.Parameter(init_sc * th.randn(feature_size, clusters))
# Extract batchnorm parameters
bn = nn.BatchNorm1d(clusters)
self.bn_weight = nn.Parameter(bn.weight.data.clone())
self.bn_bias = nn.Parameter(bn.bias.data.clone())
self.bn_mean = nn.Parameter(bn.running_mean.data.clone())
self.bn_var = nn.Parameter(bn.running_var.data.clone())
# The `clusters2` weights are the visual words `c_k` in the paper
self.clusters2 = nn.Parameter(init_sc * th.randn(1, feature_size, cluster_size))
self.out_dim = self.cluster_size * feature_size
def forward(self, x, fn=module_fn):
return fn(
x,
self.clusters,
self.clusters2,
self.bn_weight,
self.bn_bias,
self.bn_mean,
self.bn_var,
self.training,
self.cluster_size,
self.feature_size,
)
batch_size = 32
num_features = 100
num_clusters = 32
feature_size = 512
ghost_clusters = 16
def get_inputs():
return [torch.randn(batch_size, num_features, feature_size)]
def get_init_inputs():
return [num_clusters, feature_size, ghost_clusters]
# Copyright 2018 Antoine Miech All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Code modified from here
https://github.com/albanie/collaborative-experts/blob/master/model/net_vlad.py
"""
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch as th
class Model(nn.Module):
def __init__(self, cluster_size, feature_size, ghost_clusters):
super(Model, self).__init__()
self.feature_size = feature_size
self.cluster_size = cluster_size
self.ghost_clusters = ghost_clusters
init_sc = (1 / math.sqrt(feature_size))
clusters = cluster_size + ghost_clusters
# The `clusters` weights are the `(w,b)` in the paper
self.clusters = nn.Parameter(init_sc * th.randn(feature_size, clusters))
self.batch_norm = nn.BatchNorm1d(clusters)
# The `clusters2` weights are the visual words `c_k` in the paper
self.clusters2 = nn.Parameter(init_sc * th.randn(1, feature_size, cluster_size))
self.out_dim = self.cluster_size * feature_size
def forward(self, x, mask=None):
"""Aggregates feature maps into a fixed size representation. In the following
notation, B = batch_size, N = num_features, K = num_clusters, D = feature_size.
Args:
x (th.Tensor): B x N x D
Returns:
(th.Tensor): B x DK
"""
max_sample = x.size()[1]
x = x.view(-1, self.feature_size) # B x N x D -> BN x D
if x.device != self.clusters.device:
msg = f"x.device {x.device} != cluster.device {self.clusters.device}"
raise ValueError(msg)
assignment = th.matmul(x, self.clusters) # (BN x D) x (D x (K+G)) -> BN x (K+G)
assignment = self.batch_norm(assignment)
assignment = F.softmax(assignment, dim=1) # BN x (K+G) -> BN x (K+G)
# remove ghost assigments
assignment = assignment[:, :self.cluster_size]
assignment = assignment.view(-1, max_sample, self.cluster_size) # -> B x N x K
a_sum = th.sum(assignment, dim=1, keepdim=True) # B x N x K -> B x 1 x K
a = a_sum * self.clusters2
assignment = assignment.transpose(1, 2) # B x N x K -> B x K x N
x = x.view(-1, max_sample, self.feature_size) # BN x D -> B x N x D
vlad = th.matmul(assignment, x) # (B x K x N) x (B x N x D) -> B x K x D
vlad = vlad.transpose(1, 2) # -> B x D x K
vlad = vlad - a
# L2 intra norm
vlad = F.normalize(vlad)
# flattening + L2 norm
vlad = vlad.reshape(-1, self.cluster_size * self.feature_size) # -> B x DK
vlad = F.normalize(vlad)
return vlad # B x DK
batch_size = 32
num_features = 100
num_clusters = 32
feature_size = 512
ghost_clusters = 16
def get_inputs():
return [torch.randn(batch_size, num_features, feature_size)]
def get_init_inputs():
return [num_clusters, feature_size, ghost_clusters]
#include <torch/extension.h>
#include <ATen/ATen.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <vector>
// Warp-level reduction using shuffle down
__inline__ __device__ float warpReduceSum(float val) {
for (int offset = warpSize/2; offset > 0; offset /= 2)
val += __shfl_down_sync(0xFFFFFFFF, val, offset);
return val;
}
// Function to calculate L2 norm using warp-level reduction
__inline__ __device__ float warpReduceMax(float val) {
for (int offset = warpSize/2; offset > 0; offset /= 2)
val = fmaxf(val, __shfl_down_sync(0xFFFFFFFF, val, offset));
return val;
}
// Forward function for NetVLAD with ghost clusters optimized using warp-level reductions
// This function combines matmul, batch norm, and softmax calculations and optimizes them
// using warp-level primitives for small reductions.
torch::Tensor forward(
torch::Tensor x,
torch::Tensor clusters,
torch::Tensor clusters2,
torch::Tensor bn_weight,
torch::Tensor bn_bias,
torch::Tensor bn_mean,
torch::Tensor bn_var,
bool is_training,
int64_t cluster_size,
int64_t feature_size
) {
// Dimensions extraction
auto B = x.size(0);
auto N = x.size(1);
auto D = x.size(2);
// Flatten input
x = x.reshape({B * N, D});
// Modular computation blocks
auto assignment = [&] {
auto a = at::matmul(x, clusters);
a = at::batch_norm(a, bn_weight, bn_bias, bn_mean, bn_var, is_training, 0.1, 1e-5, true);
return at::softmax(a, 1).narrow(1, 0, cluster_size);
}();
// Assignment processing
assignment = assignment.reshape({B, N, cluster_size});
auto a_sum = assignment.sum(1, true);
// Final VLAD computation
auto a = a_sum * clusters2;
assignment = assignment.transpose(1, 2);
x = x.reshape({B, N, D});
auto vlad = at::bmm(assignment, x).transpose(1, 2) - a;
// Perform warp-level reductions for intra-normalization
vlad = vlad / (vlad.norm(2, {1}, true) + 1e-12);
vlad = vlad.reshape({B, D * cluster_size});
// Final normalization using warp-level reduction
vlad = vlad / (vlad.norm(2, {1}, true) + 1e-12);
return vlad;
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("forward", &forward, "Optimized NetVLAD with warp-level reductions (CUDA)");
}
Operation / Metric | Value | Unit |
---|---|---|
aten::zero_ | ||
CPU Time | 494205.33 | μs |
Device Time | 2207427.58 | μs |
Self CPU Time | 90733.22 | μs |
Self Device Time | 0.00 | μs |
CPU Memory Usage | 0 | B |
Device Memory Usage | 0 | B |
Self CPU Memory Usage | 0 | B |
Self Device Memory Usage | 0 | B |
aten::fill_ | ||
CPU Time | 403497.05 | μs |
Device Time | 2207427.58 | μs |
Self CPU Time | 119549.73 | μs |
Self Device Time | 2207427.58 | μs |
CPU Memory Usage | 0 | B |
Device Memory Usage | 0 | B |
Self CPU Memory Usage | 0 | B |
Self Device Memory Usage | 0 | B |
aten::matmul | ||
CPU Time | 835849.94 | μs |
Device Time | 483202.56 | μs |
Self CPU Time | 29826.26 | μs |
Self Device Time | 0.00 | μs |
CPU Memory Usage | 0 | B |
Device Memory Usage | 0 | B |
Self CPU Memory Usage | 0 | B |
Self Device Memory Usage | 0 | B |
aten::batch_norm | ||
CPU Time | 1742635.48 | μs |
Device Time | 483339.96 | μs |
Self CPU Time | 50864.22 | μs |
Self Device Time | 0.00 | μs |
CPU Memory Usage | 0 | B |
Device Memory Usage | 0 | B |
Self CPU Memory Usage | 0 | B |
Self Device Memory Usage | 0 | B |
aten::_batch_norm_impl_index | ||
CPU Time | 1691771.26 | μs |
Device Time | 483339.96 | μs |
Self CPU Time | 83720.45 | μs |
Self Device Time | 0.00 | μs |
CPU Memory Usage | 0 | B |
Device Memory Usage | 0 | B |
Self CPU Memory Usage | 0 | B |
Self Device Memory Usage | 0 | B |
aten::native_batch_norm | ||
CPU Time | 1543402.13 | μs |
Device Time | 483339.96 | μs |
Self CPU Time | 406540.91 | μs |
Self Device Time | 416416.33 | μs |
CPU Memory Usage | 0 | B |
Device Memory Usage | 0 | B |
Self CPU Memory Usage | 0 | B |
Self Device Memory Usage | 0 | B |
cudaLaunchKernel | ||
CPU Time | 2402046.78 | μs |
Device Time | 0.00 | μs |
Self CPU Time | 2402046.78 | μs |
Self Device Time | 0.00 | μs |
CPU Memory Usage | 0 | B |
Device Memory Usage | 0 | B |
Self CPU Memory Usage | 0 | B |
Self Device Memory Usage | 0 | B |
void at::native::vectorized_elementwise_kernel<4, at::native::FillFunctor<int>, at::detail::Array<char*, 1> >(int, at::native::FillFunctor<int>, at::detail::Array<char*, 1>) | ||
CPU Time | 0.00 | μs |
Device Time | 2207427.58 | μs |
Self CPU Time | 0.00 | μs |
Self Device Time | 2207427.58 | μs |
CPU Memory Usage | 0 | B |
Device Memory Usage | 0 | B |
Self CPU Memory Usage | 0 | B |
Self Device Memory Usage | 0 | B |