main
cuh 195 lines 6.89 KB
Raw
1 #pragma once
2 #include "vectorization.cuh"
3
4 namespace vllm {
5
6 template <int VEC_SIZE, typename InT, typename OutT, typename ScaOp>
7 struct DefaultVecOp {
8 ScaOp scalar_op;
9
10 __device__ __forceinline__ void operator()(
11 vec_n_t<OutT, VEC_SIZE>& dst, const vec_n_t<InT, VEC_SIZE>& src) const {
12 #pragma unroll
13 for (int i = 0; i < VEC_SIZE; ++i) {
14 scalar_op(dst.val[i], src.val[i]);
15 }
16 }
17 };
18
19 template <int VEC_SIZE, typename InT, typename OutT, typename VecOp,
20 typename ScaOp>
21 __device__ inline void vectorize_with_alignment(
22 const InT* in, OutT* out, int len, int tid, int stride,
23 VecOp&& vec_op, // vec_n_t<InT,16> -> vec_n_t<OutT,16>
24 ScaOp&& scalar_op) { // InT -> OutT
25 static_assert(VEC_SIZE > 0 && (VEC_SIZE & (VEC_SIZE - 1)) == 0,
26 "VEC_SIZE must be a positive power-of-two");
27 constexpr int WIDTH = VEC_SIZE * sizeof(InT); // eg: 16 B
28 constexpr int OUT_WIDTH = VEC_SIZE * sizeof(OutT); // eg: 16 B
29 uintptr_t addr = reinterpret_cast<uintptr_t>(in);
30 uintptr_t out_addr = reinterpret_cast<uintptr_t>(out);
31
32 // fast path when input and output are both fully aligned. The vector
33 // load/store below go through vec_n_t<T, VEC_SIZE>, declared
34 // __align__(VEC_SIZE * sizeof(T)), so each side must be aligned to its
35 // own vector width. out is NOT generally co-aligned with in: e.g.
36 // reshape_and_cache_flash writes KV-cache rows whose byte offset is a
37 // multiple of head_size, which for head sizes that are not a multiple
38 // of VEC_SIZE puts some rows off the vector-width boundary.
39 bool can_vec = ((addr & (WIDTH - 1)) == 0) &&
40 ((out_addr & (OUT_WIDTH - 1)) == 0) &&
41 ((len & (VEC_SIZE - 1)) == 0);
42 if (can_vec) {
43 int num_vec = len / VEC_SIZE;
44
45 using vin_t = vec_n_t<InT, VEC_SIZE>;
46 using vout_t = vec_n_t<OutT, VEC_SIZE>;
47 auto* v_in = reinterpret_cast<const vin_t*>(in);
48 auto* v_out = reinterpret_cast<vout_t*>(out);
49
50 for (int i = tid; i < num_vec; i += stride) {
51 vout_t tmp;
52 // Make a local copy of the entire pack
53 vin_t src = v_in[i]; // <- encourages a single vector ld
54 vec_op(tmp, src);
55 v_out[i] = tmp; // <- encourages a single vector st
56 }
57 return;
58 }
59
60 int misalignment_offset = addr & (WIDTH - 1); // addr % 64
61 int alignment_bytes = WIDTH - misalignment_offset; // 64 - (addr % 64)
62 int prefix_elems = alignment_bytes & (WIDTH - 1); // handle 64
63 prefix_elems /= sizeof(InT);
64 prefix_elems = min(prefix_elems, len); // 0 ≤ prefix < 16
65
66 // the prefix below aligns in; if that does not also align out (their
67 // addresses differ modulo the vector width), vectorizing is impossible
68 // and the whole copy must stay scalar.
69 if (((out_addr + prefix_elems * sizeof(OutT)) & (OUT_WIDTH - 1)) != 0) {
70 for (int i = tid; i < len; i += stride) {
71 scalar_op(out[i], in[i]);
72 }
73 return;
74 }
75
76 // 1. prefill the when it is unsafe to vectorize
77 for (int i = tid; i < prefix_elems; i += stride) {
78 scalar_op(out[i], in[i]);
79 }
80
81 in += prefix_elems;
82 out += prefix_elems;
83 len -= prefix_elems;
84
85 int num_vec = len / VEC_SIZE;
86 using vin_t = vec_n_t<InT, VEC_SIZE>;
87 using vout_t = vec_n_t<OutT, VEC_SIZE>;
88 auto* v_in = reinterpret_cast<const vin_t*>(in);
89 auto* v_out = reinterpret_cast<vout_t*>(out);
90
91 // 2. vectorize the main part
92 for (int i = tid; i < num_vec; i += stride) {
93 vout_t tmp;
94 // Make a local copy of the entire pack
95 vin_t src = v_in[i]; // <- encourages a single vector ld
96 vec_op(tmp, src);
97 v_out[i] = tmp; // <- encourages a single vector st
98 }
99
100 // 3. handle the tail
101 int tail_start = num_vec * VEC_SIZE;
102 for (int i = tid + tail_start; i < len; i += stride) {
103 scalar_op(out[i], in[i]);
104 }
105 }
106
107 template <int VEC_SIZE, typename InT, typename OutT, typename ScaOp>
108 __device__ __forceinline__ void vectorize_with_alignment(const InT* in,
109 OutT* out, int len,
110 int tid, int stride,
111 ScaOp&& scalar_op) {
112 using Vec = DefaultVecOp<VEC_SIZE, InT, OutT, std::decay_t<ScaOp>>;
113 vectorize_with_alignment<VEC_SIZE>(in, out, len, tid, stride, Vec{scalar_op},
114 std::forward<ScaOp>(scalar_op));
115 }
116
117 template <int VEC_SIZE, typename InT, typename ScaOp>
118 struct DefaultReadVecOp {
119 ScaOp scalar_op;
120
121 __device__ __forceinline__ void operator()(
122 const vec_n_t<InT, VEC_SIZE>& src) const {
123 #pragma unroll
124 for (int i = 0; i < VEC_SIZE; ++i) {
125 scalar_op(src.val[i]);
126 }
127 }
128 };
129
130 // read-only version: iterate over the input with alignment guarantees
131 template <int VEC_SIZE, typename InT, typename VecOp, typename ScaOp>
132 __device__ inline void vectorize_read_with_alignment(const InT* in, int len,
133 int tid, int stride,
134 VecOp&& vec_op,
135 ScaOp&& scalar_op) {
136 static_assert(VEC_SIZE > 0 && (VEC_SIZE & (VEC_SIZE - 1)) == 0,
137 "VEC_SIZE must be a positive power-of-two");
138 constexpr int WIDTH = VEC_SIZE * sizeof(InT);
139 uintptr_t addr = reinterpret_cast<uintptr_t>(in);
140
141 // fast path when the whole region is already aligned
142 bool can_vec = ((addr & (WIDTH - 1)) == 0) && ((len & (VEC_SIZE - 1)) == 0);
143 if (can_vec) {
144 int num_vec = len / VEC_SIZE;
145
146 using vin_t = vec_n_t<InT, VEC_SIZE>;
147 auto* v_in = reinterpret_cast<const vin_t*>(in);
148
149 for (int i = tid; i < num_vec; i += stride) {
150 vin_t tmp = v_in[i];
151 vec_op(tmp);
152 }
153 return;
154 }
155
156 int misalignment_offset = addr & (WIDTH - 1);
157 int alignment_bytes = WIDTH - misalignment_offset;
158 int prefix_elems = alignment_bytes & (WIDTH - 1);
159 prefix_elems /= sizeof(InT);
160 prefix_elems = min(prefix_elems, len);
161
162 // 1. handle the possibly unaligned prefix with scalar access.
163 for (int i = tid; i < prefix_elems; i += stride) {
164 scalar_op(in[i]);
165 }
166
167 in += prefix_elems;
168 len -= prefix_elems;
169
170 int num_vec = len / VEC_SIZE;
171 using vin_t = vec_n_t<InT, VEC_SIZE>;
172 auto* v_in = reinterpret_cast<const vin_t*>(in);
173
174 // 2. vectorized traversal of the main aligned region.
175 for (int i = tid; i < num_vec; i += stride) {
176 vec_op(v_in[i]);
177 }
178
179 // 3. handle remaining tail elements.
180 int tail_start = num_vec * VEC_SIZE;
181 for (int i = tid + tail_start; i < len; i += stride) {
182 scalar_op(in[i]);
183 }
184 }
185
186 // overload that requires only a scalar_op
187 template <int VEC_SIZE, typename InT, typename ScaOp>
188 __device__ __forceinline__ void vectorize_read_with_alignment(
189 const InT* in, int len, int tid, int stride, ScaOp&& scalar_op) {
190 using Vec = DefaultReadVecOp<VEC_SIZE, InT, std::decay_t<ScaOp>>;
191 vectorize_read_with_alignment<VEC_SIZE>(in, len, tid, stride, Vec{scalar_op},
192 std::forward<ScaOp>(scalar_op));
193 }
194
195 } // namespace vllm