cuDDL
Loading...
Searching...
No Matches
detail/hybrid_cardinality.cuh
Go to the documentation of this file.
1#pragma once
2
3#include <cuda/std/cmath>
4#include <cuda/std/cstdint>
5
8
9namespace cuddl::detail {
10
12constexpr int32_t nlz_bins = 66;
13constexpr double dlc_blend_low = 0.088;
14constexpr double dlc_blend_high = 0.952;
15constexpr double dlc_min_fraction = 0.002;
16constexpr double dlc_info_power = 4.5;
17constexpr double hybrid_low = 0.2;
18constexpr double hybrid_high = 5.0;
19
21enum class hybrid_variant : uint8_t {
22 bbtools,
23 paper,
24};
25constexpr uint32_t ddl_cf_size = 13;
26
27static __device__ constexpr float ddl_cf_keys[] = {
28 256.0f,
29 512.0f,
30 1024.0f,
31 2048.0f,
32 4096.0f,
33 8192.0f,
34 16384.0f,
35 32768.0f,
36 65536.0f,
37 131072.0f,
38 262144.0f,
39 524288.0f,
40 1048576.0f,
41};
42static __device__ constexpr float ddl_mean_m_cf[] = {
43 0.92533006f,
44 0.887325502f,
45 0.833883596f,
46 0.810002197f,
47 0.850952315f,
48 0.948926187f,
49 0.998481533f,
50 0.999045759f,
51 0.996665452f,
52 1.00076597f,
53 0.999102624f,
54 0.99457023f,
55 1.00041684f,
56};
57
59__device__ inline double interpolate_mean_m_cf(double estimate) noexcept {
60 if (estimate <= ddl_cf_keys[0]) {
61 return ddl_mean_m_cf[0];
62 }
63 if (estimate >= ddl_cf_keys[ddl_cf_size - 1]) {
64 return ddl_mean_m_cf[ddl_cf_size - 1];
65 }
66 uint32_t low = 0;
67 uint32_t high = ddl_cf_size - 1;
68 while (low + 1 < high) {
69 auto const middle = (low + high) / 2;
70 if (ddl_cf_keys[middle] <= estimate) {
71 low = middle;
72 } else {
73 high = middle;
74 }
75 }
76 auto const fraction = (estimate - ddl_cf_keys[low]) / (ddl_cf_keys[high] - ddl_cf_keys[low]);
77 return ddl_mean_m_cf[low] + fraction * (ddl_mean_m_cf[high] - ddl_mean_m_cf[low]);
78}
79
81__device__ inline double lc_min(uint32_t const* bins, double bucket_count) noexcept {
82 auto cumulative = bins[0];
83 if (cumulative > 0U) {
84 return bucket_count * cuda::std::log(bucket_count / cumulative);
85 }
86 for (int32_t tier = 0; tier + 1 < nlz_bins; ++tier) {
87 cumulative += bins[tier + 1];
88 if (cumulative > 0U) {
89 return cuda::std::ldexp(
90 bucket_count * cuda::std::log(bucket_count / cumulative), tier + 1
91 );
92 }
93 }
94 return 0.0;
95}
96
98__device__ inline double dlc(uint32_t const* bins, double bucket_count, double lc) noexcept {
99 auto const empty = bins[0];
100 auto const minimum = max(1.0, bucket_count * dlc_min_fraction);
101 auto const maximum = bucket_count - minimum;
102 auto cumulative = empty;
103 double weight_sum = 0.0;
104 double weighted_log_sum = 0.0;
105 for (int32_t tier = 0; tier + 1 < nlz_bins; ++tier) {
106 if (cumulative >= minimum && cumulative <= maximum) {
107 auto const occupied = bucket_count - cumulative;
108 auto const log_ratio = cuda::std::log(bucket_count / cumulative);
109 auto const tier_estimate = cuda::std::ldexp(bucket_count * log_ratio, tier);
110
111 auto const error = cuda::std::sqrt(2.0 / 3.14159265358979323846) *
112 cuda::std::sqrt(occupied / (bucket_count * cumulative)) / log_ratio;
113
114 // `1 / error` is positive here. `pow(x, 4.5)` is the same value as
115 // `x^4 * sqrt(x)` and avoids the generic double-precision pow path.
116 auto const inv_error = 1.0 / error;
117 auto const inv_squared = inv_error * inv_error;
118 auto const weight = inv_squared * inv_squared * cuda::std::sqrt(inv_error);
119 weight_sum += weight;
120 weighted_log_sum += weight * cuda::std::log(tier_estimate);
121 }
122 cumulative += bins[tier + 1];
123 if (cumulative >= bucket_count) {
124 break;
125 }
126 }
127 if (weight_sum == 0.0) {
128 return lc;
129 }
130 auto const pure = cuda::std::exp(weighted_log_sum / weight_sum);
131 if (empty >= dlc_blend_high * bucket_count) {
132 return lc;
133 }
134 if (empty > dlc_blend_low * bucket_count) {
135 auto const t = (empty - dlc_blend_low * bucket_count) /
136 ((dlc_blend_high - dlc_blend_low) * bucket_count);
137 return t * lc + (1.0 - t) * pure;
138 }
139 return pure;
140}
141
143__device__ inline double hybrid(
144 double low_estimate,
145 double zone_estimate,
146 double corrected_mean,
147 double bucket_count
148) noexcept {
149 auto const low = hybrid_low * bucket_count;
150 auto const high = hybrid_high * bucket_count;
151 if (zone_estimate <= low) {
152 return low_estimate;
153 }
154 if (zone_estimate >= high) {
155 return corrected_mean;
156 }
157 auto const t = cuda::std::log(zone_estimate / low) / cuda::std::log(high / low);
158 return (1.0 - t) * low_estimate + t * corrected_mean;
159}
160
162__device__ inline double
163bbtools_mean_m(double bucket_count, double filled, double restored_sum) noexcept {
164 if (filled <= 0.0 || restored_sum <= 0.0) {
165 return 0.0;
166 }
167 auto const mean = restored_sum / filled;
168 auto const occupancy_correction = (filled + bucket_count) / (2.0 * bucket_count);
169 return hash_range / mean * filled * occupancy_correction;
170}
171
173__device__ inline hybrid_cardinality_estimates
174hybrid_estimates(uint32_t const* bins, double bucket_count, double restored_sum) noexcept {
175 auto const empty = static_cast<double>(bins[0]);
176 auto const filled = bucket_count - empty;
177 auto const raw_mean = bbtools_mean_m(bucket_count, filled, restored_sum);
178 auto const lc = lc_min(bins, bucket_count);
179 auto const dlc_estimate = dlc(bins, bucket_count, lc);
180 auto const corrected_mean = raw_mean * interpolate_mean_m_cf(dlc_estimate);
181 return {
182 hybrid(lc, lc, corrected_mean, bucket_count),
183 hybrid(lc, dlc_estimate, corrected_mean, bucket_count),
184 lc,
185 dlc_estimate,
186 raw_mean,
187 };
188}
189
200__device__ inline float interpolate_mean_m_cf_f32(float estimate) noexcept {
201 if (estimate <= ddl_cf_keys[0]) {
202 return ddl_mean_m_cf[0];
203 }
204 if (estimate >= ddl_cf_keys[ddl_cf_size - 1]) {
205 return ddl_mean_m_cf[ddl_cf_size - 1];
206 }
207 uint32_t low = 0;
208 uint32_t high = ddl_cf_size - 1;
209 while (low + 1 < high) {
210 auto const middle = (low + high) / 2;
211 if (ddl_cf_keys[middle] <= estimate) {
212 low = middle;
213 } else {
214 high = middle;
215 }
216 }
217 auto const fraction = (estimate - ddl_cf_keys[low]) / (ddl_cf_keys[high] - ddl_cf_keys[low]);
218 return ddl_mean_m_cf[low] + fraction * (ddl_mean_m_cf[high] - ddl_mean_m_cf[low]);
219}
220
222__device__ inline float lc_min_f32(uint32_t const* bins, float bucket_count) noexcept {
223 auto cumulative = bins[0];
224 if (cumulative > 0U) {
225 return bucket_count * cuda::std::log(bucket_count / cumulative);
226 }
227 for (int32_t tier = 0; tier + 1 < nlz_bins; ++tier) {
228 cumulative += bins[tier + 1];
229 if (cumulative > 0U) {
230 return cuda::std::ldexp(
231 bucket_count * cuda::std::log(bucket_count / cumulative), tier + 1
232 );
233 }
234 }
235 return 0.0f;
236}
237
239__device__ inline float dlc_f32(uint32_t const* bins, float bucket_count, float lc) noexcept {
240 auto const empty = static_cast<float>(bins[0]);
241 auto const minimum = max(1.0f, bucket_count * static_cast<float>(dlc_min_fraction));
242 auto const maximum = bucket_count - minimum;
243 auto cumulative = empty;
244 float weight_sum = 0.0f;
245 float weighted_log_sum = 0.0f;
246 for (int32_t tier = 0; tier + 1 < nlz_bins; ++tier) {
247 if (cumulative >= minimum && cumulative <= maximum) {
248 auto const occupied = bucket_count - cumulative;
249 auto const log_ratio = cuda::std::log(bucket_count / cumulative);
250 auto const tier_estimate = cuda::std::ldexp(bucket_count * log_ratio, tier);
251
252 auto const error = cuda::std::sqrt(2.0f / 3.14159265358979323846f) *
253 cuda::std::sqrt(occupied / (bucket_count * cumulative)) / log_ratio;
254
255 // `1 / error` is positive here. `pow(x, 4.5)` is the same value as
256 // `x^4 * sqrt(x)` and avoids a generic pow path.
257 auto const inv_error = 1.0f / error;
258 auto const inv_squared = inv_error * inv_error;
259 auto const weight = inv_squared * inv_squared * cuda::std::sqrt(inv_error);
260 weight_sum += weight;
261 weighted_log_sum += weight * cuda::std::log(tier_estimate);
262 }
263 cumulative += bins[tier + 1];
264 if (cumulative >= bucket_count) {
265 break;
266 }
267 }
268 if (weight_sum == 0.0f) {
269 return lc;
270 }
271 auto const pure = cuda::std::exp(weighted_log_sum / weight_sum);
272 if (empty >= static_cast<float>(dlc_blend_high) * bucket_count) {
273 return lc;
274 }
275 if (empty > static_cast<float>(dlc_blend_low) * bucket_count) {
276 auto const t = (empty - static_cast<float>(dlc_blend_low) * bucket_count) /
277 ((static_cast<float>(dlc_blend_high) - static_cast<float>(dlc_blend_low)) *
278 bucket_count);
279 return t * lc + (1.0f - t) * pure;
280 }
281 return pure;
282}
283
285__device__ inline float hybrid_f32(
286 float low_estimate,
287 float zone_estimate,
288 float corrected_mean,
289 float bucket_count
290) noexcept {
291 auto const low = static_cast<float>(hybrid_low) * bucket_count;
292 auto const high = static_cast<float>(hybrid_high) * bucket_count;
293 if (zone_estimate <= low) {
294 return low_estimate;
295 }
296 if (zone_estimate >= high) {
297 return corrected_mean;
298 }
299 auto const t = cuda::std::log(zone_estimate / low) / cuda::std::log(high / low);
300 return (1.0f - t) * low_estimate + t * corrected_mean;
301}
302
304__device__ inline float
305bbtools_mean_m_f32(float bucket_count, float filled, float restored_sum) noexcept {
306 if (filled <= 0.0f || restored_sum <= 0.0f) {
307 return 0.0f;
308 }
309 auto const mean = restored_sum / filled;
310 auto const occupancy_correction = (filled + bucket_count) / (2.0f * bucket_count);
311 return static_cast<float>(hash_range) / mean * filled * occupancy_correction;
312}
313
315__device__ inline hybrid_cardinality_estimates
316hybrid_estimates_f32(uint32_t const* bins, float bucket_count, float restored_sum) noexcept {
317 auto const empty = static_cast<float>(bins[0]);
318 auto const filled = bucket_count - empty;
319 auto const raw_mean = bbtools_mean_m_f32(bucket_count, filled, restored_sum);
320 auto const lc = lc_min_f32(bins, bucket_count);
321 auto const dlc_estimate = dlc_f32(bins, bucket_count, lc);
322 auto const corrected_mean = raw_mean * interpolate_mean_m_cf_f32(dlc_estimate);
323 return {
324 static_cast<double>(hybrid_f32(lc, lc, corrected_mean, bucket_count)),
325 static_cast<double>(hybrid_f32(lc, dlc_estimate, corrected_mean, bucket_count)),
326 static_cast<double>(lc),
327 static_cast<double>(dlc_estimate),
328 static_cast<double>(raw_mean),
329 };
330}
331
332} // namespace cuddl::detail
__device__ float dlc_f32(uint32_t const *bins, float bucket_count, float lc) noexcept
Single-precision dlc for the latency-bound hybrid scan.
__device__ double interpolate_mean_m_cf(double estimate) noexcept
Interpolates the MeanM correction factor for estimate from the calibration table.
constexpr double dlc_blend_low
auto const high
Definition kernels.cuh:940
__device__ hybrid_cardinality_estimates hybrid_estimates(uint32_t const *bins, double bucket_count, double restored_sum) noexcept
Fused BBTools, paper, linear-counting, DLC, and raw MeanM estimates.
constexpr double hybrid_low
constexpr double hybrid_high
__device__ float hybrid_f32(float low_estimate, float zone_estimate, float corrected_mean, float bucket_count) noexcept
Single-precision hybrid for the latency-bound hybrid scan.
__device__ float interpolate_mean_m_cf_f32(float estimate) noexcept
Single-precision variant of the hybrid estimator tail.
constexpr double hash_range
2^64, the range of the 64-bit hash used by the sketch.
__device__ hybrid_cardinality_estimates hybrid_estimates_f32(uint32_t const *bins, float bucket_count, float restored_sum) noexcept
Single-precision hybrid_estimates for the latency-bound hybrid scan.
constexpr double dlc_info_power
constexpr uint32_t ddl_cf_size
__device__ double dlc(uint32_t const *bins, double bucket_count, double lc) noexcept
Distributed linear-counting estimate, blended from per-tier estimates by precision.
constexpr double dlc_blend_high
hybrid_variant
Which HybridDDL blend the variant kernel computes.
__device__ double bbtools_mean_m(double bucket_count, double filled, double restored_sum) noexcept
BBTools occupancy-corrected MeanM estimate from restored magnitudes.
@ hybrid
CPU inflation, followed by GPU FASTA normalisation of the inflated bytes.
__device__ float bbtools_mean_m_f32(float bucket_count, float filled, float restored_sum) noexcept
Single-precision bbtools_mean_m for the latency-bound hybrid scan.
constexpr double dlc_min_fraction
__device__ float lc_min_f32(uint32_t const *bins, float bucket_count) noexcept
Single-precision lc_min for the latency-bound hybrid scan.
__device__ double lc_min(uint32_t const *bins, double bucket_count) noexcept
Linear-counting estimate from the first occupied NLZ tier.
constexpr int32_t nlz_bins
NLZ histogram bins for the hybrid estimator, including the empty tier.
Experimental DDL cardinality estimates computed from one register scan.