← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
vision_kernels.c
Go to the documentation of this file.
1/**
2 * @file vision_kernels.c
3 * @brief Vision kernels (im2patch, patch embedding, etc.)
4 *
5 * CK-ENGINE KERNEL RULES:
6 * =======================
7 * 1. NO malloc/free - memory via bump allocator, pointers passed in
8 * 2. NO OpenMP - parallelization at orchestrator/codegen layer
9 * 3. API must define: inputs, outputs, workspace, and memory layouts
10 * 4. Pure computation - deterministic, no side effects
11 *
12 * After changes: make test && make llamacpp-parity-full
13 */
14
15#include <string.h>
16#include <stddef.h>
17#include <stdint.h>
18#include <math.h>
19
20#include "bf16_utils.h"
21
22#if defined(__clang__)
23#define CK_VISION_NOINLINE __attribute__((noinline))
24#elif defined(__GNUC__)
25#define CK_VISION_NOINLINE __attribute__((noinline))
26#else
27#define CK_VISION_NOINLINE
28#endif
29
30static int tile_order_index_2d(int linear_idx,
31 int grid_h,
32 int grid_w,
33 int merge_size)
34{
35 if (linear_idx < 0 || grid_h <= 0 || grid_w <= 0 || merge_size <= 0) {
36 return 0;
37 }
38
39 int index = 0;
40 for (int y = 0; y < grid_h; y += merge_size) {
41 for (int x = 0; x < grid_w; x += merge_size) {
42 for (int dy = 0; dy < merge_size && y + dy < grid_h; ++dy) {
43 for (int dx = 0; dx < merge_size && x + dx < grid_w; ++dx) {
44 if (index == linear_idx) {
45 return (y + dy) * grid_w + x + dx;
46 }
47 ++index;
48 }
49 }
50 }
51 }
52 return 0;
53}
54
56 int x,
57 int grid_h,
58 int grid_w,
59 int merge_size)
60{
61 const int tile_y = y / merge_size;
62 const int tile_x = x / merge_size;
63 const int tile_height = grid_h - tile_y * merge_size < merge_size
64 ? grid_h - tile_y * merge_size : merge_size;
65 const int tile_width = grid_w - tile_x * merge_size < merge_size
66 ? grid_w - tile_x * merge_size : merge_size;
67 const int rows_before = tile_y * merge_size * grid_w;
68 const int tiles_before = tile_height * tile_x * merge_size;
69 const int within_tile = (y % merge_size) * tile_width + x % merge_size;
70 return rows_before + tiles_before + within_tile;
71}
72
73/**
74 * im2patch: Transforms an image into a sequence of flattened patches.
75 *
76 * Image Layout: [C, H, W] (Row-major: W is fastest moving)
77 * Output Layout: [num_patches, C * P * P]
78 *
79 * num_patches = (H/P) * (W/P)
80 * P = patch_size
81 */
82void im2patch(const float *image,
83 float *patches,
84 int C, int H, int W, int P)
85{
86 int num_patches_h = H / P;
87 int num_patches_w = W / P;
88 int patch_dim = C * P * P;
89
90 // ph, pw: patch grid coordinates
91 for (int ph = 0; ph < num_patches_h; ++ph) {
92 for (int pw = 0; pw < num_patches_w; ++pw) {
93
94 int patch_idx = ph * num_patches_w + pw;
95 float *dst_patch = patches + (size_t)patch_idx * patch_dim;
96
97 // For each patch, grab pixels from all channels
98 for (int c = 0; c < C; ++c) {
99 for (int py = 0; py < P; ++py) {
100 int y = ph * P + py;
101 int x = pw * P;
102
103 // Input row start in the image
104 const float *src_row = image + (size_t)c * H * W + (size_t)y * W + x;
105
106 // Destination row in the flattened patch sequence
107 float *dst_row = dst_patch + (size_t)c * P * P + (size_t)py * P;
108
109 // Copy P pixels (one row of the patch)
110 memcpy(dst_row, src_row, P * sizeof(float));
111 }
112 }
113 }
114 }
115}
116
117/**
118 * patch2im: Accumulates gradients from patches back into the image. (Backward pass)
119 *
120 * d_patches: [num_patches, C * P * P]
121 * d_image: [C, H, W] (Accumulated)
122 */
123void patch2im(const float *d_patches,
124 float *d_image,
125 int C, int H, int W, int P)
126{
127 int num_patches_h = H / P;
128 int num_patches_w = W / P;
129 int patch_dim = C * P * P;
130
131 // Zero out the image first as we are accumulating gradients
132 memset(d_image, 0, (size_t)C * H * W * sizeof(float));
133
134 for (int ph = 0; ph < num_patches_h; ++ph) {
135 for (int pw = 0; pw < num_patches_w; ++pw) {
136
137 int patch_idx = ph * num_patches_w + pw;
138 const float *src_patch = d_patches + (size_t)patch_idx * patch_dim;
139
140 for (int c = 0; c < C; ++c) {
141 for (int py = 0; py < P; ++py) {
142 int y = ph * P + py;
143 int x = pw * P;
144
145 float *dst_row = d_image + (size_t)c * H * W + (size_t)y * W + x;
146 const float *src_row = src_patch + (size_t)c * P * P + (size_t)py * P;
147
148 // Add the patch gradient to the image gradient
149 for (int px = 0; px < P; ++px) {
150 dst_row[px] += src_row[px];
151 }
152 }
153 }
154 }
155 }
156}
157
158/**
159 * Add learned absolute position embeddings in-place.
160 *
161 * x layout: [num_tokens, embed_dim]
162 * position_embd: [num_positions, embed_dim]
163 *
164 * This first v8 vision path intentionally assumes native resized embeddings are
165 * already materialized in the weight tensor, so token i maps directly to
166 * position_embd[i]. This is the correct contract for fixed-size bring-up.
167 */
169 const float *position_embd,
170 int num_tokens,
171 int embed_dim,
172 int num_positions)
173{
174 if (x == NULL || position_embd == NULL || num_tokens <= 0 || embed_dim <= 0) {
175 return;
176 }
177 const int limit = num_tokens < num_positions ? num_tokens : num_positions;
178 for (int tok = 0; tok < limit; ++tok) {
179 float *dst = x + (size_t)tok * embed_dim;
180 const float *src = position_embd + (size_t)tok * embed_dim;
181 for (int d = 0; d < embed_dim; ++d) {
182 dst[d] += src[d];
183 }
184 }
185}
186
188 const float *position_embd,
189 int num_tokens,
190 int embed_dim,
191 int num_positions,
192 int start_position)
193{
194 if (x == NULL || position_embd == NULL || num_tokens <= 0 || embed_dim <= 0 ||
195 num_positions <= 0 || start_position < 0 || start_position >= num_positions) {
196 return;
197 }
198 const int available = num_positions - start_position;
199 const int limit = num_tokens < available ? num_tokens : available;
200 for (int tok = 0; tok < limit; ++tok) {
201 float *dst = x + (size_t)tok * embed_dim;
202 const float *src = position_embd + (size_t)(start_position + tok) * embed_dim;
203 for (int d = 0; d < embed_dim; ++d) {
204 dst[d] += src[d];
205 }
206 }
207}
208
210 const float *position_embd,
211 int grid_h,
212 int grid_w,
213 int embed_dim,
214 int source_grid_size)
215{
216 if (x == NULL || position_embd == NULL || grid_h <= 0 || grid_w <= 0 || embed_dim <= 0) {
217 return;
218 }
219 if (source_grid_size <= 0) {
220 source_grid_size = grid_w > grid_h ? grid_w : grid_h;
221 }
222
223 const size_t table_stride = (size_t) source_grid_size * (size_t) embed_dim;
224 const float *table_x = position_embd;
225 const float *table_y = position_embd + table_stride;
226
227 for (int y = 0; y < grid_h; ++y) {
228 const int yy = y < source_grid_size ? y : (source_grid_size - 1);
229 const float *row_y = table_y + (size_t) yy * (size_t) embed_dim;
230 for (int x_pos = 0; x_pos < grid_w; ++x_pos) {
231 const int xx = x_pos < source_grid_size ? x_pos : (source_grid_size - 1);
232 const float *row_x = table_x + (size_t) xx * (size_t) embed_dim;
233 float *dst = x + ((size_t) y * (size_t) grid_w + (size_t) x_pos) * (size_t) embed_dim;
234 for (int d = 0; d < embed_dim; ++d) {
235 dst[d] += row_x[d] + row_y[d];
236 }
237 }
238 }
239}
240
241/* GGML evaluates each interpolation sample in source traversal order and may
242 * contract only the sample*weight + accumulator expression. Use fmaf
243 * explicitly so Intel and GCC builds preserve that contract without enabling
244 * broader reassociation across source pixels. */
245#if defined(__clang__) || defined(__INTEL_LLVM_COMPILER)
246#pragma float_control(precise, on, push)
247#endif
249 const float *position_embd,
250 int grid_h,
251 int grid_w,
252 int embed_dim,
253 int merge_size,
254 int source_grid_size)
255{
256 if (x == NULL || position_embd == NULL || grid_h <= 0 || grid_w <= 0 || embed_dim <= 0 || merge_size <= 0) {
257 return;
258 }
259
260 if (source_grid_size <= 0) {
261 source_grid_size = grid_h == grid_w ? grid_h : (grid_h > grid_w ? grid_h : grid_w);
262 }
263
264 const int num_tokens = grid_h * grid_w;
265 const int source_tokens = source_grid_size * source_grid_size;
266 const int needs_resize = source_grid_size != grid_h || source_grid_size != grid_w;
267
268 if (!needs_resize) {
269 for (int tok = 0; tok < num_tokens; ++tok) {
270 const int flat = tile_order_index_2d(tok, grid_h, grid_w, merge_size);
271 if (flat < 0 || flat >= source_tokens) {
272 continue;
273 }
274 float *dst = x + (size_t) tok * (size_t) embed_dim;
275 const float *src = position_embd + (size_t) flat * (size_t) embed_dim;
276 for (int d = 0; d < embed_dim; ++d) {
277 dst[d] += src[d];
278 }
279 }
280 return;
281 }
282
283 const float sf_x = (float) grid_w / (float) source_grid_size;
284 const float sf_y = (float) grid_h / (float) source_grid_size;
285 const float pixel_offset = 0.5f;
286 const float support_x = fmaxf(1.0f, 1.0f / sf_x);
287 const float support_y = fmaxf(1.0f, 1.0f / sf_y);
288 const float invscale_x = 1.0f / support_x;
289 const float invscale_y = 1.0f / support_y;
290
291 /* Preserve ggml's channel -> row -> column loop nesting. The destination
292 * index converts the row-major interpolation result directly to CK's
293 * merge-tiled token layout without allocating an intermediate tensor. */
294 for (int d = 0; d < embed_dim; ++d) {
295 for (int dst_y = 0; dst_y < grid_h; ++dst_y) {
296 const float y_src = ((float) dst_y + pixel_offset) / sf_y;
297 int y_min = (int) (y_src - support_y + pixel_offset);
298 int y_max = (int) (y_src + support_y + pixel_offset);
299 if (y_min < 0) y_min = 0;
300 if (y_max > source_grid_size) y_max = source_grid_size;
301
302 for (int dst_x = 0; dst_x < grid_w; ++dst_x) {
303 const float x_src = ((float) dst_x + pixel_offset) / sf_x;
304 int x_min = (int) (x_src - support_x + pixel_offset);
305 int x_max = (int) (x_src + support_x + pixel_offset);
306 if (x_min < 0) x_min = 0;
307 if (x_max > source_grid_size) x_max = source_grid_size;
308 float val = 0.0f;
309 float total_weight = 0.0f;
310 for (int sy = y_min; sy < y_max; ++sy) {
311 const float wy_arg = ((float) sy - y_src + pixel_offset) * invscale_y;
312 const float wy = fmaxf(1.0f - fabsf(wy_arg), 0.0f);
313 if (wy <= 0.0f) {
314 continue;
315 }
316 for (int sx = x_min; sx < x_max; ++sx) {
317 const float wx_arg = ((float) sx - x_src + pixel_offset) * invscale_x;
318 const float wx = fmaxf(1.0f - fabsf(wx_arg), 0.0f);
319 const float weight = wx * wy;
320 if (weight <= 0.0f) {
321 continue;
322 }
323 const float sample = position_embd[((size_t) sy * (size_t) source_grid_size + (size_t) sx) * (size_t) embed_dim + (size_t) d];
324 val = fmaf(sample, weight, val);
325 total_weight += weight;
326 }
327 }
328 if (total_weight > 0.0f) {
329 val /= total_weight;
330 const int tok = tile_order_linear_index_2d(
331 dst_y, dst_x, grid_h, grid_w, merge_size);
332 x[(size_t) tok * (size_t) embed_dim + (size_t) d] += val;
333 }
334 }
335 }
336 }
337}
338#if defined(__clang__) || defined(__INTEL_LLVM_COMPILER)
339#pragma float_control(pop)
340#endif
341
343 const float *position_embd,
344 int grid_h,
345 int grid_w,
346 int embed_dim,
347 int merge_size,
348 int source_grid_size)
349{
350 if (x == NULL || position_embd == NULL || grid_h <= 0 || grid_w <= 0 ||
351 embed_dim <= 0 || merge_size <= 0 || source_grid_size <= 0) {
352 return;
353 }
354
355 const float y_scale = grid_h > 1
356 ? (float)(source_grid_size - 1) / (float)(grid_h - 1)
357 : 0.0f;
358 const float x_scale = grid_w > 1
359 ? (float)(source_grid_size - 1) / (float)(grid_w - 1)
360 : 0.0f;
361
362 for (int tok = 0; tok < grid_h * grid_w; ++tok) {
363 const int row_major = tile_order_index_2d(tok, grid_h, grid_w, merge_size);
364 const int dst_y = row_major / grid_w;
365 const int dst_x = row_major % grid_w;
366 const float src_y = (float)dst_y * y_scale;
367 const float src_x = (float)dst_x * x_scale;
368 const int y0 = (int)src_y;
369 const int x0 = (int)src_x;
370 const int y1 = y0 + 1 < source_grid_size ? y0 + 1 : y0;
371 const int x1 = x0 + 1 < source_grid_size ? x0 + 1 : x0;
372 const float dy = src_y - (float)y0;
373 const float dx = src_x - (float)x0;
374 const float w00 = (1.0f - dy) * (1.0f - dx);
375 const float w01 = (1.0f - dy) * dx;
376 const float w10 = dy * (1.0f - dx);
377 const float w11 = dy * dx;
378 const float *p00 = position_embd + ((size_t)y0 * source_grid_size + x0) * embed_dim;
379 const float *p01 = position_embd + ((size_t)y0 * source_grid_size + x1) * embed_dim;
380 const float *p10 = position_embd + ((size_t)y1 * source_grid_size + x0) * embed_dim;
381 const float *p11 = position_embd + ((size_t)y1 * source_grid_size + x1) * embed_dim;
382 float *dst = x + (size_t)tok * embed_dim;
383
384 for (int d = 0; d < embed_dim; ++d) {
385 const float pos = p00[d] * w00 + p01[d] * w01 + p10[d] * w10 + p11[d] * w11;
386 dst[d] += pos;
387 }
388 }
389}
390
391/*
392 * Match a backend that materializes this interpolation and residual-add edge
393 * in BF16. The ABI remains FP32 so generated runtimes can share activation
394 * storage, but every value is rounded at the BF16 boundary before the next
395 * arithmetic step. Selection belongs to the circuit/kernel-map contract.
396 */
398 const float *position_embd,
399 int grid_h,
400 int grid_w,
401 int embed_dim,
402 int merge_size,
403 int source_grid_size)
404{
405 if (x == NULL || position_embd == NULL || grid_h <= 0 || grid_w <= 0 ||
406 embed_dim <= 0 || merge_size <= 0 || source_grid_size <= 0) {
407 return;
408 }
409
410 for (int tok = 0; tok < grid_h * grid_w; ++tok) {
411 const int row_major = tile_order_index_2d(tok, grid_h, grid_w, merge_size);
412 const int dst_y = row_major / grid_w;
413 const int dst_x = row_major % grid_w;
414 /* torch.linspace computes each rational coordinate before FP32 storage. */
415 const float src_y = grid_h > 1
416 ? (float)((double)dst_y * (double)(source_grid_size - 1) / (double)(grid_h - 1))
417 : 0.0f;
418 const float src_x = grid_w > 1
419 ? (float)((double)dst_x * (double)(source_grid_size - 1) / (double)(grid_w - 1))
420 : 0.0f;
421 const int y0 = (int)src_y;
422 const int x0 = (int)src_x;
423 const int y1 = y0 + 1 < source_grid_size ? y0 + 1 : y0;
424 const int x1 = x0 + 1 < source_grid_size ? x0 + 1 : x0;
425 const float dy = src_y - (float)y0;
426 const float dx = src_x - (float)x0;
427 const float w00 = bf16_to_float(float_to_bf16((1.0f - dy) * (1.0f - dx)));
428 const float w01 = bf16_to_float(float_to_bf16((1.0f - dy) * dx));
429 const float w10 = bf16_to_float(float_to_bf16(dy * (1.0f - dx)));
430 const float w11 = bf16_to_float(float_to_bf16(dy * dx));
431 const float *p00 = position_embd + ((size_t)y0 * source_grid_size + x0) * embed_dim;
432 const float *p01 = position_embd + ((size_t)y0 * source_grid_size + x1) * embed_dim;
433 const float *p10 = position_embd + ((size_t)y1 * source_grid_size + x0) * embed_dim;
434 const float *p11 = position_embd + ((size_t)y1 * source_grid_size + x1) * embed_dim;
435 float *dst = x + (size_t)tok * embed_dim;
436
437 for (int d = 0; d < embed_dim; ++d) {
438 const float v00 = bf16_to_float(float_to_bf16(p00[d] * w00));
439 const float v01 = bf16_to_float(float_to_bf16(p01[d] * w01));
440 const float v10 = bf16_to_float(float_to_bf16(p10[d] * w10));
441 const float v11 = bf16_to_float(float_to_bf16(p11[d] * w11));
442 float pos = bf16_to_float(float_to_bf16(v00 + v01));
443 pos = bf16_to_float(float_to_bf16(pos + v10));
444 pos = bf16_to_float(float_to_bf16(pos + v11));
445 const float hidden = bf16_to_float(float_to_bf16(dst[d]));
446 dst[d] = bf16_to_float(float_to_bf16(hidden + pos));
447 }
448 }
449}
450
451/*
452 * Mixed-precision position interpolation used by runtimes that retain the
453 * learned table in BF16 but form interpolation products and their reduction
454 * in FP32. Only the completed position vector and residual output cross BF16
455 * storage boundaries. The distinct provider keeps this arithmetic contract
456 * independent from the all-BF16 interpolation path above.
457 */
459 float *x,
460 const float *position_embd,
461 int grid_h,
462 int grid_w,
463 int embed_dim,
464 int merge_size,
465 int source_grid_size)
466{
467 if (x == NULL || position_embd == NULL || grid_h <= 0 || grid_w <= 0 ||
468 embed_dim <= 0 || merge_size <= 0 || source_grid_size <= 0) {
469 return;
470 }
471
472 for (int tok = 0; tok < grid_h * grid_w; ++tok) {
473 const int row_major = tile_order_index_2d(tok, grid_h, grid_w, merge_size);
474 const int dst_y = row_major / grid_w;
475 const int dst_x = row_major % grid_w;
476 const float src_y = grid_h > 1
477 ? (float)dst_y * (float)(source_grid_size - 1) / (float)(grid_h - 1)
478 : 0.0f;
479 const float src_x = grid_w > 1
480 ? (float)dst_x * (float)(source_grid_size - 1) / (float)(grid_w - 1)
481 : 0.0f;
482 const int y0 = (int)src_y;
483 const int x0 = (int)src_x;
484 const int y1 = y0 + 1 < source_grid_size ? y0 + 1 : y0;
485 const int x1 = x0 + 1 < source_grid_size ? x0 + 1 : x0;
486 const float y_distance0 = fabsf(src_y - (float)y0);
487 const float y_distance1 = fabsf(src_y - (float)y0 - 1.0f);
488 const float x_distance0 = fabsf(src_x - (float)x0);
489 const float x_distance1 = fabsf(src_x - (float)x0 - 1.0f);
490 const float wy0 = fmaxf(1.0f - y_distance0, 0.0f);
491 const float wy1 = fmaxf(1.0f - y_distance1, 0.0f);
492 const float wx0 = fmaxf(1.0f - x_distance0, 0.0f);
493 const float wx1 = fmaxf(1.0f - x_distance1, 0.0f);
494 const float w00 = wy0 * wx0;
495 const float w01 = wy0 * wx1;
496 const float w10 = wy1 * wx0;
497 const float w11 = wy1 * wx1;
498 const float *p00 = position_embd + ((size_t)y0 * source_grid_size + x0) * embed_dim;
499 const float *p01 = position_embd + ((size_t)y0 * source_grid_size + x1) * embed_dim;
500 const float *p10 = position_embd + ((size_t)y1 * source_grid_size + x0) * embed_dim;
501 const float *p11 = position_embd + ((size_t)y1 * source_grid_size + x1) * embed_dim;
502 float *dst = x + (size_t)tok * embed_dim;
503
504 for (int d = 0; d < embed_dim; ++d) {
505 /* Materialize products before the reduction, as torch.sum does. */
506 volatile float v00 = p00[d] * w00;
507 volatile float v01 = p01[d] * w01;
508 volatile float v10 = p10[d] * w10;
509 volatile float v11 = p11[d] * w11;
510 volatile float pos01 = v00 + v01;
511 volatile float pos012 = pos01 + v10;
512 const float pos = pos012 + v11;
513 const float pos_bf16 = bf16_to_float(float_to_bf16(pos));
514 const float hidden = bf16_to_float(float_to_bf16(dst[d]));
515 dst[d] = bf16_to_float(float_to_bf16(hidden + pos_bf16));
516 }
517 }
518}
519
520/**
521 * Build merged 2D vision position IDs in the layout expected by vision M-RoPE.
522 *
523 * Output layout: [4, grid_h * grid_w] flattened as
524 * [y_stream | x_stream | y_stream_dup | x_stream_dup]
525 *
526 * Tokens are emitted in merged-tile traversal order so the position buffer
527 * matches the same 2x2 grouping used by Qwen-style vision encoders.
528 */
529void vision_position_ids_2d_merge(int32_t *positions,
530 int grid_h,
531 int grid_w,
532 int merge_size)
533{
534 if (!positions || grid_h <= 0 || grid_w <= 0 || merge_size <= 0) {
535 return;
536 }
537
538 const int num_tokens = grid_h * grid_w;
539 int ptr = 0;
540
541 for (int y = 0; y < grid_h; y += merge_size) {
542 for (int x = 0; x < grid_w; x += merge_size) {
543 for (int dy = 0; dy < merge_size; ++dy) {
544 for (int dx = 0; dx < merge_size; ++dx) {
545 const int yy = y + dy;
546 const int xx = x + dx;
547 if (yy >= grid_h || xx >= grid_w || ptr >= num_tokens) {
548 continue;
549 }
550 positions[ptr] = yy;
551 positions[num_tokens + ptr] = xx;
552 positions[2 * num_tokens + ptr] = yy;
553 positions[3 * num_tokens + ptr] = xx;
554 ++ptr;
555 }
556 }
557 }
558 }
559
560 for (; ptr < num_tokens; ++ptr) {
561 positions[ptr] = 0;
562 positions[num_tokens + ptr] = 0;
563 positions[2 * num_tokens + ptr] = 0;
564 positions[3 * num_tokens + ptr] = 0;
565 }
566}
567
568/**
569 * Merge 2x2 neighboring tokens into a single wider token.
570 *
571 * Input layout: [grid_h * grid_w, embed_dim]
572 * Output layout: [(grid_h/2) * (grid_w/2), embed_dim * 4]
573 *
574 * Pack order within each merged token:
575 * top-left, top-right, bottom-left, bottom-right
576 */
577void spatial_merge_2x2(const float *input,
578 float *output,
579 int grid_h,
580 int grid_w,
581 int embed_dim)
582{
583 if (input == NULL || output == NULL || grid_h <= 0 || grid_w <= 0 || embed_dim <= 0) {
584 return;
585 }
586
587 const int merged_h = grid_h / 2;
588 const int merged_w = grid_w / 2;
589 const size_t token_stride = (size_t)embed_dim;
590 const size_t merged_stride = (size_t)embed_dim * 4;
591
592 for (int mh = 0; mh < merged_h; ++mh) {
593 for (int mw = 0; mw < merged_w; ++mw) {
594 const int y0 = mh * 2;
595 const int x0 = mw * 2;
596 const int in_idx00 = y0 * grid_w + x0;
597 const int in_idx01 = in_idx00 + 1;
598 const int in_idx10 = in_idx00 + grid_w;
599 const int in_idx11 = in_idx10 + 1;
600 const int out_idx = mh * merged_w + mw;
601
602 const float *src00 = input + (size_t)in_idx00 * token_stride;
603 const float *src01 = input + (size_t)in_idx01 * token_stride;
604 const float *src10 = input + (size_t)in_idx10 * token_stride;
605 const float *src11 = input + (size_t)in_idx11 * token_stride;
606 float *dst = output + (size_t)out_idx * merged_stride;
607
608 memcpy(dst + 0 * embed_dim, src00, (size_t)embed_dim * sizeof(float));
609 memcpy(dst + 1 * embed_dim, src01, (size_t)embed_dim * sizeof(float));
610 memcpy(dst + 2 * embed_dim, src10, (size_t)embed_dim * sizeof(float));
611 memcpy(dst + 3 * embed_dim, src11, (size_t)embed_dim * sizeof(float));
612 }
613 }
614}
615
616void spatial_merge_contiguous_tiled(const float *input,
617 float *output,
618 int grid_h,
619 int grid_w,
620 int embed_dim,
621 int merge_size)
622{
623 if (input == NULL || output == NULL || grid_h <= 0 || grid_w <= 0 || embed_dim <= 0 || merge_size <= 0) {
624 return;
625 }
626
627 const size_t num_tokens = (size_t) grid_h * (size_t) grid_w;
628 const size_t merge_factor = (size_t) merge_size * (size_t) merge_size;
629 const size_t merged_tokens = num_tokens / merge_factor;
630 memcpy(output, input, merged_tokens * (size_t) embed_dim * merge_factor * sizeof(float));
631}
632
634 float *output,
635 int tokens,
636 int dim,
637 float scale,
638 float eps)
639{
640 if (input == NULL || output == NULL || tokens <= 0 || dim <= 0) return;
641 if (eps <= 0.0f) eps = 1.0e-6f;
642 for (int t = 0; t < tokens; ++t) {
643 const float *src = input + (size_t)t * (size_t)dim;
644 float *dst = output + (size_t)t * (size_t)dim;
645 double ss = 0.0;
646 for (int i = 0; i < dim; ++i) {
647 const float v = src[i] * scale;
648 ss += (double)v * (double)v;
649 }
650 const float inv_rms = 1.0f / sqrtf((float)(ss / (double)dim) + eps);
651 for (int i = 0; i < dim; ++i) {
652 dst[i] = (src[i] * scale) * inv_rms;
653 }
654 }
655}
656
657void spatial_average_pool_contiguous(const float *input,
658 float *output,
659 int grid_h,
660 int grid_w,
661 int embed_dim,
662 int merge_size)
663{
664 if (input == NULL || output == NULL || grid_h <= 0 || grid_w <= 0 || embed_dim <= 0 || merge_size <= 0) {
665 return;
666 }
667
668 const int out_h = grid_h / merge_size;
669 const int out_w = grid_w / merge_size;
670 if (out_h <= 0 || out_w <= 0) {
671 return;
672 }
673
674 const float inv_area = 1.0f / (float)(merge_size * merge_size);
675 for (int oy = 0; oy < out_h; ++oy) {
676 for (int ox = 0; ox < out_w; ++ox) {
677 float *dst = output + ((size_t)oy * (size_t)out_w + (size_t)ox) * (size_t)embed_dim;
678 memset(dst, 0, (size_t)embed_dim * sizeof(float));
679 for (int dy = 0; dy < merge_size; ++dy) {
680 const int iy = oy * merge_size + dy;
681 for (int dx = 0; dx < merge_size; ++dx) {
682 const int ix = ox * merge_size + dx;
683 const float *src = input + ((size_t)iy * (size_t)grid_w + (size_t)ix) * (size_t)embed_dim;
684 for (int c = 0; c < embed_dim; ++c) {
685 dst[c] += src[c];
686 }
687 }
688 }
689 for (int c = 0; c < embed_dim; ++c) {
690 dst[c] *= inv_area;
691 }
692 }
693 }
694}
695
696void rowwise_bias_add(float *x,
697 const float *bias,
698 int rows,
699 int dim)
700{
701 if (!x || !bias || rows <= 0 || dim <= 0) {
702 return;
703 }
704
705 for (int r = 0; r < rows; ++r) {
706 float *row = x + ((size_t) r * (size_t) dim);
707 for (int c = 0; c < dim; ++c) {
708 row[c] += bias[c];
709 }
710 }
711}
712
713void add_stream_inplace(float *a,
714 const float *b,
715 size_t n)
716{
717 if (!a || !b || n == 0) {
718 return;
719 }
720
721 for (size_t i = 0; i < n; ++i) {
722 a[i] += b[i];
723 }
724}
725
726void add_stream_reorder_2d(float *main_inout,
727 float *aux_scratch,
728 int grid_h,
729 int grid_w,
730 int embed_dim,
731 int merge_size)
732{
733 if (!main_inout || !aux_scratch || grid_h <= 0 || grid_w <= 0 || embed_dim <= 0 || merge_size <= 0) {
734 return;
735 }
736
737 const int num_tokens = grid_h * grid_w;
738 const size_t total_elems = (size_t) num_tokens * (size_t) embed_dim;
739
740 for (size_t i = 0; i < total_elems; ++i) {
741 main_inout[i] += aux_scratch[i];
742 }
743
744 for (int tok = 0; tok < num_tokens; ++tok) {
745 const int src_tok = tile_order_index_2d(tok, grid_h, grid_w, merge_size);
746 const float *src_main = main_inout + (size_t) src_tok * (size_t) embed_dim;
747 float *dst = aux_scratch + (size_t) tok * (size_t) embed_dim;
748 for (int d = 0; d < embed_dim; ++d) {
749 dst[d] = src_main[d];
750 }
751 }
752
753 memcpy(main_inout, aux_scratch, total_elems * sizeof(float));
754}
755
756void feature_concat_2way(const float *main_input,
757 const float *branch_input,
758 float *output,
759 int rows,
760 int main_dim,
761 int branch_slice_dim,
762 int num_branch_slices)
763{
764 if (!main_input || !branch_input || !output || rows <= 0 || main_dim <= 0 ||
765 branch_slice_dim < 0 || num_branch_slices < 0) {
766 return;
767 }
768
769 const int branch_dim = branch_slice_dim * num_branch_slices;
770 const size_t main_bytes = (size_t) main_dim * sizeof(float);
771 const size_t branch_bytes = (size_t) branch_dim * sizeof(float);
772 const size_t out_stride = (size_t) (main_dim + branch_dim);
773
774 for (int r = 0; r < rows; ++r) {
775 const float *src_main = main_input + ((size_t) r * (size_t) main_dim);
776 const float *src_branch = branch_input + ((size_t) r * (size_t) branch_dim);
777 float *dst = output + ((size_t) r * out_stride);
778 memcpy(dst, src_main, main_bytes);
779 memcpy(dst + main_dim, src_branch, branch_bytes);
780 }
781}
782
783void feature_slice_copy(const float *src,
784 float *dst,
785 int rows,
786 int src_dim,
787 int dst_dim,
788 int dst_feature_offset)
789{
790 if (!src || !dst || rows <= 0 || src_dim <= 0 || dst_dim <= 0 || dst_feature_offset < 0) {
791 return;
792 }
793 if (dst_feature_offset + src_dim > dst_dim) {
794 return;
795 }
796
797 for (int row = 0; row < rows; ++row) {
798 const float *src_row = src + (size_t) row * (size_t) src_dim;
799 float *dst_row = dst + (size_t) row * (size_t) dst_dim + (size_t) dst_feature_offset;
800 memcpy(dst_row, src_row, (size_t) src_dim * sizeof(float));
801 }
802}
803
804void feature_concat(const float *main_input,
805 const float *branch_input,
806 float *output,
807 int rows,
808 int main_dim,
809 int branch_slice_dim,
810 int num_branch_slices)
811{
812 if (!main_input || !output || rows <= 0 || main_dim < 0 || branch_slice_dim < 0 || num_branch_slices < 0) {
813 return;
814 }
815
816 const int branch_total_dim = branch_slice_dim * num_branch_slices;
817 const int out_dim = main_dim + branch_total_dim;
818
819 const int in_place_expand = (main_input == output) && (out_dim > main_dim);
820 const int row_start = in_place_expand ? rows - 1 : 0;
821 const int row_end = in_place_expand ? -1 : rows;
822 const int row_step = in_place_expand ? -1 : 1;
823
824 for (int row = row_start; row != row_end; row += row_step) {
825 const float *src_main = main_input + (size_t) row * (size_t) main_dim;
826 float *dst_row = output + (size_t) row * (size_t) out_dim;
827
828 if (main_dim > 0) {
829 memmove(dst_row, src_main, (size_t) main_dim * sizeof(float));
830 }
831
832 for (int slice = 0; slice < num_branch_slices; ++slice) {
833 const float *src_branch = branch_input
834 + (size_t) slice * (size_t) rows * (size_t) branch_slice_dim
835 + (size_t) row * (size_t) branch_slice_dim;
836 float *dst_branch = dst_row + (size_t) main_dim + (size_t) slice * (size_t) branch_slice_dim;
837 if (branch_slice_dim > 0) {
838 memcpy(dst_branch, src_branch, (size_t) branch_slice_dim * sizeof(float));
839 }
840 }
841 }
842}
static uint16_t float_to_bf16(float f)
Definition bf16_utils.h:90
static float bf16_to_float(uint16_t v)
Definition bf16_utils.h:38
#define C(color)
Definition show_config.c:39
void add_stream_reorder_2d(float *main_inout, float *aux_scratch, int grid_h, int grid_w, int embed_dim, int merge_size)
void position_embeddings_add_gemma4v_xy(float *x, const float *position_embd, int grid_h, int grid_w, int embed_dim, int source_grid_size)
static int tile_order_index_2d(int linear_idx, int grid_h, int grid_w, int merge_size)
static int tile_order_linear_index_2d(int y, int x, int grid_h, int grid_w, int merge_size)
void spatial_merge_2x2(const float *input, float *output, int grid_h, int grid_w, int embed_dim)
void position_embeddings_add_tiled_2d_align_corners(float *x, const float *position_embd, int grid_h, int grid_w, int embed_dim, int merge_size, int source_grid_size)
void position_embeddings_add_tiled_2d(float *x, const float *position_embd, int grid_h, int grid_w, int embed_dim, int merge_size, int source_grid_size)
void patch2im(const float *d_patches, float *d_image, int C, int H, int W, int P)
void position_embeddings_add_tiled_2d_align_corners_bf16(float *x, const float *position_embd, int grid_h, int grid_w, int embed_dim, int merge_size, int source_grid_size)
void position_embeddings_add_tiled_2d_align_corners_fp32_interp_bf16(float *x, const float *position_embd, int grid_h, int grid_w, int embed_dim, int merge_size, int source_grid_size)
void feature_slice_copy(const float *src, float *dst, int rows, int src_dim, int dst_dim, int dst_feature_offset)
void spatial_merge_contiguous_tiled(const float *input, float *output, int grid_h, int grid_w, int embed_dim, int merge_size)
void im2patch(const float *image, float *patches, int C, int H, int W, int P)
void gemma4_vision_projector_prep_forward(const float *input, float *output, int tokens, int dim, float scale, float eps)
void feature_concat_2way(const float *main_input, const float *branch_input, float *output, int rows, int main_dim, int branch_slice_dim, int num_branch_slices)
void spatial_average_pool_contiguous(const float *input, float *output, int grid_h, int grid_w, int embed_dim, int merge_size)
void feature_concat(const float *main_input, const float *branch_input, float *output, int rows, int main_dim, int branch_slice_dim, int num_branch_slices)
void position_embeddings_add_at_offset(float *x, const float *position_embd, int num_tokens, int embed_dim, int num_positions, int start_position)
void add_stream_inplace(float *a, const float *b, size_t n)
void rowwise_bias_add(float *x, const float *bias, int rows, int dim)
#define CK_VISION_NOINLINE
void position_embeddings_add(float *x, const float *position_embd, int num_tokens, int embed_dim, int num_positions)
void vision_position_ids_2d_merge(int32_t *positions, int grid_h, int grid_w, int merge_size)