Skip to content

Commit bea438e

Browse files
committed
Argmax
1 parent 66e4b82 commit bea438e

8 files changed

Lines changed: 218 additions & 24 deletions

File tree

include/tensor/ops.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,6 @@ Tensor<int, D> argmax(const TensorView<T, D>& input, int dim, bool keepdim);
6868
// mutations
6969

7070
template <typename T, typename D>
71-
void replace_from_(Tensor<T, D>& destination, const TensorView<T, D>& source);
71+
void replace_from_(Tensor<T, D>& out, const TensorView<T, D>& input);
7272

7373
} // namespace tensor

src/tensor/cpu/ops.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -545,14 +545,15 @@ void replace_from_(Tensor<T, D>& destination, const TensorView<T, D>& source) {
545545
});
546546
}
547547

548+
// argmax
548549
template Tensor<int, CPU> argmax(const TensorView<bfloat16, CPU>& input, int dim, bool keepdim);
549-
template Tensor<int, CPU> argmax(const TensorView<float, CPU>& input, int dim, bool keepdim);
550-
template Tensor<int, CPU> argmax(const TensorView<int, CPU>& input, int dim, bool keepdim);
550+
// template Tensor<int, CPU> argmax(const TensorView<float, CPU>& input, int dim, bool keepdim);
551+
// template Tensor<int, CPU> argmax(const TensorView<int, CPU>& input, int dim, bool keepdim);
551552

552-
template void replace_from_(Tensor<bfloat16, CPU>& destination,
553-
const TensorView<bfloat16, CPU>& source);
554-
template void replace_from_(Tensor<int, CPU>& destination, const TensorView<int, CPU>& source);
555-
template void replace_from_(Tensor<float, CPU>& destination, const TensorView<float, CPU>& source);
553+
// replace_from_
554+
template void replace_from_(Tensor<bfloat16, CPU>& out, const TensorView<bfloat16, CPU>& input);
555+
template void replace_from_(Tensor<int, CPU>& out, const TensorView<int, CPU>& input);
556+
template void replace_from_(Tensor<float, CPU>& out, const TensorView<float, CPU>& input);
556557

557558
// add
558559
template Tensor<bfloat16, CPU> add(const TensorView<bfloat16, CPU>&,

src/tensor/cuda/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
file(GLOB HEADER_LIST CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/include/tensor/*.hpp")
22

3-
add_library(tensor_cuda STATIC storage.cu loader.cu ops.cu kernels/fill.cu kernels/arange.cu kernels/add.cu kernels/sub.cu kernels/div.cu kernels/mul.cu kernels/sum.cu kernels/max.cu)
3+
add_library(tensor_cuda STATIC storage.cu loader.cu ops.cu kernels/fill.cu kernels/argmax.cu kernels/arange.cu kernels/add.cu kernels/sub.cu kernels/div.cu kernels/mul.cu kernels/sum.cu kernels/max.cu)
44

55
set_target_properties(tensor_cuda PROPERTIES
66
CUDA_SEPARABLE_COMPILATION ON

src/tensor/cuda/kernels/argmax.cu

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#include "argmax.cuh"
2+
#include "utils.cuh"
3+
#include <cstddef>
4+
#include <cuda_bf16.hpp>
5+
#include <limits>
6+
7+
namespace tensor::kernels {
8+
9+
using namespace dtype;
10+
11+
const int blockThreads = 256;
12+
13+
__global__ void argmax_bfloat16_kernel(Cuda<int>* out, Cuda<bfloat16>* input, size_t num_reductions, size_t reduce_size, size_t reduce_stride) {
14+
__shared__ Cuda<bfloat16> shmem_val[blockThreads]; // NOLINT
15+
__shared__ int shmem_idx[blockThreads]; // NOLINT
16+
17+
size_t tid = threadIdx.x;
18+
19+
size_t reduction_idx = blockIdx.x; // which reduction are we doing?
20+
21+
// decompose into outer and inner indices
22+
size_t outer_idx = reduction_idx / reduce_stride;
23+
size_t inner_idx = reduction_idx % reduce_stride;
24+
25+
// base pointer for this reduction
26+
size_t base = (outer_idx * reduce_size * reduce_stride) + inner_idx;
27+
28+
// reduce with a grid stride loop to handle reduce_size > blockThreads
29+
Cuda<bfloat16> thread_max_val = -std::numeric_limits<float>::infinity();
30+
31+
int thread_max_idx = tid; // NOLINT
32+
33+
for (int element = tid; element < reduce_size; element += blockDim.x) { // NOLINT
34+
Cuda<bfloat16> incoming_val = input[base + (element * reduce_stride)];
35+
int incoming_idx = element;
36+
37+
if (incoming_val > thread_max_val) {
38+
thread_max_idx = incoming_idx;
39+
}
40+
41+
thread_max_val = __hmax(thread_max_val, incoming_val);
42+
}
43+
44+
// now we only have to reduce 'blockThreads' elements, which is easy within a block
45+
46+
// load partial argmaxs onto shmem
47+
shmem_val[tid] = thread_max_val;
48+
shmem_idx[tid] = thread_max_idx;
49+
__syncthreads();
50+
51+
// reduce in shared memory
52+
for (int stride = blockDim.x / 2; stride > 32; stride >>= 1) { // NOLINT
53+
if (tid < stride) {
54+
Cuda<bfloat16> existing_val = shmem_val[tid];
55+
Cuda<bfloat16> incoming_val = shmem_val[tid + stride];
56+
int incoming_idx = shmem_idx[tid + stride];
57+
if (incoming_val > existing_val) {
58+
shmem_idx[tid] = incoming_idx;
59+
}
60+
61+
shmem_val[tid] = __hmax(existing_val, incoming_val);
62+
}
63+
__syncthreads();
64+
}
65+
66+
// warp shuffle for the final warp-level reduction
67+
if (tid < 32) {
68+
Cuda<bfloat16> val = shmem_val[tid];
69+
int idx = shmem_idx[tid];
70+
71+
Cuda<bfloat16> incoming_val = shmem_val[tid + 32];
72+
int incoming_idx = shmem_idx[tid + 32];
73+
if (incoming_val > val) {
74+
shmem_idx[tid] = incoming_idx;
75+
}
76+
val = __hmax(val, incoming_val);
77+
78+
for (int offset = 16; offset > 0; offset >>= 1) {
79+
Cuda<bfloat16> incoming_val = __shfl_down_sync(0xffffffff, val, offset);
80+
int incoming_idx = __shfl_down_sync(0xffffffff, idx, offset);
81+
if (incoming_val > val) {
82+
idx = incoming_idx;
83+
}
84+
val = __hmax(val, incoming_val);
85+
}
86+
87+
if (tid == 0) {
88+
out[reduction_idx] = idx;
89+
}
90+
}
91+
}
92+
93+
Tensor<int, CUDA> argmax_bfloat16(const TensorView<bfloat16, CUDA>& input, int dim, bool keepdim) {
94+
assert(input.is_contiguous() && "the tensor should be contiguous");
95+
96+
auto shape = input.shape;
97+
98+
if (dim < 0) {
99+
dim = shape.size() + dim;
100+
}
101+
102+
assert(dim >= 0 && static_cast<size_t>(dim) < shape.size());
103+
104+
size_t outer_size = 1; // how many reductions will we perform? ("batch size")
105+
size_t inner_size = 1; // what's the distance between elements to reduce?
106+
size_t reduce_size = 1; // how many elements each reduction needs to reduce over
107+
108+
bool found_dim = false;
109+
110+
// Output shape
111+
Shape out_shape;
112+
for (size_t i = 0; i < shape.size(); ++i) {
113+
if (i == static_cast<size_t>(dim)) {
114+
if (keepdim) {
115+
out_shape.push_back(1);
116+
}
117+
reduce_size = shape[dim];
118+
found_dim = true;
119+
} else {
120+
if (!found_dim) {
121+
outer_size *= shape[i];
122+
} else {
123+
inner_size *= shape[i];
124+
}
125+
126+
out_shape.push_back(shape[i]);
127+
}
128+
}
129+
130+
if (out_shape.empty()) {
131+
out_shape.push_back(1);
132+
}
133+
134+
auto n_elements = outer_size * inner_size;
135+
136+
auto input_strides = get_all_strides(shape);
137+
138+
TensorStorage<int, CUDA> storage(n_elements);
139+
Tensor<int, CUDA> out{out_shape, std::move(storage)};
140+
141+
int block_size = blockThreads;
142+
143+
// Convert to device-native types for kernel call
144+
auto* out_d = reinterpret_cast<int*>(out.data()); // NOLINT
145+
auto* input_d = reinterpret_cast<Cuda<bfloat16>*>(input.data); // NOLINT
146+
147+
argmax_bfloat16_kernel<<<n_elements, block_size>>>(out_d, input_d, n_elements, reduce_size, inner_size);
148+
149+
return out;
150+
}
151+
152+
} // namespace tensor::kernels

src/tensor/cuda/kernels/argmax.cuh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
3+
#include <cuda_runtime.h>
4+
#include <tensor/device_type.hpp>
5+
#include <tensor/tensor.hpp>
6+
#include <cstddef>
7+
8+
namespace tensor::kernels {
9+
10+
using namespace dtype;
11+
12+
__global__ void argmax_bfloat16_kernel(int* out, Cuda<bfloat16>* input, size_t num_reductions, size_t reduce_size, size_t reduce_stride);
13+
14+
Tensor<int, CUDA> argmax_bfloat16(const TensorView<bfloat16, CUDA>& input, int dim, bool keepdim);
15+
16+
} // namespace tensor::kernels

src/tensor/cuda/ops.cu

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "kernels/mul.cuh"
1313
#include "kernels/sum.cuh"
1414
#include "kernels/max.cuh"
15+
#include "kernels/argmax.cuh"
1516
#include "kernels/utils.cuh"
1617

1718
namespace tensor {
@@ -47,11 +48,15 @@ template Tensor<int, CUDA> arange(int start, int end, int step);
4748
template Tensor<float, CUDA> arange(float start, float end, float step);
4849
template Tensor<bfloat16, CUDA> arange(bfloat16 start, bfloat16 end, bfloat16 step);
4950

50-
template <>
51-
void replace_from_(Tensor<bfloat16, CUDA>& out, const TensorView<bfloat16, CUDA>& in) {
52-
CUDA_CHECK(cudaMemcpy(out.data(), in.data, in.data_size * sizeof(bfloat16), cudaMemcpyDeviceToDevice)); // NOLINT
51+
template <typename T, typename D>
52+
void replace_from_(Tensor<T, D>& out, const TensorView<T, D>& input) {
53+
CUDA_CHECK(cudaMemcpy(out.data(), input.data, input.data_size * sizeof(T), cudaMemcpyDeviceToDevice)); // NOLINT
5354
}
5455

56+
template void replace_from_(Tensor<bfloat16, CUDA>& out, const TensorView<bfloat16, CUDA>& input);
57+
template void replace_from_(Tensor<float, CUDA>& out, const TensorView<float, CUDA>& input);
58+
template void replace_from_(Tensor<int, CUDA>& out, const TensorView<int, CUDA>& input);
59+
5560
template <>
5661
Tensor<bfloat16, CUDA> add(const TensorView<bfloat16, CUDA>& tensor_a, const TensorView<bfloat16, CUDA>& tensor_b) {
5762
return add_bfloat16(tensor_a, tensor_b);
@@ -92,4 +97,9 @@ Tensor<float, CUDA> max(const TensorView<float, CUDA>& input, int dim, bool keep
9297
return max_float(input, dim, keepdim);
9398
}
9499

100+
template <>
101+
Tensor<int, CUDA> argmax(const TensorView<bfloat16, CUDA>& input, int dim, bool keepdim) {
102+
return argmax_bfloat16(input, dim, keepdim);
103+
}
104+
95105
} // namespace tensor

tests/tensor/cpu/test_ops.cpp

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ TEST(TensorCPUTest, Arange) {
1414
tensor_is_close<int>(result.span(), std::span(exp));
1515
}
1616

17-
TEST(TensorCPUTest, AddBF16) {
17+
TEST(TensorCPUTest, AddBf16) {
1818
Tensor<bfloat16, CPU> tensor_a({2, 4});
1919
Tensor<bfloat16, CPU> tensor_b({2, 4});
2020
Tensor<bfloat16, CPU> exp({2, 4});
@@ -53,7 +53,7 @@ TEST(TensorCPUTest, MaxFp32) {
5353
tensor_is_close<float>(result.span(), exp.span());
5454
}
5555

56-
TEST(TensorCPUTest, PowBF16) {
56+
TEST(TensorCPUTest, PowBf16) {
5757
Tensor<bfloat16, CPU> tensor({2, 2});
5858
tensor.fill_(2.0);
5959

@@ -64,7 +64,7 @@ TEST(TensorCPUTest, PowBF16) {
6464
tensor_is_close<bfloat16>(result.span(), std::span(exp));
6565
}
6666

67-
TEST(TensorCPUTest, MatmulBF16) {
67+
TEST(TensorCPUTest, MatmulBf16) {
6868
Tensor<bfloat16, CPU> tensor_a({2, 4});
6969
Tensor<bfloat16, CPU> tensor_b({4, 2});
7070
Tensor<bfloat16, CPU> exp({2, 2});
@@ -81,7 +81,7 @@ TEST(TensorCPUTest, MatmulBF16) {
8181
tensor_is_close<bfloat16>(result.span(), exp.span());
8282
}
8383

84-
TEST(TensorCPUTest, CatF16) {
84+
TEST(TensorCPUTest, CatFp16) {
8585
Tensor<bfloat16, CPU> tensor_a({2, 4});
8686
Tensor<bfloat16, CPU> tensor_b({2, 2});
8787

@@ -102,7 +102,7 @@ TEST(TensorCPUTest, CatF16) {
102102
tensor_is_close<bfloat16>(result.span(), std::span(exp));
103103
}
104104

105-
TEST(TensorCPUTest, SliceF16) {
105+
TEST(TensorCPUTest, SliceFp16) {
106106
Tensor<bfloat16, CPU> tensor({1, 4});
107107
Tensor<bfloat16, CPU> exp({1, 2});
108108

@@ -122,7 +122,7 @@ TEST(TensorCPUTest, SliceF16) {
122122
tensor_is_close<bfloat16>(result.span(), exp.span());
123123
}
124124

125-
TEST(TensorCPUTest, TrilBF16) {
125+
TEST(TensorCPUTest, TrilBf16) {
126126
Tensor<bfloat16, CPU> tensor({4, 4});
127127
tensor.fill_(1.0);
128128

@@ -137,16 +137,14 @@ TEST(TensorCPUTest, TrilBF16) {
137137
tensor_is_close<bfloat16>(diag.span(), std::span(exp));
138138
}
139139

140-
TEST(TensorCPUTest, ArgmaxInt) {
141-
Tensor<int, CPU> tensor({4, 4});
142-
tensor.fill_(1);
143-
tensor.set_(2, 4); // idx 2 of first batch element
144-
tensor.set_(7, 8); // idx 3 of second batch element
140+
TEST(TensorCPUTest, ArgmaxBf16) {
141+
Tensor<bfloat16, CPU> tensor({4, 4});
142+
tensor.fill_(1.0);
143+
tensor.set_(2., 4.0); // idx 2 of first batch element
144+
tensor.set_(7, 8.0); // idx 3 of second batch element
145145

146146
auto maxes = argmax(tensor.view(), -1, true);
147147

148-
fmt::println("MAXES {}", maxes.view());
149-
150148
std::vector<int> exp{2, 3, 0, 0};
151149
tensor_is_close<int>(maxes.span(), std::span(exp));
152150
}

tests/tensor/cuda/test_ops.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@
66

77
using namespace tensor;
88

9+
TEST(TensorCUDATest, ArgmaxBf16) {
10+
SKIP_IF_NO_GPU();
11+
Tensor<bfloat16, CPU> tensor({4, 4});
12+
tensor.fill_(1.0);
13+
tensor.set_(2., 4.0); // idx 2 of first batch element
14+
tensor.set_(7, 8.0); // idx 3 of second batch element
15+
16+
Tensor<bfloat16, CUDA> input = tensor.cuda();
17+
18+
auto maxes = argmax(input.view(), -1, true);
19+
20+
auto cpu = maxes.cpu();
21+
22+
std::vector<int> exp{2, 3, 0, 0};
23+
tensor_is_close<int>(cpu.span(), std::span(exp));
24+
}
25+
926
TEST(TensorCUDATest, Arange) {
1027
SKIP_IF_NO_GPU();
1128
Tensor<int, CUDA> result = arange<int, CUDA>(0, 10, 2);

0 commit comments

Comments
 (0)