GCC Code Coverage Report


Directory: src/
File: src/types/vec16.h
Date: 2026-09-30 11:11:31
Exec Total Coverage
Lines: 96 96 100.0%
Functions: 16 16 100.0%
Branches: 26 30 86.7%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2026 Tiger Data, Inc.
3 * Licensed under the PostgreSQL License. See LICENSE for details.
4 *
5 * vec16.h - Half-precision (float16) vector type and type dispatch
6 *
7 * Provides:
8 * - Platform-adaptive half type (F16C, _Float16, or uint16_t fallback)
9 * - Vec16 struct (binary-compatible with pgvector's HalfVector)
10 * - Vec32TypeOps vtable for type-generic k-means and quantization
11 * - Scalar and bulk conversion between half and float
12 *
13 * The vtable enables mixed-type distance computation (vec_type × float32
14 * centroid) so that halfvec inputs read half the memory without bulk
15 * conversion. Full float32 conversion is only done where unavoidable
16 * (CBLAS sgemm, RaBitQ rotation).
17 */
18
19 #ifndef VEC16_H
20 #define VEC16_H
21
22 #include <float.h>
23 #include <math.h>
24 #include <stdbool.h>
25 #include <stdint.h>
26 #include <string.h>
27
28 #include "core/types.h"
29
30 /* ----------------------------------------------------------------
31 * Half type detection (independent flags, prefer _Float16)
32 *
33 * _Float16 is preferred when available because the compiler can
34 * auto-vectorize (float)h casts into bulk vcvtph2ps (8 at a time),
35 * whereas _cvtsh_ss is a scalar intrinsic (1 at a time). With
36 * -mf16c, the compiler emits F16C instructions for _Float16 casts.
37 *
38 * F16C intrinsics are the fallback when _Float16 is not supported
39 * but -mf16c is available (e.g. older compilers).
40 * ---------------------------------------------------------------- */
41
42 #if defined(__FLT16_MAX__) && !defined(__FreeBSD__) && \
43 (!defined(__i386__) || defined(__SSE2__))
44 #define VS_FLT16_SUPPORT
45 #endif
46
47 /* The F16C vtable below is built on the x86-64 AVX2 helpers in
48 * algo/simd_utils.h; 32-bit x86 converts halves through the portable
49 * paths instead. */
50 #if defined(__F16C__) && (defined(__x86_64__) || defined(_M_X64))
51 #include <immintrin.h>
52 #define VS_F16C_SUPPORT
53 #endif
54
55 #ifdef VS_FLT16_SUPPORT
56 typedef _Float16 half;
57 #define VS_HALF_MAX FLT16_MAX
58 #else
59 typedef uint16_t half;
60 #define VS_HALF_MAX 65504
61 #endif
62
63 /* ----------------------------------------------------------------
64 * Scalar conversion (inline, 3-tier)
65 * ---------------------------------------------------------------- */
66
67 VS_VTABLE_INLINE float
68 3735490 vs_half_to_float(half h)
69 {
70 #if defined(VS_FLT16_SUPPORT)
71 180479 return (float)h;
72 #elif defined(VS_F16C_SUPPORT)
73 return _cvtsh_ss(h);
74 #else
75 /* IEEE 754 bit manipulation fallback */
76 uint16_t bits = h;
77 uint32_t sign = (uint32_t)(bits >> 15) << 31;
78 uint32_t exp = (bits >> 10) & 0x1F;
79 uint32_t frac = bits & 0x3FF;
80 uint32_t f32;
81
82 if (exp == 0)
83 {
84 if (frac == 0)
85 {
86 /* ±zero */
87 f32 = sign;
88 }
89 else
90 {
91 /* subnormal: normalize */
92 exp = 1;
93 while ((frac & 0x400) == 0)
94 {
95 frac <<= 1;
96 exp--;
97 }
98 frac &= 0x3FF;
99 f32 = sign | ((uint32_t)(exp + 127 - 15) << 23) |
100 ((uint32_t)frac << 13);
101 }
102 }
103 else if (exp == 31)
104 {
105 /* Inf or NaN */
106 f32 = sign | 0x7F800000u | ((uint32_t)frac << 13);
107 }
108 else
109 {
110 /* normal */
111 f32 = sign | ((uint32_t)(exp + 127 - 15) << 23) |
112 ((uint32_t)frac << 13);
113 }
114
115 float result;
116 memcpy(&result, &f32, sizeof(float));
117 return result;
118 #endif
119 }
120
121 static inline half
122 12533 vs_float_to_half(float f)
123 {
124 #if defined(VS_FLT16_SUPPORT)
125 12533 return (_Float16)f;
126 #elif defined(VS_F16C_SUPPORT)
127 return _cvtss_sh(f, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
128 #else
129 /* IEEE 754 bit manipulation fallback */
130 uint32_t bits;
131 memcpy(&bits, &f, sizeof(uint32_t));
132
133 uint32_t sign = (bits >> 16) & 0x8000;
134 int32_t exp = ((bits >> 23) & 0xFF) - 127 + 15;
135 uint32_t frac = bits & 0x7FFFFF;
136
137 if (exp <= 0)
138 {
139 if (exp < -10)
140 {
141 /* Too small, flush to zero */
142 return (half)(sign);
143 }
144 /* Subnormal in half precision */
145 frac |= 0x800000;
146 uint32_t shift = (uint32_t)(1 - exp);
147 /* Round to nearest, ties to even */
148 uint32_t round_bit = 1u << (shift + 12);
149 frac += round_bit >> 1;
150 if ((frac & round_bit) && (frac & (round_bit - 1)) == 0)
151 frac &= ~(round_bit >> 1); /* tie: round to even */
152 return (half)(sign | (frac >> (shift + 13)));
153 }
154 else if (exp >= 31)
155 {
156 if (exp == 31 && frac != 0)
157 {
158 /* NaN: preserve some significand bits */
159 return (half)(sign | 0x7E00 | (frac >> 13));
160 }
161 /* Overflow: Inf */
162 return (half)(sign | 0x7C00);
163 }
164
165 /* Round to nearest, ties to even */
166 frac += 0x1000; /* round bit at position 12 */
167 if (frac & 0x800000)
168 {
169 frac = 0;
170 exp++;
171 if (exp >= 31)
172 return (half)(sign | 0x7C00); /* overflow to Inf */
173 }
174
175 return (half)(sign | ((uint32_t)exp << 10) | (frac >> 13));
176 #endif
177 }
178
179 /* ----------------------------------------------------------------
180 * Special value checks
181 * ---------------------------------------------------------------- */
182
183 static inline bool
184 8 vs_half_is_nan(half h)
185 {
186 #ifdef VS_FLT16_SUPPORT
187 8 return isnan(h);
188 #else
189 uint16_t bits = h;
190 return ((bits & 0x7C00) == 0x7C00) && ((bits & 0x03FF) != 0);
191 #endif
192 }
193
194 static inline bool
195 10 vs_half_is_inf(half h)
196 {
197 #ifdef VS_FLT16_SUPPORT
198
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
10 return isinf(h);
199 #else
200 uint16_t bits = h;
201 return ((bits & 0x7FFF) == 0x7C00);
202 #endif
203 }
204
205 static inline bool
206 10 vs_half_is_zero(half h)
207 {
208 #ifdef VS_FLT16_SUPPORT
209 10 return h == (_Float16)0;
210 #else
211 return (h & 0x7FFF) == 0;
212 #endif
213 }
214
215 /* ----------------------------------------------------------------
216 * Vec16 (binary-compatible with pgvector HalfVector)
217 * ---------------------------------------------------------------- */
218
219 typedef struct Vec16
220 {
221 int32_t vl_len_; /* varlena header / size in standalone mode */
222 int16_t dim; /* number of dimensions */
223 int16_t unused; /* reserved for future use, always zero */
224 half x[]; /* flexible array member */
225 } Vec16;
226
227 #define VEC16_SIZE(dim) (offsetof(Vec16, x) + sizeof(half) * (dim))
228 #define VEC16_DIM(v) ((v)->dim)
229 #define VEC16_DATA(v) ((v)->x)
230
231 /* ----------------------------------------------------------------
232 * Bulk conversion (SIMD-dispatched in vec16.c)
233 * ---------------------------------------------------------------- */
234
235 void vs_half_to_float_array(const half *src, float *dst, uint32_t n);
236 void vs_float_to_half_array(const float *src, half *dst, uint32_t n);
237
238 /* ----------------------------------------------------------------
239 * Vec16 lifecycle
240 * ---------------------------------------------------------------- */
241
242 Vec16 *vec16_create(Dimension dim);
243 Vec16 *vec16_from_floats(const float *values, Dimension dim);
244 void vec16_free(Vec16 *v);
245 void vec16_set(Vec16 *v, const float *values);
246
247 /* Convert to Vec32Ref (requires caller-provided float32 buffer) */
248 static inline Vec32Ref
249 2 Vec16ToRef(const Vec16 *hv, float *buffer)
250 {
251 2 vs_half_to_float_array(hv->x, buffer, hv->dim);
252 2 return (Vec32Ref){.data = buffer, .dim = (Dimension)hv->dim};
253 }
254
255 /* ----------------------------------------------------------------
256 * Inline vtable for compile-time specialization
257 *
258 * These always_inline functions + static const vtable enable the
259 * compiler to inline through vtable function pointers when the
260 * pointer target is known at compile time. Used by k-means and
261 * other hot loops that dispatch once at the entry point.
262 * ---------------------------------------------------------------- */
263
264 VS_VTABLE_INLINE float
265 1007 vs_f16_dot_product(const void *vec, const float *centroid, Dimension dim)
266 {
267 1007 const half *v = (const half *)vec;
268 1007 float sum = 0.0f;
269
2/4
✓ Branch 0 taken 3523 times.
✓ Branch 1 taken 1007 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4530 for (Dimension d = 0; d < dim; d++)
270 3523 sum += vs_half_to_float(v[d]) * centroid[d];
271 1007 return sum;
272 }
273
274 VS_VTABLE_INLINE float
275 146740 vs_f16_l2_squared(const void *vec, const float *centroid, Dimension dim)
276 {
277 146740 const half *v = (const half *)vec;
278 146740 float sum = 0.0f;
279
2/2
✓ Branch 0 taken 3594288 times.
✓ Branch 1 taken 146740 times.
3741028 for (Dimension d = 0; d < dim; d++)
280 {
281 3594288 float diff = vs_half_to_float(v[d]) - centroid[d];
282 3594288 sum += diff * diff;
283 }
284 146740 return sum;
285 }
286
287 VS_VTABLE_INLINE float
288 2004 vs_f16_norm_sq(const void *vec, Dimension dim)
289 {
290 2004 const half *v = (const half *)vec;
291 2004 float sum = 0.0f;
292
2/2
✓ Branch 0 taken 9060 times.
✓ Branch 1 taken 2004 times.
11064 for (Dimension d = 0; d < dim; d++)
293 {
294 9060 float val = vs_half_to_float(v[d]);
295 9060 sum += val * val;
296 }
297
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2004 return sum;
298 }
299
300 VS_VTABLE_INLINE void
301 13604 vs_f16_sum_to_float(const void *vec, float *accum, Dimension dim)
302 {
303 13604 const half *v = (const half *)vec;
304
2/2
✓ Branch 0 taken 94662 times.
✓ Branch 1 taken 13604 times.
108266 for (Dimension d = 0; d < dim; d++)
305 94662 accum[d] += vs_half_to_float(v[d]);
306 13604 }
307
308 VS_VTABLE_INLINE void
309 46 vs_f16_to_float_one(const void *src, float *dst, Dimension dim)
310 {
311 46 vs_half_to_float_array((const half *)src, dst, dim);
312 46 }
313
314 VS_VTABLE_INLINE const float *
315 13053 vs_f16_to_float_block(
316 const void *src, float *dst, uint32_t count, Dimension dim)
317 {
318 13053 vs_half_to_float_array((const half *)src, dst, (uint32_t)count * dim);
319 13053 return dst;
320 }
321
322 static const Vec32TypeOps vs_f16_type_ops = {
323 .name = "float16",
324 .element_size = sizeof(half),
325 .dot_product = vs_f16_dot_product,
326 .l2_squared = vs_f16_l2_squared,
327 .norm_sq = vs_f16_norm_sq,
328 .sum_to_float = vs_f16_sum_to_float,
329 .to_float_one = vs_f16_to_float_one,
330 .to_float_block = vs_f16_to_float_block,
331 };
332
333 /* ----------------------------------------------------------------
334 * F16C inline vtable for compile-time specialization
335 *
336 * Hand-written AVX2+FMA+F16C: loads 8 halfs via _mm256_cvtph_ps,
337 * accumulates with FMA, reduces via horizontal sum. Scalar tail
338 * for remainder. Used by k-means and RaBitQ wrappers that target
339 * AVX2 explicitly rather than relying on TARGET_CLONES
340 * auto-vectorization.
341 * ---------------------------------------------------------------- */
342
343 #if defined(VS_F16C_SUPPORT) && !defined(VS_SIMD_NONE)
344
345 #include "algo/simd_utils.h"
346
347 VS_TARGET_F16C_AVX2 VS_VTABLE_INLINE float
348 4 vs_f16c_dot_product(const void *vec, const float *centroid, Dimension dim)
349 {
350 4 const half *v = (const half *)vec;
351 4 __m256 sum8 = _mm256_setzero_ps();
352 4 Dimension d = 0;
353
354
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 4 times.
36 for (; d + 8 <= dim; d += 8)
355 {
356 64 __m128i h8 = _mm_loadu_si128((const __m128i *)(v + d));
357 32 __m256 fv = _mm256_cvtph_ps(h8);
358 64 __m256 fc = _mm256_loadu_ps(centroid + d);
359 32 sum8 = _mm256_fmadd_ps(fv, fc, sum8);
360 }
361
362 4 float sum = vs_horizontal_sum_avx2(sum8);
363
364
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 for (; d < dim; d++)
365 8 sum += vs_half_to_float(v[d]) * centroid[d];
366
367 4 return sum;
368 }
369
370 VS_TARGET_F16C_AVX2 VS_VTABLE_INLINE float
371 4 vs_f16c_l2_squared(const void *vec, const float *centroid, Dimension dim)
372 {
373 4 const half *v = (const half *)vec;
374 4 __m256 sum8 = _mm256_setzero_ps();
375 4 Dimension d = 0;
376
377
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 4 times.
36 for (; d + 8 <= dim; d += 8)
378 {
379 64 __m128i h8 = _mm_loadu_si128((const __m128i *)(v + d));
380 32 __m256 fv = _mm256_cvtph_ps(h8);
381 64 __m256 fc = _mm256_loadu_ps(centroid + d);
382 32 __m256 diff = _mm256_sub_ps(fv, fc);
383 32 sum8 = _mm256_fmadd_ps(diff, diff, sum8);
384 }
385
386 4 float sum = vs_horizontal_sum_avx2(sum8);
387
388
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 for (; d < dim; d++)
389 {
390 8 float diff = vs_half_to_float(v[d]) - centroid[d];
391 8 sum += diff * diff;
392 }
393
394 4 return sum;
395 }
396
397 VS_TARGET_F16C_AVX2 VS_VTABLE_INLINE float
398 4 vs_f16c_norm_sq(const void *vec, Dimension dim)
399 {
400 4 const half *v = (const half *)vec;
401 4 __m256 sum8 = _mm256_setzero_ps();
402 4 Dimension d = 0;
403
404
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 4 times.
36 for (; d + 8 <= dim; d += 8)
405 {
406 64 __m128i h8 = _mm_loadu_si128((const __m128i *)(v + d));
407 32 __m256 fv = _mm256_cvtph_ps(h8);
408 32 sum8 = _mm256_fmadd_ps(fv, fv, sum8);
409 }
410
411 4 float sum = vs_horizontal_sum_avx2(sum8);
412
413
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 for (; d < dim; d++)
414 {
415 4 float val = vs_half_to_float(v[d]);
416 4 sum += val * val;
417 }
418
419 4 return sum;
420 }
421
422 VS_TARGET_F16C_AVX2 VS_VTABLE_INLINE void
423 4 vs_f16c_sum_to_float(const void *vec, float *accum, Dimension dim)
424 {
425 4 const half *v = (const half *)vec;
426 4 Dimension d = 0;
427
428
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 4 times.
36 for (; d + 8 <= dim; d += 8)
429 {
430 64 __m128i h8 = _mm_loadu_si128((const __m128i *)(v + d));
431 32 __m256 fv = _mm256_cvtph_ps(h8);
432 64 __m256 ac = _mm256_loadu_ps(accum + d);
433 32 _mm256_storeu_ps(accum + d, _mm256_add_ps(ac, fv));
434 }
435
436
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
10 for (; d < dim; d++)
437 6 accum[d] += vs_half_to_float(v[d]);
438 4 }
439
440 static const Vec32TypeOps vs_f16c_type_ops = {
441 .name = "float16-f16c",
442 .element_size = sizeof(half),
443 .dot_product = vs_f16c_dot_product,
444 .l2_squared = vs_f16c_l2_squared,
445 .norm_sq = vs_f16c_norm_sq,
446 .sum_to_float = vs_f16c_sum_to_float,
447 .to_float_one = vs_f16_to_float_one,
448 .to_float_block = vs_f16_to_float_block,
449 };
450
451 #endif /* VS_F16C_SUPPORT && !VS_SIMD_NONE */
452
453 #endif /* VEC16_H */
454