/home/liu/actions-runner/_work/ccv/ccv/lib/nnc/cmd/scaled_dot_product_attention/ccv_nnc_scaled_dot_product_attention_cpu_ref.c
Line | Count | Source |
1 | | #include "ccv.h" |
2 | | #include "ccv_internal.h" |
3 | | #include "nnc/ccv_nnc.h" |
4 | | #include "nnc/ccv_nnc_easy.h" |
5 | | #include "nnc/ccv_nnc_internal.h" |
6 | | #ifdef USE_OPENMP |
7 | | #include <omp.h> |
8 | | #endif |
9 | | #ifdef USE_DISPATCH |
10 | | #include <dispatch/dispatch.h> |
11 | | #endif |
12 | | |
13 | | // Shared methods. |
14 | | #include "../_ccv_nnc_cpu_ref.h" |
15 | | |
16 | | static int _ccv_nnc_scaled_dot_product_attention_forw(const ccv_nnc_cmd_t cmd, const ccv_nnc_hint_t hint, const int flags, ccv_nnc_tensor_t* const* const inputs, const int input_size, ccv_nnc_tensor_t* const* const outputs, const int output_size, ccv_nnc_stream_context_t* const stream_context) |
17 | 11 | { |
18 | 11 | assert(input_size >= 3); |
19 | 11 | assert(output_size >= 1); |
20 | 11 | const int is_varlen = cmd.info.scaled_dot_product_attention.is_varlen; |
21 | 11 | ccv_nnc_tensor_view_t* const q = (ccv_nnc_tensor_view_t*)inputs[0]; |
22 | 11 | ccv_nnc_tensor_view_t* const k = (ccv_nnc_tensor_view_t*)inputs[1]; |
23 | 11 | ccv_nnc_tensor_view_t* const v = (ccv_nnc_tensor_view_t*)inputs[2]; |
24 | 11 | ccv_nnc_tensor_view_t* const attn_mask = input_size > 3 ? (ccv_nnc_tensor_view_t*)inputs[3]8 : 03 ; |
25 | 11 | ccv_nnc_tensor_view_t* const w = input_size > 4 ? (ccv_nnc_tensor_view_t*)inputs[4]7 : 04 ; |
26 | 11 | ccv_nnc_tensor_view_t* const bias = input_size > 5 ? (ccv_nnc_tensor_view_t*)inputs[5]7 : 04 ; |
27 | 11 | ccv_nnc_tensor_view_t* const q_seq_offsets = is_varlen && input_size > 62 ? (ccv_nnc_tensor_view_t*)inputs[6]2 : 09 ; |
28 | 11 | ccv_nnc_tensor_view_t* const kv_seq_offsets = is_varlen && input_size > 72 ? (ccv_nnc_tensor_view_t*)inputs[7]2 : 09 ; |
29 | 11 | if (bias) // bias always requires a weight matrix. |
30 | 3 | { assert(w); } |
31 | 11 | if (is_varlen && (2 attn_mask2 || w2 || bias2 || !q_seq_offsets2 || !kv_seq_offsets2 )) |
32 | 0 | return CCV_NNC_EXEC_INVALID; |
33 | 11 | ccv_nnc_tensor_view_t* const c = (w) ? (ccv_nnc_tensor_view_t*)outputs[2]3 : (ccv_nnc_tensor_view_t*)outputs[0]8 ; |
34 | 11 | const int q_nd = ccv_nnc_tensor_nd(q->info.dim); |
35 | 11 | assert(q_nd == 3 || q_nd == 4); |
36 | 11 | const int k_nd = ccv_nnc_tensor_nd(k->info.dim); |
37 | 11 | assert(k_nd == 3 || k_nd == 4); |
38 | 11 | const int v_nd = ccv_nnc_tensor_nd(v->info.dim); |
39 | 11 | assert(v_nd == 3 || v_nd == 4); |
40 | 11 | const int c_nd = ccv_nnc_tensor_nd(c->info.dim); |
41 | 11 | assert(c_nd == 3 || c_nd == 4); |
42 | 11 | assert(q_nd == k_nd && k_nd == v_nd && v_nd == c_nd); |
43 | 11 | if (is_varlen && q_nd != 42 ) |
44 | 0 | return CCV_NNC_EXEC_INVALID; |
45 | | // Assuming this is float 32. |
46 | 11 | int qdim[CCV_NNC_MAX_DIM_ALLOC]; |
47 | 11 | int kdim[CCV_NNC_MAX_DIM_ALLOC]; |
48 | 11 | int vdim[CCV_NNC_MAX_DIM_ALLOC]; |
49 | 11 | int cdim[CCV_NNC_MAX_DIM_ALLOC]; |
50 | 11 | int amdim[CCV_NNC_MAX_DIM_ALLOC]; |
51 | 11 | ccv_nnc_tensor_view_get_dim(q, qdim); |
52 | 11 | ccv_nnc_tensor_view_get_dim(k, kdim); |
53 | 11 | ccv_nnc_tensor_view_get_dim(v, vdim); |
54 | 11 | ccv_nnc_tensor_view_get_dim(c, cdim); |
55 | 11 | if (is_varlen) |
56 | 2 | { |
57 | 2 | assert(q_seq_offsets->info.datatype == CCV_32S); |
58 | 2 | assert(kv_seq_offsets->info.datatype == CCV_32S); |
59 | 2 | assert(CCV_IS_TENSOR_CONTIGUOUS(q_seq_offsets)); |
60 | 2 | assert(CCV_IS_TENSOR_CONTIGUOUS(kv_seq_offsets)); |
61 | 2 | } |
62 | 11 | if (q_nd == 3) |
63 | 0 | { |
64 | 0 | qdim[0] = qdim[1], qdim[1] = qdim[2], qdim[2] = 1; |
65 | 0 | kdim[0] = kdim[1], kdim[1] = kdim[2], kdim[2] = 1; |
66 | 0 | vdim[0] = vdim[1], vdim[1] = vdim[2], vdim[2] = 1; |
67 | 0 | cdim[0] = cdim[1], cdim[1] = cdim[2], cdim[2] = 1; |
68 | 0 | } |
69 | 11 | assert(qdim[0] == kdim[0] && kdim[0] == vdim[0] && vdim[0] == cdim[0]); |
70 | 11 | assert(qdim[2] == cdim[2]); |
71 | 11 | assert(kdim[2] == vdim[2]); |
72 | 11 | assert(qdim[2] % kdim[2] == 0); |
73 | 11 | assert(qdim[2] >= kdim[2]); |
74 | 11 | assert(qdim[3] == kdim[3]); |
75 | 11 | assert(kdim[1] == vdim[1]); |
76 | 11 | assert(cdim[1] == qdim[1]); |
77 | 11 | assert(cdim[3] == vdim[3]); |
78 | 11 | assert(CCV_NNC_MAX_DIM == 2); // Need to change this logic for CCV_NNC_MAX_DIM == other number. |
79 | 11 | int qstride[CCV_NNC_MAX_DIM_ALLOC]; |
80 | 11 | int kstride[CCV_NNC_MAX_DIM_ALLOC]; |
81 | 11 | int vstride[CCV_NNC_MAX_DIM_ALLOC]; |
82 | 11 | int cstride[CCV_NNC_MAX_DIM_ALLOC]; |
83 | 11 | int amstride[CCV_NNC_MAX_DIM_ALLOC]; |
84 | 11 | ccv_nnc_tensor_view_get_stride(q, qstride); |
85 | 11 | ccv_nnc_tensor_view_get_stride(k, kstride); |
86 | 11 | ccv_nnc_tensor_view_get_stride(v, vstride); |
87 | 11 | ccv_nnc_tensor_view_get_stride(c, cstride); |
88 | 11 | if (q_nd == 3) |
89 | 0 | { |
90 | 0 | qstride[0] = qstride[1], qstride[1] = qstride[2], qstride[2] = qstride[3]; |
91 | 0 | kstride[0] = kstride[1], kstride[1] = kstride[2], kstride[2] = kstride[3]; |
92 | 0 | vstride[0] = vstride[1], vstride[1] = vstride[2], vstride[2] = vstride[3]; |
93 | 0 | cstride[0] = cstride[1], cstride[1] = cstride[2], cstride[2] = cstride[3]; |
94 | 0 | } |
95 | 11 | if (attn_mask) |
96 | 2 | { |
97 | 2 | ccv_nnc_tensor_view_get_dim(attn_mask, amdim); |
98 | 2 | ccv_nnc_tensor_view_get_stride(attn_mask, amstride); |
99 | 2 | assert(amdim[0] == qdim[0] || amdim[0] == 1); |
100 | 2 | assert(amdim[1] == qdim[2] || amdim[1] == 1); |
101 | 2 | assert(amdim[2] == qdim[1]); |
102 | 2 | assert(amdim[3] == kdim[1]); |
103 | 2 | } |
104 | 11 | int i[CCV_NNC_MAX_DIM + 2]; |
105 | 11 | float* qk = ccv_nnc_stream_context_get_workspace(stream_context, sizeof(float) * qdim[1] * kdim[1], CCV_TENSOR_CPU_MEMORY); |
106 | 11 | const float* const qp = q->data.f32; |
107 | 11 | const float* const kp = k->data.f32; |
108 | 11 | const float* const vp = v->data.f32; |
109 | 11 | const float* const amp = attn_mask ? attn_mask->data.f322 : 09 ; |
110 | 11 | float* const cp = c->data.f32; |
111 | 11 | const float scale = cmd.info.scaled_dot_product_attention.scale; |
112 | 11 | const int is_causal = cmd.info.scaled_dot_product_attention.is_causal; |
113 | 11 | const int h_h_k_ratio = qdim[2] / kdim[2]; |
114 | 11 | assert(kdim[2] == vdim[2]); |
115 | 11 | assert(qdim[2] >= kdim[2]); |
116 | 11 | assert(qdim[2] % kdim[2] == 0); |
117 | 11 | if (is_varlen) |
118 | 2 | { |
119 | 2 | const int batch_size = ccv_nnc_tensor_count(q_seq_offsets->info) - 1; |
120 | 2 | assert(batch_size > 0); |
121 | 2 | assert(ccv_nnc_tensor_count(kv_seq_offsets->info) == batch_size + 1); |
122 | 2 | assert(qdim[0] == 1); |
123 | 2 | assert(kdim[0] == 1); |
124 | 2 | assert(vdim[0] == 1); |
125 | 2 | assert(cdim[0] == 1); |
126 | 2 | assert(cdim[1] == qdim[1]); |
127 | 2 | assert(cdim[2] == qdim[2]); |
128 | 2 | assert(cdim[3] == vdim[3]); |
129 | 2 | assert(kdim[1] == vdim[1]); |
130 | 2 | const int* const q_offset = q_seq_offsets->data.i32; |
131 | 2 | const int* const kv_offset = kv_seq_offsets->data.i32; |
132 | 2 | assert(q_offset[0] == 0); |
133 | 2 | assert(kv_offset[0] == 0); |
134 | 2 | assert(q_offset[batch_size] == qdim[1]); |
135 | 2 | assert(kv_offset[batch_size] == kdim[1]); |
136 | 10 | for (i[0] = 0; 2 i[0] < batch_size; i[0]++8 ) |
137 | 8 | { |
138 | 8 | const int q_start = q_offset[i[0]]; |
139 | 8 | const int q_end = q_offset[i[0] + 1]; |
140 | 8 | const int k_start = kv_offset[i[0]]; |
141 | 8 | const int k_end = kv_offset[i[0] + 1]; |
142 | 8 | assert(q_start <= q_end); |
143 | 8 | assert(k_start <= k_end); |
144 | 8 | const int R = q_end - q_start; |
145 | 8 | const int K = k_end - k_start; |
146 | 8 | assert(R > 0); |
147 | 8 | assert(K > 0); |
148 | 8 | assert(R <= cmd.info.scaled_dot_product_attention.max_seqlen_q); |
149 | 8 | assert(K <= cmd.info.scaled_dot_product_attention.max_seqlen_kv); |
150 | 8 | const float* const qp0 = qp + q_start * qstride[1]; |
151 | 8 | const float* const kp0 = kp + k_start * kstride[1]; |
152 | 8 | const float* const vp0 = vp + k_start * vstride[1]; |
153 | 8 | float* const cp0 = cp + q_start * cstride[1]; |
154 | 40 | for (i[1] = 0; i[1] < qdim[2]; i[1]++32 ) |
155 | 32 | { |
156 | 32 | const float* const qp1 = qp0 + i[1] * qstride[2]; |
157 | 32 | const float* const kp1 = kp0 + (i[1] / h_h_k_ratio) * kstride[2]; |
158 | 32 | const float* const vp1 = vp0 + (i[1] / h_h_k_ratio) * vstride[2]; |
159 | 32 | float* const cp1 = cp0 + i[1] * cstride[2]; |
160 | 136 | parallel_for32 (x, R) { |
161 | 136 | int y, k; |
162 | 136 | const float* const qp2 = qp1 + x * qstride[1]; |
163 | 136 | float* const cp2 = cp1 + x * cstride[1]; |
164 | 136 | float* const qk0 = qk + x * K; |
165 | 808 | for (y = 0; y < K; y++672 ) |
166 | 672 | { |
167 | 672 | const float* const kp2 = kp1 + y * kstride[1]; |
168 | 672 | float v = 0; |
169 | 43.6k | for (k = 0; k < qdim[3]; k++43.0k ) |
170 | 43.0k | v += qp2[k * qstride[3]] * kp2[k * kstride[3]]; |
171 | 672 | qk0[y] = scale * v; |
172 | 672 | } |
173 | 136 | if (is_causal) |
174 | 136 | { |
175 | 136 | const int x_end = ccv_max(x - R + K + 1, 0); |
176 | 368 | for (y = x_end; y < K; y++232 ) |
177 | 232 | qk0[y] = 0; |
178 | 136 | double maxval = qk0[0]; |
179 | 472 | for (y = 1; y < x_end; y++336 ) |
180 | 336 | if (qk0[y] > maxval) |
181 | 118 | maxval = qk0[y]; |
182 | 136 | double sumval = 0; |
183 | 576 | for (y = 0; y < x_end; y++440 ) |
184 | 440 | sumval += (qk0[y] = expf(qk0[y] - maxval)); |
185 | 136 | sumval = 1.0 / sumval; |
186 | 576 | for (y = 0; y < x_end; y++440 ) |
187 | 440 | qk0[y] *= sumval; |
188 | 136 | } else { |
189 | 0 | double maxval = qk0[0]; |
190 | 0 | for (y = 1; y < K; y++) |
191 | 0 | if (qk0[y] > maxval) |
192 | 0 | maxval = qk0[y]; |
193 | 0 | double sumval = 0; |
194 | 0 | for (y = 0; y < K; y++) |
195 | 0 | sumval += (qk0[y] = expf(qk0[y] - maxval)); |
196 | 0 | sumval = 1.0 / sumval; |
197 | 0 | for (y = 0; y < K; y++) |
198 | 0 | qk0[y] *= sumval; |
199 | 0 | } |
200 | 8.84k | for (k = 0; k < vdim[3]; k++8.70k ) |
201 | 8.70k | cp2[k * cstride[3]] = 0; |
202 | 808 | for (y = 0; y < K; y++672 ) |
203 | 672 | { |
204 | 672 | const float* const vp2 = vp1 + y * vstride[1]; |
205 | 672 | const float v = qk0[y]; |
206 | 43.6k | for (k = 0; k < vdim[3]; k++43.0k ) |
207 | 43.0k | cp2[k * cstride[3]] += v * vp2[k * vstride[3]]; |
208 | 672 | } |
209 | 136 | } parallel_endfor |
210 | 32 | } |
211 | 8 | } |
212 | 2 | return CCV_NNC_EXEC_SUCCESS; |
213 | 2 | } |
214 | 297 | for (i[0] = 0; 9 i[0] < qdim[0]; i[0]++288 ) |
215 | 288 | { |
216 | 288 | const float* const qp0 = qp + i[0] * qstride[0]; |
217 | 288 | const float* const kp0 = kp + i[0] * kstride[0]; |
218 | 288 | const float* const vp0 = vp + i[0] * vstride[0]; |
219 | 288 | const float* const amp0 = amp && amdim[0] > 164 ? amp + i[0] * amstride[0]0 : amp; |
220 | 288 | float* const cp0 = cp + i[0] * cstride[0]; |
221 | 2.59k | for (i[1] = 0; i[1] < qdim[2]; i[1]++2.30k ) |
222 | 2.30k | { |
223 | 2.30k | const float* const qp1 = qp0 + i[1] * qstride[2]; |
224 | 2.30k | const float* const kp1 = kp0 + (i[1] / h_h_k_ratio) * kstride[2]; |
225 | 2.30k | const float* const vp1 = vp0 + (i[1] / h_h_k_ratio) * vstride[2]; |
226 | 2.30k | const float* const amp1 = amp && amdim[1] > 1512 ? amp0 + i[1] * amstride[1]0 : amp0; |
227 | 2.30k | float* const cp1 = cp0 + i[1] * cstride[2]; |
228 | | // Compute Q @ K^T |
229 | 294k | parallel_for2.30k (x, qdim[1]) { |
230 | 294k | int y, k; |
231 | 294k | const float* const qp2 = qp1 + x * qstride[1]; |
232 | 294k | float* const cp2 = cp1 + x * cstride[1]; |
233 | 294k | float* const qk0 = qk + x * kdim[1]; |
234 | 294k | const float* const amp2 = amp1 ? amp1 + x * amstride[2]65.5k : 0229k ; |
235 | 294k | if (attn_mask) |
236 | 65.5k | { |
237 | 8.45M | for (y = 0; y < kdim[1]; y++8.38M ) |
238 | 8.38M | { |
239 | 8.38M | const float* const kp2 = kp1 + y * kstride[1]; |
240 | 8.38M | float v = 0; |
241 | 545M | for (k = 0; k < qdim[3]; k++536M ) |
242 | 536M | v += qp2[k * qstride[3]] * kp2[k * kstride[3]]; |
243 | 8.38M | qk0[y] = scale * v + amp2[y * amstride[3]]; |
244 | 8.38M | } |
245 | 229k | } else { |
246 | 29.5M | for (y = 0; y < kdim[1]; y++29.3M ) |
247 | 29.3M | { |
248 | 29.3M | const float* const kp2 = kp1 + y * kstride[1]; |
249 | 29.3M | float v = 0; |
250 | 1.90G | for (k = 0; k < qdim[3]; k++1.87G ) |
251 | 1.87G | v += qp2[k * qstride[3]] * kp2[k * kstride[3]]; |
252 | 29.3M | qk0[y] = scale * v; |
253 | 29.3M | } |
254 | 229k | } |
255 | | // Compute softmax on qk. |
256 | 294k | if (is_causal) |
257 | 0 | { |
258 | 0 | const int x_end = ccv_max(x - qdim[1] + kdim[1] + 1, 0); |
259 | 0 | for (y = x_end; y < kdim[1]; y++) |
260 | 0 | qk0[y] = 0; |
261 | 0 | double maxval = qk0[0]; |
262 | 0 | for (y = 1; y < x_end; y++) |
263 | 0 | if (qk0[y] > maxval) |
264 | 0 | maxval = qk0[y]; |
265 | 0 | double sumval = 0; |
266 | 0 | for (y = 0; y < x_end; y++) |
267 | 0 | sumval += (qk0[y] = expf(qk0[y] - maxval)); |
268 | 0 | sumval = 1.0 / sumval; |
269 | 0 | for (y = 0; y < x_end; y++) |
270 | 0 | qk0[y] *= sumval; |
271 | 294k | } else { |
272 | 294k | double maxval = qk0[0]; |
273 | 37.7M | for (y = 1; y < kdim[1]; y++37.4M ) |
274 | 37.4M | if (qk0[y] > maxval) |
275 | 1.25M | maxval = qk0[y]; |
276 | 294k | double sumval = 0; |
277 | 38.0M | for (y = 0; y < kdim[1]; y++37.7M ) |
278 | 37.7M | sumval += (qk0[y] = expf(qk0[y] - maxval)); |
279 | 294k | sumval = 1.0 / sumval; |
280 | 38.0M | for (y = 0; y < kdim[1]; y++37.7M ) |
281 | 37.7M | qk0[y] *= sumval; |
282 | 294k | } |
283 | 28.6M | for (k = 0; k < vdim[3]; k++28.3M ) |
284 | 28.3M | cp2[k * cstride[3]] = 0; |
285 | 38.0M | for (y = 0; y < kdim[1]; y++37.7M ) |
286 | 37.7M | { |
287 | 37.7M | const float* const vp2 = vp1 + y * vstride[1]; |
288 | 37.7M | const float v = qk0[y]; |
289 | 3.66G | for (k = 0; k < vdim[3]; k++3.62G ) |
290 | 3.62G | cp2[k * cstride[3]] += v * vp2[k * vstride[3]]; |
291 | 37.7M | } |
292 | 294k | } parallel_endfor |
293 | 2.30k | } |
294 | 288 | } |
295 | 9 | if (w) |
296 | 3 | { |
297 | 3 | const int num_heads = cdim[2]; |
298 | 3 | ccv_nnc_tensor_view_t* const d = (ccv_nnc_tensor_view_t*)outputs[0]; |
299 | 3 | const int w_nd = ccv_nnc_tensor_nd(w->info.dim); |
300 | 3 | assert(w_nd == 2); |
301 | 3 | assert(CCV_IS_TENSOR_CONTIGUOUS(w)); |
302 | 3 | const int d_nd = ccv_nnc_tensor_nd(d->info.dim); |
303 | 3 | assert(d_nd == 3); |
304 | 3 | int ddim[CCV_NNC_MAX_DIM_ALLOC]; |
305 | 3 | int dstride[CCV_NNC_MAX_DIM_ALLOC]; |
306 | 3 | ccv_nnc_tensor_view_get_dim(d, ddim); |
307 | 3 | ccv_nnc_tensor_view_get_stride(d, dstride); |
308 | 3 | assert(ddim[2] == cdim[1]); |
309 | 3 | assert(ddim[3] == num_heads * cdim[3]); |
310 | 3 | assert(w->info.dim[1] == ddim[3]); |
311 | 3 | assert(w->info.dim[0] == ddim[3]); |
312 | 3 | float* const dp = d->data.f32; |
313 | 3 | const float* const wp = w->data.f32; |
314 | 3 | const float* const cp = c->data.f32; |
315 | 3 | if (bias) |
316 | 3 | { |
317 | 3 | assert(ccv_nnc_tensor_count(bias->info) == ddim[3]); |
318 | 3 | assert(CCV_IS_TENSOR_CONTIGUOUS(bias)); |
319 | 3 | const float* const biasp = bias->data.f32; |
320 | 99 | for (i[0] = 0; i[0] < ddim[1]; i[0]++96 ) |
321 | 96 | { |
322 | 96 | const float* const cp0 = cp + i[0] * cstride[0]; |
323 | 96 | float* const dp0 = dp + i[0] * dstride[1]; |
324 | 12.2k | parallel_for96 (y, ddim[2]) { |
325 | 12.2k | int x, j, k; |
326 | 12.2k | const float* const cp1 = cp0 + y * cstride[1]; |
327 | 12.2k | float* const dp1 = dp0 + y * dstride[2]; |
328 | 9.44M | for (x = 0; x < ddim[3]; x++9.43M ) |
329 | 9.43M | { |
330 | 9.43M | const float* const wp0 = wp + x * ddim[3]; |
331 | 9.43M | float v = biasp[x]; |
332 | 84.9M | for (j = 0; j < num_heads; j++75.4M ) |
333 | 75.4M | { |
334 | 75.4M | const float* const cp2 = cp1 + j * cstride[2]; |
335 | 7.32G | for (k = 0; k < cdim[3]; k++7.24G ) |
336 | 7.24G | v += wp0[j * cdim[3] + k] * cp2[k * cstride[3]]; |
337 | 75.4M | } |
338 | 9.43M | dp1[x * dstride[3]] = v; |
339 | 9.43M | } |
340 | 12.2k | } parallel_endfor |
341 | 96 | } |
342 | 3 | } else { |
343 | 0 | for (i[0] = 0; i[0] < ddim[1]; i[0]++) |
344 | 0 | { |
345 | 0 | const float* const cp0 = cp + i[0] * cstride[0]; |
346 | 0 | float* const dp0 = dp + i[0] * dstride[1]; |
347 | 0 | parallel_for(y, ddim[2]) { |
348 | 0 | int x, j, k; |
349 | 0 | const float* const cp1 = cp0 + y * cstride[1]; |
350 | 0 | float* const dp1 = dp0 + y * dstride[2]; |
351 | 0 | for (x = 0; x < ddim[3]; x++) |
352 | 0 | { |
353 | 0 | const float* const wp0 = wp + x * ddim[3]; |
354 | 0 | float v = 0; |
355 | 0 | for (j = 0; j < num_heads; j++) |
356 | 0 | { |
357 | 0 | const float* const cp2 = cp1 + j * cstride[2]; |
358 | 0 | for (k = 0; k < cdim[3]; k++) |
359 | 0 | v += wp0[j * cdim[3] + k] * cp2[k * cstride[3]]; |
360 | 0 | } |
361 | 0 | dp1[x * dstride[3]] = v; |
362 | 0 | } |
363 | 0 | } parallel_endfor |
364 | 0 | } |
365 | 0 | } |
366 | 3 | } |
367 | 9 | return CCV_NNC_EXEC_SUCCESS; |
368 | 9 | } |
369 | | |
370 | | static int _ccv_nnc_scaled_dot_product_attention_back(const ccv_nnc_cmd_t cmd, const ccv_nnc_hint_t hint, const int flags, ccv_nnc_tensor_t* const* const inputs, const int input_size, ccv_nnc_tensor_t* const* const outputs, const int output_size, ccv_nnc_stream_context_t* const stream_context) |
371 | 1 | { |
372 | | // Assuming no saved_softmax, we need to recompute from q, k, v. |
373 | | // We cannot do this with masks (yet). |
374 | 1 | assert(input_size >= 6); |
375 | 1 | if (cmd.info.scaled_dot_product_attention.is_varlen) |
376 | 0 | return CCV_NNC_EXEC_INVALID; |
377 | 1 | ccv_nnc_tensor_view_t* const g = (ccv_nnc_tensor_view_t*)inputs[0]; |
378 | 1 | ccv_nnc_tensor_view_t* const q = (ccv_nnc_tensor_view_t*)inputs[3]; |
379 | 1 | ccv_nnc_tensor_view_t* const k = (ccv_nnc_tensor_view_t*)inputs[4]; |
380 | 1 | ccv_nnc_tensor_view_t* const v = (ccv_nnc_tensor_view_t*)inputs[5]; |
381 | 1 | ccv_nnc_tensor_view_t* const dq = (ccv_nnc_tensor_view_t*)outputs[0]; |
382 | 1 | ccv_nnc_tensor_view_t* const dk = (ccv_nnc_tensor_view_t*)outputs[1]; |
383 | 1 | ccv_nnc_tensor_view_t* const dv = (ccv_nnc_tensor_view_t*)outputs[2]; |
384 | 1 | const int q_nd = ccv_nnc_tensor_nd(q->info.dim); |
385 | 1 | assert(q_nd == 3 || q_nd == 4); |
386 | 1 | const int k_nd = ccv_nnc_tensor_nd(k->info.dim); |
387 | 1 | assert(k_nd == 3 || k_nd == 4); |
388 | 1 | const int v_nd = ccv_nnc_tensor_nd(v->info.dim); |
389 | 1 | assert(v_nd == 3 || v_nd == 4); |
390 | 1 | const int g_nd = ccv_nnc_tensor_nd(g->info.dim); |
391 | 1 | assert(g_nd == 3 || g_nd == 4); |
392 | 1 | const int dq_nd = ccv_nnc_tensor_nd(dq->info.dim); |
393 | 1 | assert(dq_nd == 3 || dq_nd == 4); |
394 | 1 | assert(dq_nd == q_nd); |
395 | 1 | const int dk_nd = ccv_nnc_tensor_nd(dk->info.dim); |
396 | 1 | assert(dk_nd == 3 || dk_nd == 4); |
397 | 1 | assert(dk_nd == k_nd); |
398 | 1 | const int dv_nd = ccv_nnc_tensor_nd(dv->info.dim); |
399 | 1 | assert(dv_nd == 3 || dv_nd == 4); |
400 | 1 | assert(dv_nd == v_nd); |
401 | 1 | assert(q_nd == k_nd && k_nd == v_nd && v_nd == g_nd); |
402 | | // Assuming this is float 32. |
403 | 1 | int qdim[CCV_NNC_MAX_DIM_ALLOC]; |
404 | 1 | int kdim[CCV_NNC_MAX_DIM_ALLOC]; |
405 | 1 | int vdim[CCV_NNC_MAX_DIM_ALLOC]; |
406 | 1 | int gdim[CCV_NNC_MAX_DIM_ALLOC]; |
407 | 1 | int dqdim[CCV_NNC_MAX_DIM_ALLOC]; |
408 | 1 | int dkdim[CCV_NNC_MAX_DIM_ALLOC]; |
409 | 1 | int dvdim[CCV_NNC_MAX_DIM_ALLOC]; |
410 | 1 | ccv_nnc_tensor_view_get_dim(q, qdim); |
411 | 1 | ccv_nnc_tensor_view_get_dim(k, kdim); |
412 | 1 | ccv_nnc_tensor_view_get_dim(v, vdim); |
413 | 1 | ccv_nnc_tensor_view_get_dim(g, gdim); |
414 | 1 | ccv_nnc_tensor_view_get_dim(dq, dqdim); |
415 | 1 | ccv_nnc_tensor_view_get_dim(dk, dkdim); |
416 | 1 | ccv_nnc_tensor_view_get_dim(dv, dvdim); |
417 | 1 | if (q_nd == 3) |
418 | 0 | { |
419 | 0 | qdim[0] = qdim[1], qdim[1] = qdim[2], qdim[2] = 1; |
420 | 0 | kdim[0] = kdim[1], kdim[1] = kdim[2], kdim[2] = 1; |
421 | 0 | vdim[0] = vdim[1], vdim[1] = vdim[2], vdim[2] = 1; |
422 | 0 | gdim[0] = gdim[1], gdim[1] = gdim[2], gdim[2] = 1; |
423 | 0 | dqdim[0] = dqdim[1], dqdim[1] = dqdim[2], dqdim[2] = 1; |
424 | 0 | dkdim[0] = dkdim[1], dkdim[1] = dkdim[2], dkdim[2] = 1; |
425 | 0 | dvdim[0] = dvdim[1], dvdim[1] = dvdim[2], dvdim[2] = 1; |
426 | 0 | } |
427 | 1 | assert(qdim[0] == kdim[0] && kdim[0] == vdim[0] && vdim[0] == gdim[0]); |
428 | 1 | assert(qdim[2] == gdim[2]); |
429 | 1 | assert(kdim[2] == vdim[2]); |
430 | 1 | assert(qdim[2] % kdim[2] == 0); |
431 | 1 | assert(qdim[2] >= kdim[2]); |
432 | 1 | assert(qdim[3] == kdim[3]); |
433 | 1 | assert(kdim[1] == vdim[1]); |
434 | 1 | assert(gdim[1] == qdim[1]); |
435 | 1 | assert(gdim[3] == vdim[3]); |
436 | 1 | assert(CCV_NNC_MAX_DIM == 2); // Need to change this logic for CCV_NNC_MAX_DIM == other number. |
437 | 1 | int qstride[CCV_NNC_MAX_DIM_ALLOC]; |
438 | 1 | int kstride[CCV_NNC_MAX_DIM_ALLOC]; |
439 | 1 | int vstride[CCV_NNC_MAX_DIM_ALLOC]; |
440 | 1 | int gstride[CCV_NNC_MAX_DIM_ALLOC]; |
441 | 1 | int dqstride[CCV_NNC_MAX_DIM_ALLOC]; |
442 | 1 | int dkstride[CCV_NNC_MAX_DIM_ALLOC]; |
443 | 1 | int dvstride[CCV_NNC_MAX_DIM_ALLOC]; |
444 | 1 | ccv_nnc_tensor_view_get_stride(q, qstride); |
445 | 1 | ccv_nnc_tensor_view_get_stride(k, kstride); |
446 | 1 | ccv_nnc_tensor_view_get_stride(v, vstride); |
447 | 1 | ccv_nnc_tensor_view_get_stride(g, gstride); |
448 | 1 | ccv_nnc_tensor_view_get_stride(dq, dqstride); |
449 | 1 | ccv_nnc_tensor_view_get_stride(dk, dkstride); |
450 | 1 | ccv_nnc_tensor_view_get_stride(dv, dvstride); |
451 | 1 | if (q_nd == 3) |
452 | 0 | { |
453 | 0 | qstride[0] = qstride[1], qstride[1] = qstride[2], qstride[2] = qstride[3]; |
454 | 0 | kstride[0] = kstride[1], kstride[1] = kstride[2], kstride[2] = kstride[3]; |
455 | 0 | vstride[0] = vstride[1], vstride[1] = vstride[2], vstride[2] = vstride[3]; |
456 | 0 | gstride[0] = gstride[1], gstride[1] = gstride[2], gstride[2] = gstride[3]; |
457 | 0 | dqstride[0] = dqstride[1], dqstride[1] = dqstride[2], dqstride[2] = dqstride[3]; |
458 | 0 | dkstride[0] = dkstride[1], dkstride[1] = dkstride[2], dkstride[2] = dkstride[3]; |
459 | 0 | dvstride[0] = dvstride[1], dvstride[1] = dvstride[2], dvstride[2] = dvstride[3]; |
460 | 0 | } |
461 | 1 | int i[CCV_NNC_MAX_DIM + 2]; |
462 | 1 | float* qk = ccv_nnc_stream_context_get_workspace(stream_context, sizeof(float) * 2 * kdim[1], CCV_TENSOR_CPU_MEMORY); |
463 | 1 | const float* const qp = q->data.f32; |
464 | 1 | const float* const kp = k->data.f32; |
465 | 1 | const float* const vp = v->data.f32; |
466 | 1 | const float* const gp = g->data.f32; |
467 | 1 | float* const dqp = dq->data.f32; |
468 | 1 | float* const dkp = dk->data.f32; |
469 | 1 | float* const dvp = dv->data.f32; |
470 | 1 | const float scale = cmd.info.scaled_dot_product_attention.scale; |
471 | 1 | const int is_causal = cmd.info.scaled_dot_product_attention.is_causal; |
472 | 1 | const int h_h_k_ratio = qdim[2] / kdim[2]; |
473 | 33 | for (i[0] = 0; i[0] < qdim[0]; i[0]++32 ) |
474 | 32 | { |
475 | 32 | const float* const qp0 = qp + i[0] * qstride[0]; |
476 | 32 | const float* const kp0 = kp + i[0] * kstride[0]; |
477 | 32 | const float* const vp0 = vp + i[0] * vstride[0]; |
478 | 32 | const float* const gp0 = gp + i[0] * gstride[0]; |
479 | 32 | float* const dqp0 = dqp + i[0] * dqstride[0]; |
480 | 32 | float* const dkp0 = dkp + i[0] * dkstride[0]; |
481 | 32 | float* const dvp0 = dvp + i[0] * dvstride[0]; |
482 | 288 | for (i[1] = 0; i[1] < qdim[2]; i[1]++256 ) |
483 | 256 | { |
484 | 256 | const float* const qp1 = qp0 + i[1] * qstride[2]; |
485 | 256 | const float* const kp1 = kp0 + (i[1] / h_h_k_ratio) * kstride[2]; |
486 | 256 | const float* const vp1 = vp0 + (i[1] / h_h_k_ratio) * vstride[2]; |
487 | 256 | const float* const gp1 = gp0 + i[1] * gstride[2]; |
488 | 256 | float* const dqp1 = dqp0 + i[1] * dqstride[2]; |
489 | 256 | float* const dkp1 = dkp0 + (i[1] / h_h_k_ratio) * dkstride[2]; |
490 | 256 | float* const dvp1 = dvp0 + (i[1] / h_h_k_ratio) * dvstride[2]; |
491 | | // Compute Q @ K^T |
492 | 256 | int x, y, k; |
493 | 33.0k | for (x = 0; x < qdim[1]; x++32.7k ) |
494 | 32.7k | { |
495 | 32.7k | float* const dqp2 = dqp1 + x * dqstride[1]; |
496 | 2.12M | for (k = 0; k < qdim[3]; k++2.09M ) |
497 | 2.09M | dqp2[k * dqstride[3]] = 0; |
498 | 32.7k | } |
499 | | // Only zero out when it is at 0-index. |
500 | 256 | if (i[1] % h_h_k_ratio == 0) |
501 | 33.0k | for (y = 0; 256 y < kdim[1]; y++32.7k ) |
502 | 32.7k | { |
503 | 32.7k | float* const dkp2 = dkp1 + y * dkstride[1]; |
504 | 2.12M | for (k = 0; k < qdim[3]; k++2.09M ) |
505 | 2.09M | dkp2[k * dkstride[3]] = 0; |
506 | 32.7k | } |
507 | | // Only zero out when it is at 0-index. |
508 | 256 | if (i[1] % h_h_k_ratio == 0) |
509 | 33.0k | for (y = 0; 256 y < kdim[1]; y++32.7k ) |
510 | 32.7k | { |
511 | 32.7k | float* const dvp2 = dvp1 + y * dvstride[1]; |
512 | 3.17M | for (k = 0; k < vdim[3]; k++3.14M ) |
513 | 3.14M | dvp2[k * dvstride[3]] = 0; |
514 | 32.7k | } |
515 | 33.0k | for (x = 0; x < qdim[1]; x++32.7k ) |
516 | 32.7k | { |
517 | 32.7k | const float* const qp2 = qp1 + x * qstride[1]; |
518 | 32.7k | const float* const gp2 = gp1 + x * gstride[1]; |
519 | 32.7k | float* const qk0 = qk; |
520 | 32.7k | float* const qks0 = qk + kdim[1]; |
521 | 4.22M | for (y = 0; y < kdim[1]; y++4.19M ) |
522 | 4.19M | { |
523 | 4.19M | const float* const kp2 = kp1 + y * kstride[1]; |
524 | 4.19M | float v = 0; |
525 | 272M | for (k = 0; k < qdim[3]; k++268M ) |
526 | 268M | v += qp2[k * qstride[3]] * kp2[k * kstride[3]]; |
527 | 4.19M | qk0[y] = scale * v; |
528 | 4.19M | } |
529 | | // Compute softmax on qk. |
530 | 32.7k | if (is_causal) |
531 | 0 | { |
532 | 0 | const int x_end = ccv_max(x - qdim[1] + kdim[1] + 1, 0); |
533 | 0 | for (y = x_end; y < kdim[1]; y++) |
534 | 0 | qk0[y] = 0; |
535 | 0 | double maxval = qk0[0]; |
536 | 0 | for (y = 1; y < x_end; y++) |
537 | 0 | if (qk0[y] > maxval) |
538 | 0 | maxval = qk0[y]; |
539 | 0 | double sumval = 0; |
540 | 0 | for (y = 0; y < x_end; y++) |
541 | 0 | sumval += (qk0[y] = expf(qk0[y] - maxval)); |
542 | 0 | sumval = 1.0 / sumval; |
543 | 0 | for (y = 0; y < x_end; y++) |
544 | 0 | qk0[y] *= sumval; |
545 | 32.7k | } else { |
546 | 32.7k | double maxval = qk0[0]; |
547 | 4.19M | for (y = 1; y < kdim[1]; y++4.16M ) |
548 | 4.16M | if (qk0[y] > maxval) |
549 | 146k | maxval = qk0[y]; |
550 | 32.7k | double sumval = 0; |
551 | 4.22M | for (y = 0; y < kdim[1]; y++4.19M ) |
552 | 4.19M | sumval += (qk0[y] = expf(qk0[y] - maxval)); |
553 | 32.7k | sumval = 1.0 / sumval; |
554 | 4.22M | for (y = 0; y < kdim[1]; y++4.19M ) |
555 | 4.19M | qk0[y] *= sumval; |
556 | 32.7k | } |
557 | 4.22M | for (y = 0; y < kdim[1]; y++4.19M ) |
558 | 4.19M | { |
559 | 4.19M | float* const dvp2 = dvp1 + y * dvstride[1]; |
560 | 4.19M | const float v = qk0[y]; |
561 | 406M | for (k = 0; k < vdim[3]; k++402M ) |
562 | 402M | dvp2[k * dvstride[3]] += v * gp2[k * gstride[3]]; |
563 | 4.19M | } |
564 | 32.7k | double sumval = 0; |
565 | 4.22M | for (y = 0; y < kdim[1]; y++4.19M ) |
566 | 4.19M | { |
567 | 4.19M | const float* const vp2 = vp1 + y * vstride[1]; |
568 | 4.19M | float v = 0; |
569 | 406M | for (k = 0; k < vdim[3]; k++402M ) |
570 | 402M | v += gp2[k * gstride[3]] * vp2[k * vstride[3]]; |
571 | 4.19M | qks0[y] = v; |
572 | 4.19M | sumval += v * qk0[y]; |
573 | 4.19M | } |
574 | 4.22M | for (y = 0; y < kdim[1]; y++4.19M ) |
575 | 4.19M | qk0[y] = (qks0[y] - sumval) * qk0[y]; |
576 | 32.7k | float* const dqp2 = dqp1 + x * dqstride[1]; |
577 | 4.22M | for (y = 0; y < kdim[1]; y++4.19M ) |
578 | 4.19M | { |
579 | 4.19M | const float* const kp2 = kp1 + y * kstride[1]; |
580 | 4.19M | float* const dkp2 = dkp1 + y * dkstride[1]; |
581 | 4.19M | const float v = scale * qk0[y]; |
582 | 272M | for (k = 0; k < qdim[3]; k++268M ) |
583 | 268M | { |
584 | 268M | dqp2[k * dqstride[3]] += v * kp2[k * kstride[3]]; |
585 | 268M | dkp2[k * dkstride[3]] += v * qp2[k * qstride[3]]; |
586 | 268M | } |
587 | 4.19M | } |
588 | 32.7k | } |
589 | 256 | } |
590 | 32 | } |
591 | 1 | return CCV_NNC_EXEC_SUCCESS; |
592 | 1 | } |
593 | | |
594 | | REGISTER_COMMAND_BACKEND(CCV_NNC_SCALED_DOT_PRODUCT_ATTENTION_FORWARD, CCV_NNC_BACKEND_CPU_REF)(ccv_nnc_cmd_backend_registry_t* const registry) |
595 | 1 | { |
596 | 1 | registry->tensor_formats = CCV_TENSOR_FORMAT_NHWC; |
597 | 1 | registry->tensor_datatypes = CCV_32F | CCV_32S; |
598 | 1 | registry->tensor_memory = CCV_TENSOR_CPU_MEMORY; |
599 | 1 | registry->algorithms = 1; |
600 | 1 | registry->exec = _ccv_nnc_scaled_dot_product_attention_forw; |
601 | 1 | } |
602 | | |
603 | | REGISTER_COMMAND_BACKEND(CCV_NNC_SCALED_DOT_PRODUCT_ATTENTION_BACKWARD, CCV_NNC_BACKEND_CPU_REF)(ccv_nnc_cmd_backend_registry_t* const registry) |
604 | 1 | { |
605 | 1 | registry->tensor_formats = CCV_TENSOR_FORMAT_NHWC; |
606 | 1 | registry->tensor_datatypes = CCV_32F; |
607 | 1 | registry->tensor_memory = CCV_TENSOR_CPU_MEMORY; |
608 | 1 | registry->algorithms = 1; |
609 | 1 | registry->exec = _ccv_nnc_scaled_dot_product_attention_back; |
610 | 1 | } |