| … | … | @@ -0,0 +1,407 @@ |
| 1 | /* | |
| 2 | * gpu-atomspace.cl -- GPU-resident AtomSpace pools | |
| 3 | * | |
| 4 | * Three flat SoA (struct-of-arrays) pools for uniform-size atoms: | |
| 5 | * - WordPool: words/concepts (nodes) | |
| 6 | * - PairPool: word pairs (binary links) | |
| 7 | * - SectionPool: word+disjunct (sections for connector vectors) | |
| 8 | * | |
| 9 | * Each pool has: | |
| 10 | * - A bump allocator (atomic counter for next free slot) | |
| 11 | * - A hash table mapping content hash → pool index | |
| 12 | * - SoA arrays for each field (coalesced GPU access) | |
| 13 | * | |
| 14 | * All counting uses atomic operations (CAS-loop for doubles). | |
| 15 | * | |
| 16 | * This file is concatenated after gpu-hashtable.cl at load time, | |
| 17 | * so ht_hash(), atom_cmpxchg, HT_EMPTY_KEY etc. are available. | |
| 18 | */ | |
| 19 | ||
| 20 | /* ─── Double-precision atomic add via CAS loop ─── */ | |
| 21 | ||
| 22 | /* | |
| 23 | * OpenCL has no native atomicAdd for doubles. We implement it | |
| 24 | * via compare-and-swap on the 64-bit integer representation. | |
| 25 | * Uses atom_cmpxchg from cl_khr_int64_base_atomics. | |
| 26 | */ | |
| 27 | void atomic_add_double(__global volatile double* addr, double val) | |
| 28 | { | |
| 29 | union { ulong i; double f; } next, expected, current; | |
| 30 | current.f = *addr; | |
| 31 | do { | |
| 32 | expected = current; | |
| 33 | next.f = expected.f + val; | |
| 34 | current.i = atom_cmpxchg( | |
| 35 | (__global volatile ulong*)addr, | |
| 36 | expected.i, next.i); | |
| 37 | } while (current.i != expected.i); | |
| 38 | } | |
| 39 | ||
| 40 | /* ─── Pool capacity constants (set by host at compile time) ─── */ | |
| 41 | ||
| 42 | /* These are defined as macros by the host via -D flags: | |
| 43 | * WORD_CAPACITY max words (e.g., 131072 = 128K) | |
| 44 | * PAIR_CAPACITY max pairs (e.g., 4194304 = 4M) | |
| 45 | * SECTION_CAPACITY max sections (e.g., 1048576 = 1M) | |
| 46 | * WORD_HT_CAPACITY word hash table size (power of 2) | |
| 47 | * PAIR_HT_CAPACITY pair hash table size (power of 2) | |
| 48 | * SECTION_HT_CAPACITY section hash table size (power of 2) | |
| 49 | */ | |
| 50 | ||
| 51 | /* ═══════════════════════════════════════════════════════════════ | |
| 52 | * WORD POOL — Nodes | |
| 53 | * | |
| 54 | * SoA layout: | |
| 55 | * word_name_hash[N] ulong — content hash (from string) | |
| 56 | * word_count[N] double — marginal count | |
| 57 | * word_mi_marginal[N] double — sum of MI for marginal | |
| 58 | * word_class_id[N] uint — class assignment (0 = none) | |
| 59 | * | |
| 60 | * Hash table: name_hash → word_index | |
| 61 | * ═══════════════════════════════════════════════════════════════ */ | |
| 62 | ||
| 63 | /* | |
| 64 | * Find or create words in the pool. | |
| 65 | * | |
| 66 | * For each input name_hash, looks up in the word hash table. | |
| 67 | * If found, returns the existing index. If not found, atomically | |
| 68 | * allocates a new slot and initializes it. | |
| 69 | * | |
| 70 | * Args: | |
| 71 | * wht_keys, wht_values - word hash table (keys + values) | |
| 72 | * word_name_hash - word pool: name hashes | |
| 73 | * word_count - word pool: counts (init to 0.0) | |
| 74 | * word_class_id - word pool: class IDs (init to 0) | |
| 75 | * word_next_free - atomic bump allocator (single uint) | |
| 76 | * in_name_hashes - input: name hashes to find/create | |
| 77 | * out_indices - output: pool index for each input | |
| 78 | * num_items - number of inputs | |
| 79 | */ | |
| 80 | __kernel void word_find_or_create( | |
| 81 | __global volatile ulong* wht_keys, | |
| 82 | __global volatile uint* wht_values, | |
| 83 | __global ulong* word_name_hash, | |
| 84 | __global double* word_count, | |
| 85 | __global uint* word_class_id, | |
| 86 | __global volatile uint* word_next_free, | |
| 87 | __global const ulong* in_name_hashes, | |
| 88 | __global uint* out_indices, | |
| 89 | const uint num_items) | |
| 90 | { | |
| 91 | uint tid = get_global_id(0); | |
| 92 | if (tid >= num_items) return; | |
| 93 | ||
| 94 | ulong name_hash = in_name_hashes[tid]; | |
| 95 | if (name_hash == HT_EMPTY_KEY) { | |
| 96 | out_indices[tid] = HT_EMPTY_VALUE; | |
| 97 | return; | |
| 98 | } | |
| 99 | ||
| 100 | ulong ht_cap = WORD_HT_CAPACITY; | |
| 101 | ulong mask = ht_cap - 1; | |
| 102 | ulong slot = ht_hash(name_hash) & mask; | |
| 103 | ||
| 104 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 105 | { | |
| 106 | ulong prev = atom_cmpxchg(&wht_keys[slot], HT_EMPTY_KEY, name_hash); | |
| 107 | ||
| 108 | if (prev == HT_EMPTY_KEY) | |
| 109 | { | |
| 110 | /* We won the slot — allocate from pool and publish value */ | |
| 111 | uint idx = atomic_add(word_next_free, 1U); | |
| 112 | if (idx < WORD_CAPACITY) { | |
| 113 | word_name_hash[idx] = name_hash; | |
| 114 | word_count[idx] = 0.0; | |
| 115 | word_class_id[idx] = 0; | |
| 116 | /* Write value LAST so spin-waiters see valid index */ | |
| 117 | mem_fence(CLK_GLOBAL_MEM_FENCE); | |
| 118 | wht_values[slot] = idx; | |
| 119 | out_indices[tid] = idx; | |
| 120 | } else { | |
| 121 | out_indices[tid] = HT_EMPTY_VALUE; | |
| 122 | } | |
| 123 | return; | |
| 124 | } | |
| 125 | if (prev == name_hash) | |
| 126 | { | |
| 127 | /* Already exists — spin until creator publishes value */ | |
| 128 | uint val = wht_values[slot]; | |
| 129 | while (val == HT_EMPTY_VALUE) { | |
| 130 | val = wht_values[slot]; | |
| 131 | } | |
| 132 | out_indices[tid] = val; | |
| 133 | return; | |
| 134 | } | |
| 135 | ||
| 136 | slot = (slot + 1) & mask; | |
| 137 | } | |
| 138 | out_indices[tid] = HT_EMPTY_VALUE; | |
| 139 | } | |
| 140 | ||
| 141 | /* ═══════════════════════════════════════════════════════════════ | |
| 142 | * PAIR POOL — Binary Links (word pairs) | |
| 143 | * | |
| 144 | * SoA layout: | |
| 145 | * pair_word_a[N] uint — index into WordPool | |
| 146 | * pair_word_b[N] uint — index into WordPool | |
| 147 | * pair_count[N] double — joint count | |
| 148 | * pair_mi[N] double — mutual information | |
| 149 | * pair_flags[N] uint — dirty bit, etc. | |
| 150 | * | |
| 151 | * Hash table key: (word_a << 32) | word_b (packed 64-bit) | |
| 152 | * ═══════════════════════════════════════════════════════════════ */ | |
| 153 | ||
| 154 | /* Pack two 32-bit word indices into a single 64-bit hash key */ | |
| 155 | inline ulong pair_key(uint word_a, uint word_b) | |
| 156 | { | |
| 157 | /* Always store with smaller index first for canonical ordering */ | |
| 158 | uint lo = min(word_a, word_b); | |
| 159 | uint hi = max(word_a, word_b); | |
| 160 | return ((ulong)lo << 32) | (ulong)hi; | |
| 161 | } | |
| 162 | ||
| 163 | /* | |
| 164 | * Find or create word pairs in the pool. | |
| 165 | * | |
| 166 | * Args: | |
| 167 | * pht_keys, pht_values - pair hash table | |
| 168 | * pair_word_a, pair_word_b - pair pool: word indices | |
| 169 | * pair_count - pair pool: counts (init to 0.0) | |
| 170 | * pair_mi - pair pool: MI values (init to 0.0) | |
| 171 | * pair_flags - pair pool: flags (init to 0) | |
| 172 | * pair_next_free - atomic bump allocator | |
| 173 | * in_word_a, in_word_b - input: word index pairs | |
| 174 | * out_indices - output: pair pool index for each input | |
| 175 | * num_items - number of inputs | |
| 176 | */ | |
| 177 | __kernel void pair_find_or_create( | |
| 178 | __global volatile ulong* pht_keys, | |
| 179 | __global volatile uint* pht_values, | |
| 180 | __global uint* pair_word_a, | |
| 181 | __global uint* pair_word_b, | |
| 182 | __global double* pair_count, | |
| 183 | __global double* pair_mi, | |
| 184 | __global uint* pair_flags, | |
| 185 | __global volatile uint* pair_next_free, | |
| 186 | __global const uint* in_word_a, | |
| 187 | __global const uint* in_word_b, | |
| 188 | __global uint* out_indices, | |
| 189 | const uint num_items) | |
| 190 | { | |
| 191 | uint tid = get_global_id(0); | |
| 192 | if (tid >= num_items) return; | |
| 193 | ||
| 194 | uint wa = in_word_a[tid]; | |
| 195 | uint wb = in_word_b[tid]; | |
| 196 | ulong key = pair_key(wa, wb); | |
| 197 | ||
| 198 | ulong ht_cap = PAIR_HT_CAPACITY; | |
| 199 | ulong mask = ht_cap - 1; | |
| 200 | ulong slot = ht_hash(key) & mask; | |
| 201 | ||
| 202 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 203 | { | |
| 204 | ulong prev = atom_cmpxchg(&pht_keys[slot], HT_EMPTY_KEY, key); | |
| 205 | ||
| 206 | if (prev == HT_EMPTY_KEY) | |
| 207 | { | |
| 208 | uint idx = atomic_add(pair_next_free, 1U); | |
| 209 | if (idx < PAIR_CAPACITY) { | |
| 210 | pair_word_a[idx] = min(wa, wb); | |
| 211 | pair_word_b[idx] = max(wa, wb); | |
| 212 | pair_count[idx] = 0.0; | |
| 213 | pair_mi[idx] = 0.0; | |
| 214 | pair_flags[idx] = 0; | |
| 215 | mem_fence(CLK_GLOBAL_MEM_FENCE); | |
| 216 | pht_values[slot] = idx; | |
| 217 | out_indices[tid] = idx; | |
| 218 | } else { | |
| 219 | out_indices[tid] = HT_EMPTY_VALUE; | |
| 220 | } | |
| 221 | return; | |
| 222 | } | |
| 223 | if (prev == key) | |
| 224 | { | |
| 225 | uint val = pht_values[slot]; | |
| 226 | while (val == HT_EMPTY_VALUE) { | |
| 227 | val = pht_values[slot]; | |
| 228 | } | |
| 229 | out_indices[tid] = val; | |
| 230 | return; | |
| 231 | } | |
| 232 | ||
| 233 | slot = (slot + 1) & mask; | |
| 234 | } | |
| 235 | out_indices[tid] = HT_EMPTY_VALUE; | |
| 236 | } | |
| 237 | ||
| 238 | /* ═══════════════════════════════════════════════════════════════ | |
| 239 | * SECTION POOL — Word + Disjunct | |
| 240 | * | |
| 241 | * SoA layout: | |
| 242 | * sec_word[N] uint — index into WordPool | |
| 243 | * sec_disjunct_hash[N] ulong — hash of full disjunct string | |
| 244 | * sec_count[N] double — section count | |
| 245 | * | |
| 246 | * Hash table key: hash(word_idx, disjunct_hash) | |
| 247 | * ═══════════════════════════════════════════════════════════════ */ | |
| 248 | ||
| 249 | /* Combine word index and disjunct hash into a single 64-bit key */ | |
| 250 | inline ulong section_key(uint word_idx, ulong disjunct_hash) | |
| 251 | { | |
| 252 | /* Mix the word index into the disjunct hash */ | |
| 253 | ulong key = disjunct_hash ^ ((ulong)word_idx * 0x9E3779B97F4A7C15UL); | |
| 254 | /* Ensure it's never the sentinel */ | |
| 255 | if (key == HT_EMPTY_KEY) key = 0; | |
| 256 | return key; | |
| 257 | } | |
| 258 | ||
| 259 | /* | |
| 260 | * Find or create sections in the pool. | |
| 261 | * | |
| 262 | * Args: | |
| 263 | * sht_keys, sht_values - section hash table | |
| 264 | * sec_word - section pool: word indices | |
| 265 | * sec_disjunct_hash - section pool: disjunct hashes | |
| 266 | * sec_count - section pool: counts (init to 0.0) | |
| 267 | * sec_next_free - atomic bump allocator | |
| 268 | * in_word_indices - input: word indices | |
| 269 | * in_disjunct_hashes - input: disjunct hashes | |
| 270 | * out_indices - output: section pool index | |
| 271 | * num_items - number of inputs | |
| 272 | */ | |
| 273 | __kernel void section_find_or_create( | |
| 274 | __global volatile ulong* sht_keys, | |
| 275 | __global volatile uint* sht_values, | |
| 276 | __global uint* sec_word, | |
| 277 | __global ulong* sec_disjunct_hash, | |
| 278 | __global double* sec_count, | |
| 279 | __global volatile uint* sec_next_free, | |
| 280 | __global const uint* in_word_indices, | |
| 281 | __global const ulong* in_disjunct_hashes, | |
| 282 | __global uint* out_indices, | |
| 283 | const uint num_items) | |
| 284 | { | |
| 285 | uint tid = get_global_id(0); | |
| 286 | if (tid >= num_items) return; | |
| 287 | ||
| 288 | uint widx = in_word_indices[tid]; | |
| 289 | ulong dhash = in_disjunct_hashes[tid]; | |
| 290 | ulong key = section_key(widx, dhash); | |
| 291 | ||
| 292 | ulong ht_cap = SECTION_HT_CAPACITY; | |
| 293 | ulong mask = ht_cap - 1; | |
| 294 | ulong slot = ht_hash(key) & mask; | |
| 295 | ||
| 296 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 297 | { | |
| 298 | ulong prev = atom_cmpxchg(&sht_keys[slot], HT_EMPTY_KEY, key); | |
| 299 | ||
| 300 | if (prev == HT_EMPTY_KEY) | |
| 301 | { | |
| 302 | uint idx = atomic_add(sec_next_free, 1U); | |
| 303 | if (idx < SECTION_CAPACITY) { | |
| 304 | sec_word[idx] = widx; | |
| 305 | sec_disjunct_hash[idx] = dhash; | |
| 306 | sec_count[idx] = 0.0; | |
| 307 | mem_fence(CLK_GLOBAL_MEM_FENCE); | |
| 308 | sht_values[slot] = idx; | |
| 309 | out_indices[tid] = idx; | |
| 310 | } else { | |
| 311 | out_indices[tid] = HT_EMPTY_VALUE; | |
| 312 | } | |
| 313 | return; | |
| 314 | } | |
| 315 | if (prev == key) | |
| 316 | { | |
| 317 | uint val = sht_values[slot]; | |
| 318 | while (val == HT_EMPTY_VALUE) { | |
| 319 | val = sht_values[slot]; | |
| 320 | } | |
| 321 | out_indices[tid] = val; | |
| 322 | return; | |
| 323 | } | |
| 324 | ||
| 325 | slot = (slot + 1) & mask; | |
| 326 | } | |
| 327 | out_indices[tid] = HT_EMPTY_VALUE; | |
| 328 | } | |
| 329 | ||
| 330 | /* ═══════════════════════════════════════════════════════════════ | |
| 331 | * COUNTING OPERATIONS | |
| 332 | * | |
| 333 | * Atomic double-precision increments on pool arrays. | |
| 334 | * ═══════════════════════════════════════════════════════════════ */ | |
| 335 | ||
| 336 | /* | |
| 337 | * Increment pair counts by 1.0 for given pair indices. | |
| 338 | * Also increments word marginal counts for both words. | |
| 339 | */ | |
| 340 | __kernel void count_pairs( | |
| 341 | __global volatile double* pair_count, | |
| 342 | __global const uint* pair_word_a, | |
| 343 | __global const uint* pair_word_b, | |
| 344 | __global volatile double* word_count, | |
| 345 | __global volatile uint* pair_flags, | |
| 346 | __global const uint* pair_indices, | |
| 347 | const uint num_items) | |
| 348 | { | |
| 349 | uint tid = get_global_id(0); | |
| 350 | if (tid >= num_items) return; | |
| 351 | ||
| 352 | uint pidx = pair_indices[tid]; | |
| 353 | if (pidx == HT_EMPTY_VALUE) return; | |
| 354 | ||
| 355 | /* Increment pair count */ | |
| 356 | atomic_add_double(&pair_count[pidx], 1.0); | |
| 357 | ||
| 358 | /* Increment word marginals */ | |
| 359 | uint wa = pair_word_a[pidx]; | |
| 360 | uint wb = pair_word_b[pidx]; | |
| 361 | atomic_add_double(&word_count[wa], 1.0); | |
| 362 | atomic_add_double(&word_count[wb], 1.0); | |
| 363 | ||
| 364 | /* Mark pair as dirty (needs MI recomputation) */ | |
| 365 | pair_flags[pidx] = 1; | |
| 366 | } | |
| 367 | ||
| 368 | /* | |
| 369 | * Increment section counts by 1.0 for given section indices. | |
| 370 | */ | |
| 371 | __kernel void count_sections( | |
| 372 | __global volatile double* sec_count, | |
| 373 | __global const uint* sec_indices, | |
| 374 | const uint num_items) | |
| 375 | { | |
| 376 | uint tid = get_global_id(0); | |
| 377 | if (tid >= num_items) return; | |
| 378 | ||
| 379 | uint sidx = sec_indices[tid]; | |
| 380 | if (sidx == HT_EMPTY_VALUE) return; | |
| 381 | ||
| 382 | atomic_add_double(&sec_count[sidx], 1.0); | |
| 383 | } | |
| 384 | ||
| 385 | /* ═══════════════════════════════════════════════════════════════ | |
| 386 | * POOL STATISTICS | |
| 387 | * | |
| 388 | * Read back pool sizes (bump allocator values). | |
| 389 | * ═══════════════════════════════════════════════════════════════ */ | |
| 390 | ||
| 391 | /* | |
| 392 | * Read pool sizes into output array. | |
| 393 | * out_stats[0] = word count | |
| 394 | * out_stats[1] = pair count | |
| 395 | * out_stats[2] = section count | |
| 396 | */ | |
| 397 | __kernel void pool_stats( | |
| 398 | __global const uint* word_next_free, | |
| 399 | __global const uint* pair_next_free, | |
| 400 | __global const uint* sec_next_free, | |
| 401 | __global uint* out_stats) | |
| 402 | { | |
| 403 | if (get_global_id(0) != 0) return; | |
| 404 | out_stats[0] = *word_next_free; | |
| 405 | out_stats[1] = *pair_next_free; | |
| 406 | out_stats[2] = *sec_next_free; | |
| 407 | } |
| … | … | @@ -0,0 +1,340 @@ |
| 1 | /* | |
| 2 | * gpu-cosine.cl -- Cosine similarity and candidate generation on GPU | |
| 3 | * | |
| 4 | * Computes pairwise cosine similarity between words based on their | |
| 5 | * section (disjunct) vectors, entirely on GPU-resident data. | |
| 6 | * | |
| 7 | * Pipeline: | |
| 8 | * 1. compute_word_norms — ||v||² for each word | |
| 9 | * 2. build_disjunct_chains — reverse index: djh → linked list of sections | |
| 10 | * 3. accumulate_dot_products — walk chains, accumulate dot(w1,w2) | |
| 11 | * 4. compute_cosines — cosine = dot / (||v1|| × ||v2||) | |
| 12 | * 5. filter_candidates — compact output above threshold | |
| 13 | * | |
| 14 | * No CPU↔GPU data transfer between stages. | |
| 15 | * | |
| 16 | * Requires capacity macros (set via -D flags): | |
| 17 | * DJH_HT_CAPACITY — disjunct reverse index hash table size (power of 2) | |
| 18 | * CANDIDATE_CAPACITY — max candidate pairs | |
| 19 | * CANDIDATE_HT_CAPACITY — candidate pair hash table size (power of 2) | |
| 20 | * | |
| 21 | * Appended after gpu-hashtable.cl, gpu-atomspace.cl at load time. | |
| 22 | * (gpu-sections.cl optional — only needed for full pipeline tests) | |
| 23 | */ | |
| 24 | ||
| 25 | /* ═══════════════════════════════════════════════════════════════ | |
| 26 | * STEP 1: COMPUTE WORD NORMS | |
| 27 | * | |
| 28 | * One thread per section. Atomically adds count² to the word's | |
| 29 | * norm accumulator. After all sections processed: | |
| 30 | * word_norm_sq[w] = Σ_d count(w,d)² | |
| 31 | * | |
| 32 | * where d ranges over all disjuncts for word w. | |
| 33 | * ═══════════════════════════════════════════════════════════════ */ | |
| 34 | ||
| 35 | __kernel void compute_word_norms( | |
| 36 | __global const uint* sec_word, | |
| 37 | __global const double* sec_count, | |
| 38 | __global volatile double* word_norm_sq, | |
| 39 | const uint num_sections) | |
| 40 | { | |
| 41 | uint tid = get_global_id(0); | |
| 42 | if (tid >= num_sections) return; | |
| 43 | ||
| 44 | double count = sec_count[tid]; | |
| 45 | if (count < 0.5) return; | |
| 46 | ||
| 47 | uint word = sec_word[tid]; | |
| 48 | atomic_add_double(&word_norm_sq[word], count * count); | |
| 49 | } | |
| 50 | ||
| 51 | /* ═══════════════════════════════════════════════════════════════ | |
| 52 | * STEP 2: BUILD DISJUNCT REVERSE INDEX (linked list chains) | |
| 53 | * | |
| 54 | * For each unique disjunct_hash, build a linked list of all | |
| 55 | * sections that share that disjunct. Uses lock-free atomic | |
| 56 | * prepend to a per-disjunct chain. | |
| 57 | * | |
| 58 | * Data structures: | |
| 59 | * djh_ht_keys[DJH_HT_CAPACITY] — disjunct hash values | |
| 60 | * djh_ht_values[DJH_HT_CAPACITY] — chain head (section index) | |
| 61 | * sec_chain_next[SECTION_CAPACITY] — per-section next pointer | |
| 62 | * | |
| 63 | * Chain traversal: start at djh_ht_values[slot], follow | |
| 64 | * sec_chain_next[] until HT_EMPTY_VALUE (end of chain). | |
| 65 | * ═══════════════════════════════════════════════════════════════ */ | |
| 66 | ||
| 67 | __kernel void build_disjunct_chains( | |
| 68 | __global const ulong* sec_disjunct_hash, | |
| 69 | __global const double* sec_count, | |
| 70 | __global volatile ulong* djh_ht_keys, | |
| 71 | __global volatile uint* djh_ht_values, | |
| 72 | __global uint* sec_chain_next, | |
| 73 | const uint num_sections) | |
| 74 | { | |
| 75 | uint tid = get_global_id(0); | |
| 76 | if (tid >= num_sections) return; | |
| 77 | ||
| 78 | /* Skip empty sections */ | |
| 79 | if (sec_count[tid] < 0.5) { | |
| 80 | sec_chain_next[tid] = HT_EMPTY_VALUE; | |
| 81 | return; | |
| 82 | } | |
| 83 | ||
| 84 | ulong djh = sec_disjunct_hash[tid]; | |
| 85 | if (djh == HT_EMPTY_KEY) { | |
| 86 | sec_chain_next[tid] = HT_EMPTY_VALUE; | |
| 87 | return; | |
| 88 | } | |
| 89 | ||
| 90 | ulong cap = DJH_HT_CAPACITY; | |
| 91 | ulong mask = cap - 1; | |
| 92 | ulong slot = ht_hash(djh) & mask; | |
| 93 | ||
| 94 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 95 | { | |
| 96 | ulong prev = atom_cmpxchg(&djh_ht_keys[slot], HT_EMPTY_KEY, djh); | |
| 97 | ||
| 98 | if (prev == HT_EMPTY_KEY || prev == djh) | |
| 99 | { | |
| 100 | /* Atomically prepend this section to the chain. | |
| 101 | * atomic_xchg returns the old head (initially HT_EMPTY_VALUE | |
| 102 | * for a fresh slot, which serves as the end-of-chain sentinel). | |
| 103 | * Lock-free: concurrent prepends produce a valid chain. */ | |
| 104 | uint old_head = atomic_xchg(&djh_ht_values[slot], tid); | |
| 105 | sec_chain_next[tid] = old_head; | |
| 106 | return; | |
| 107 | } | |
| 108 | ||
| 109 | slot = (slot + 1) & mask; | |
| 110 | } | |
| 111 | ||
| 112 | /* Hash table full — orphan this section */ | |
| 113 | sec_chain_next[tid] = HT_EMPTY_VALUE; | |
| 114 | } | |
| 115 | ||
| 116 | /* ═══════════════════════════════════════════════════════════════ | |
| 117 | * STEP 3: ACCUMULATE DOT PRODUCTS | |
| 118 | * | |
| 119 | * One thread per section. For each section (word_i, djh_i, count_i): | |
| 120 | * - Look up djh_i in the disjunct reverse index | |
| 121 | * - Walk the chain of sections sharing this disjunct | |
| 122 | * - For each other section (word_j, djh_i, count_j) where word_j != word_i: | |
| 123 | * - Find-or-create candidate pair (word_i, word_j) | |
| 124 | * - Atomically add count_i × count_j to the candidate's dot product | |
| 125 | * | |
| 126 | * Uses canonical ordering (word_i < word_j) to avoid double-counting: | |
| 127 | * each shared disjunct contributes exactly once to each pair's dot product. | |
| 128 | * | |
| 129 | * Candidate pairs are stored in a hash table + pool (same pattern | |
| 130 | * as atom pools from gpu-atomspace.cl). | |
| 131 | * ═══════════════════════════════════════════════════════════════ */ | |
| 132 | ||
| 133 | __kernel void accumulate_dot_products( | |
| 134 | /* Section data */ | |
| 135 | __global const uint* sec_word, | |
| 136 | __global const ulong* sec_disjunct_hash, | |
| 137 | __global const double* sec_count, | |
| 138 | /* Disjunct reverse index */ | |
| 139 | __global const ulong* djh_ht_keys, | |
| 140 | __global const uint* djh_ht_values, | |
| 141 | __global const uint* sec_chain_next, | |
| 142 | /* Candidate hash table */ | |
| 143 | __global volatile ulong* cand_ht_keys, | |
| 144 | __global volatile uint* cand_ht_values, | |
| 145 | /* Candidate pool SoA */ | |
| 146 | __global uint* cand_word_a, | |
| 147 | __global uint* cand_word_b, | |
| 148 | __global volatile double* cand_dot, | |
| 149 | __global volatile uint* cand_next_free, | |
| 150 | /* Size */ | |
| 151 | const uint num_sections) | |
| 152 | { | |
| 153 | uint tid = get_global_id(0); | |
| 154 | if (tid >= num_sections) return; | |
| 155 | ||
| 156 | uint my_word = sec_word[tid]; | |
| 157 | double my_count = sec_count[tid]; | |
| 158 | if (my_count < 0.5) return; | |
| 159 | ||
| 160 | ulong my_djh = sec_disjunct_hash[tid]; | |
| 161 | if (my_djh == HT_EMPTY_KEY) return; | |
| 162 | ||
| 163 | /* ── Look up disjunct in reverse index ── */ | |
| 164 | ||
| 165 | ulong cap = DJH_HT_CAPACITY; | |
| 166 | ulong mask = cap - 1; | |
| 167 | ulong slot = ht_hash(my_djh) & mask; | |
| 168 | uint chain_head = HT_EMPTY_VALUE; | |
| 169 | ||
| 170 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 171 | { | |
| 172 | ulong k = djh_ht_keys[slot]; | |
| 173 | if (k == my_djh) { | |
| 174 | chain_head = djh_ht_values[slot]; | |
| 175 | break; | |
| 176 | } | |
| 177 | if (k == HT_EMPTY_KEY) break; | |
| 178 | slot = (slot + 1) & mask; | |
| 179 | } | |
| 180 | ||
| 181 | if (chain_head == HT_EMPTY_VALUE) return; | |
| 182 | ||
| 183 | /* ── Walk chain, accumulate dot products ── */ | |
| 184 | ||
| 185 | uint cur = chain_head; | |
| 186 | uint safety = 2048; /* max chain length guard */ | |
| 187 | ||
| 188 | while (cur != HT_EMPTY_VALUE && safety > 0) | |
| 189 | { | |
| 190 | safety--; | |
| 191 | ||
| 192 | uint other_word = sec_word[cur]; | |
| 193 | double other_count = sec_count[cur]; | |
| 194 | ||
| 195 | /* Only accumulate once per pair (canonical: my_word < other_word) */ | |
| 196 | if (other_word != my_word && my_word < other_word && other_count >= 0.5) | |
| 197 | { | |
| 198 | /* ── Find or create candidate pair ── */ | |
| 199 | ||
| 200 | uint lo = my_word; /* already < other_word */ | |
| 201 | uint hi = other_word; | |
| 202 | ulong cand_key = ((ulong)lo << 32) | (ulong)hi; | |
| 203 | ||
| 204 | ulong c_cap = CANDIDATE_HT_CAPACITY; | |
| 205 | ulong c_mask = c_cap - 1; | |
| 206 | ulong c_slot = ht_hash(cand_key) & c_mask; | |
| 207 | uint cand_idx = HT_EMPTY_VALUE; | |
| 208 | ||
| 209 | for (uint p = 0; p < HT_MAX_PROBES; p++) | |
| 210 | { | |
| 211 | ulong prev = atom_cmpxchg( | |
| 212 | &cand_ht_keys[c_slot], HT_EMPTY_KEY, cand_key); | |
| 213 | ||
| 214 | if (prev == HT_EMPTY_KEY) | |
| 215 | { | |
| 216 | /* New candidate — allocate from pool */ | |
| 217 | uint idx = atomic_add(cand_next_free, 1U); | |
| 218 | if (idx < CANDIDATE_CAPACITY) { | |
| 219 | cand_word_a[idx] = lo; | |
| 220 | cand_word_b[idx] = hi; | |
| 221 | cand_dot[idx] = 0.0; | |
| 222 | mem_fence(CLK_GLOBAL_MEM_FENCE); | |
| 223 | cand_ht_values[c_slot] = idx; | |
| 224 | cand_idx = idx; | |
| 225 | } | |
| 226 | break; | |
| 227 | } | |
| 228 | if (prev == cand_key) | |
| 229 | { | |
| 230 | /* Existing candidate — spin for value */ | |
| 231 | uint val = cand_ht_values[c_slot]; | |
| 232 | while (val == HT_EMPTY_VALUE) { | |
| 233 | val = cand_ht_values[c_slot]; | |
| 234 | } | |
| 235 | cand_idx = val; | |
| 236 | break; | |
| 237 | } | |
| 238 | ||
| 239 | c_slot = (c_slot + 1) & c_mask; | |
| 240 | } | |
| 241 | ||
| 242 | if (cand_idx != HT_EMPTY_VALUE) | |
| 243 | { | |
| 244 | atomic_add_double(&cand_dot[cand_idx], | |
| 245 | my_count * other_count); | |
| 246 | } | |
| 247 | } | |
| 248 | ||
| 249 | cur = sec_chain_next[cur]; | |
| 250 | } | |
| 251 | } | |
| 252 | ||
| 253 | /* ═══════════════════════════════════════════════════════════════ | |
| 254 | * STEP 4: COMPUTE COSINES | |
| 255 | * | |
| 256 | * One thread per candidate. Converts accumulated dot products | |
| 257 | * to cosine similarity using pre-computed word norms. | |
| 258 | * | |
| 259 | * cosine(w1, w2) = dot(w1, w2) / (||w1|| × ||w2||) | |
| 260 | * ═══════════════════════════════════════════════════════════════ */ | |
| 261 | ||
| 262 | __kernel void compute_cosines( | |
| 263 | __global const uint* cand_word_a, | |
| 264 | __global const uint* cand_word_b, | |
| 265 | __global const double* cand_dot, | |
| 266 | __global double* cand_cosine, | |
| 267 | __global const double* word_norm_sq, | |
| 268 | const uint num_candidates) | |
| 269 | { | |
| 270 | uint tid = get_global_id(0); | |
| 271 | if (tid >= num_candidates) return; | |
| 272 | ||
| 273 | uint wa = cand_word_a[tid]; | |
| 274 | uint wb = cand_word_b[tid]; | |
| 275 | double dot = cand_dot[tid]; | |
| 276 | ||
| 277 | double denom = sqrt(word_norm_sq[wa]) * sqrt(word_norm_sq[wb]); | |
| 278 | cand_cosine[tid] = (denom > 1e-10) ? (dot / denom) : 0.0; | |
| 279 | } | |
| 280 | ||
| 281 | /* ═══════════════════════════════════════════════════════════════ | |
| 282 | * STEP 5: FILTER CANDIDATES | |
| 283 | * | |
| 284 | * Compact candidates above a cosine threshold into contiguous | |
| 285 | * output arrays. Returns (word_a, word_b, cosine) for each | |
| 286 | * passing pair. | |
| 287 | * | |
| 288 | * out_count must be initialized to 0 before kernel launch. | |
| 289 | * ═══════════════════════════════════════════════════════════════ */ | |
| 290 | ||
| 291 | __kernel void filter_candidates( | |
| 292 | __global const uint* cand_word_a, | |
| 293 | __global const uint* cand_word_b, | |
| 294 | __global const double* cand_cosine, | |
| 295 | const uint num_candidates, | |
| 296 | const double threshold, | |
| 297 | __global uint* out_word_a, | |
| 298 | __global uint* out_word_b, | |
| 299 | __global double* out_cosine, | |
| 300 | __global volatile uint* out_count, | |
| 301 | const uint max_output) | |
| 302 | { | |
| 303 | uint tid = get_global_id(0); | |
| 304 | if (tid >= num_candidates) return; | |
| 305 | ||
| 306 | double cos_val = cand_cosine[tid]; | |
| 307 | if (cos_val > threshold) | |
| 308 | { | |
| 309 | uint idx = atomic_add(out_count, 1U); | |
| 310 | if (idx < max_output) { | |
| 311 | out_word_a[idx] = cand_word_a[tid]; | |
| 312 | out_word_b[idx] = cand_word_b[tid]; | |
| 313 | out_cosine[idx] = cos_val; | |
| 314 | } | |
| 315 | } | |
| 316 | } | |
| 317 | ||
| 318 | /* ═══════════════════════════════════════════════════════════════ | |
| 319 | * READBACK: Dump candidate data for verification | |
| 320 | * ═══════════════════════════════════════════════════════════════ */ | |
| 321 | ||
| 322 | __kernel void read_candidates( | |
| 323 | __global const uint* cand_word_a, | |
| 324 | __global const uint* cand_word_b, | |
| 325 | __global const double* cand_dot, | |
| 326 | __global const double* cand_cosine, | |
| 327 | __global uint* out_word_a, | |
| 328 | __global uint* out_word_b, | |
| 329 | __global double* out_dot, | |
| 330 | __global double* out_cosine, | |
| 331 | const uint num_candidates) | |
| 332 | { | |
| 333 | uint tid = get_global_id(0); | |
| 334 | if (tid >= num_candidates) return; | |
| 335 | ||
| 336 | out_word_a[tid] = cand_word_a[tid]; | |
| 337 | out_word_b[tid] = cand_word_b[tid]; | |
| 338 | out_dot[tid] = cand_dot[tid]; | |
| 339 | out_cosine[tid] = cand_cosine[tid]; | |
| 340 | } |
| … | … | @@ -0,0 +1,296 @@ |
| 1 | /* | |
| 2 | * gpu-counting.cl -- Sentence processing and pair counting on GPU | |
| 3 | * | |
| 4 | * Takes batches of sentences (as word pool indices), generates all | |
| 5 | * word pairs within a sliding window, finds-or-creates each pair | |
| 6 | * in the pair pool, and atomically increments counts. | |
| 7 | * | |
| 8 | * This replaces cl-l0-count-pairs! from corpus-learning.scm. | |
| 9 | * | |
| 10 | * Appended after gpu-hashtable.cl and gpu-atomspace.cl at load time. | |
| 11 | */ | |
| 12 | ||
| 13 | /* ═══════════════════════════════════════════════════════════════ | |
| 14 | * SENTENCE PAIR COUNTING | |
| 15 | * | |
| 16 | * One thread per word position. Each thread generates up to | |
| 17 | * WINDOW_SIZE pairs with subsequent words in the same sentence. | |
| 18 | * For each pair: find-or-create + atomic count increment. | |
| 19 | * ═══════════════════════════════════════════════════════════════ */ | |
| 20 | ||
| 21 | /* | |
| 22 | * Process a batch of sentences: count all word pairs within window. | |
| 23 | * | |
| 24 | * Work assignment: one thread per word position across all sentences. | |
| 25 | * Thread i handles the word at flat_words[i], generating pairs with | |
| 26 | * the next WINDOW_SIZE words in the same sentence. | |
| 27 | * | |
| 28 | * Args: | |
| 29 | * flat_words - all sentences concatenated [total_words] | |
| 30 | * sent_offsets - start index of each sentence [num_sentences] | |
| 31 | * sent_lengths - length of each sentence [num_sentences] | |
| 32 | * num_sentences - number of sentences in batch | |
| 33 | * total_words - total words across all sentences | |
| 34 | * window_size - pair window (typically 6) | |
| 35 | * | |
| 36 | * -- Pair pool + hash table -- | |
| 37 | * pht_keys, pht_values - pair hash table | |
| 38 | * pair_word_a, pair_word_b, pair_count, pair_mi, pair_flags | |
| 39 | * pair_next_free | |
| 40 | * | |
| 41 | * -- Word pool (marginals) -- | |
| 42 | * word_count | |
| 43 | * | |
| 44 | * -- Global counter -- | |
| 45 | * total_pair_count - total pairs counted (atomic) | |
| 46 | */ | |
| 47 | __kernel void count_sentence_pairs( | |
| 48 | __global const uint* flat_words, | |
| 49 | __global const uint* sent_offsets, | |
| 50 | __global const uint* sent_lengths, | |
| 51 | const uint num_sentences, | |
| 52 | const uint total_words, | |
| 53 | const uint window_size, | |
| 54 | /* pair hash table */ | |
| 55 | __global volatile ulong* pht_keys, | |
| 56 | __global volatile uint* pht_values, | |
| 57 | /* pair pool SoA */ | |
| 58 | __global uint* pair_word_a, | |
| 59 | __global uint* pair_word_b, | |
| 60 | __global volatile double* pair_count, | |
| 61 | __global double* pair_mi, | |
| 62 | __global volatile uint* pair_flags, | |
| 63 | __global volatile uint* pair_next_free, | |
| 64 | /* word pool */ | |
| 65 | __global volatile double* word_count, | |
| 66 | /* global counter */ | |
| 67 | __global volatile uint* total_pair_count) | |
| 68 | { | |
| 69 | uint tid = get_global_id(0); | |
| 70 | if (tid >= total_words) return; | |
| 71 | ||
| 72 | uint word_i = flat_words[tid]; | |
| 73 | ||
| 74 | /* Find which sentence this word belongs to (linear scan — sentences | |
| 75 | * are small, typically <100 per batch, so this is fast) */ | |
| 76 | uint sent_idx = 0; | |
| 77 | for (uint s = 0; s < num_sentences; s++) { | |
| 78 | if (tid >= sent_offsets[s] && | |
| 79 | tid < sent_offsets[s] + sent_lengths[s]) { | |
| 80 | sent_idx = s; | |
| 81 | break; | |
| 82 | } | |
| 83 | } | |
| 84 | ||
| 85 | uint sent_start = sent_offsets[sent_idx]; | |
| 86 | uint sent_len = sent_lengths[sent_idx]; | |
| 87 | uint pos_in_sent = tid - sent_start; | |
| 88 | ||
| 89 | /* Generate pairs with subsequent words within window */ | |
| 90 | uint max_j = min(pos_in_sent + window_size, sent_len - 1); | |
| 91 | ||
| 92 | for (uint j = pos_in_sent + 1; j <= max_j; j++) | |
| 93 | { | |
| 94 | uint word_j = flat_words[sent_start + j]; | |
| 95 | ||
| 96 | /* Skip self-pairs */ | |
| 97 | if (word_i == word_j) continue; | |
| 98 | ||
| 99 | /* ── Find or create pair ── */ | |
| 100 | uint lo = min(word_i, word_j); | |
| 101 | uint hi = max(word_i, word_j); | |
| 102 | ulong key = ((ulong)lo << 32) | (ulong)hi; | |
| 103 | ||
| 104 | ulong ht_cap = PAIR_HT_CAPACITY; | |
| 105 | ulong mask = ht_cap - 1; | |
| 106 | ulong slot = ht_hash(key) & mask; | |
| 107 | uint pair_idx = HT_EMPTY_VALUE; | |
| 108 | ||
| 109 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 110 | { | |
| 111 | ulong prev = atom_cmpxchg(&pht_keys[slot], HT_EMPTY_KEY, key); | |
| 112 | ||
| 113 | if (prev == HT_EMPTY_KEY) | |
| 114 | { | |
| 115 | /* New pair — allocate from pool */ | |
| 116 | uint idx = atomic_add(pair_next_free, 1U); | |
| 117 | if (idx < PAIR_CAPACITY) { | |
| 118 | pair_word_a[idx] = lo; | |
| 119 | pair_word_b[idx] = hi; | |
| 120 | pair_count[idx] = 0.0; | |
| 121 | pair_mi[idx] = 0.0; | |
| 122 | pair_flags[idx] = 0; | |
| 123 | mem_fence(CLK_GLOBAL_MEM_FENCE); | |
| 124 | pht_values[slot] = idx; | |
| 125 | pair_idx = idx; | |
| 126 | } | |
| 127 | break; | |
| 128 | } | |
| 129 | if (prev == key) | |
| 130 | { | |
| 131 | /* Existing pair — spin for value */ | |
| 132 | uint val = pht_values[slot]; | |
| 133 | while (val == HT_EMPTY_VALUE) { | |
| 134 | val = pht_values[slot]; | |
| 135 | } | |
| 136 | pair_idx = val; | |
| 137 | break; | |
| 138 | } | |
| 139 | ||
| 140 | slot = (slot + 1) & mask; | |
| 141 | } | |
| 142 | ||
| 143 | if (pair_idx == HT_EMPTY_VALUE) continue; | |
| 144 | ||
| 145 | /* ── Increment counts ── */ | |
| 146 | ||
| 147 | /* Pair count */ | |
| 148 | atomic_add_double(&pair_count[pair_idx], 1.0); | |
| 149 | ||
| 150 | /* Word marginals (both words) */ | |
| 151 | atomic_add_double(&word_count[word_i], 1.0); | |
| 152 | atomic_add_double(&word_count[word_j], 1.0); | |
| 153 | ||
| 154 | /* Mark dirty */ | |
| 155 | pair_flags[pair_idx] = 1; | |
| 156 | ||
| 157 | /* Total count */ | |
| 158 | atomic_add(total_pair_count, 1U); | |
| 159 | } | |
| 160 | } | |
| 161 | ||
| 162 | /* ═══════════════════════════════════════════════════════════════ | |
| 163 | * BATCH SENTENCE PROCESSING — BINARY SEARCH VARIANT | |
| 164 | * | |
| 165 | * For large batches (1000+ sentences), uses binary search instead | |
| 166 | * of linear scan to find which sentence a word belongs to. | |
| 167 | * ═══════════════════════════════════════════════════════════════ */ | |
| 168 | ||
| 169 | __kernel void count_sentence_pairs_large( | |
| 170 | __global const uint* flat_words, | |
| 171 | __global const uint* sent_offsets, | |
| 172 | __global const uint* sent_lengths, | |
| 173 | const uint num_sentences, | |
| 174 | const uint total_words, | |
| 175 | const uint window_size, | |
| 176 | __global volatile ulong* pht_keys, | |
| 177 | __global volatile uint* pht_values, | |
| 178 | __global uint* pair_word_a, | |
| 179 | __global uint* pair_word_b, | |
| 180 | __global volatile double* pair_count, | |
| 181 | __global double* pair_mi, | |
| 182 | __global volatile uint* pair_flags, | |
| 183 | __global volatile uint* pair_next_free, | |
| 184 | __global volatile double* word_count, | |
| 185 | __global volatile uint* total_pair_count) | |
| 186 | { | |
| 187 | uint tid = get_global_id(0); | |
| 188 | if (tid >= total_words) return; | |
| 189 | ||
| 190 | uint word_i = flat_words[tid]; | |
| 191 | ||
| 192 | /* Binary search for sentence index */ | |
| 193 | uint lo_s = 0, hi_s = num_sentences; | |
| 194 | while (lo_s < hi_s) { | |
| 195 | uint mid = (lo_s + hi_s) / 2; | |
| 196 | if (sent_offsets[mid] + sent_lengths[mid] <= tid) | |
| 197 | lo_s = mid + 1; | |
| 198 | else | |
| 199 | hi_s = mid; | |
| 200 | } | |
| 201 | uint sent_idx = lo_s; | |
| 202 | if (sent_idx >= num_sentences) return; | |
| 203 | ||
| 204 | uint sent_start = sent_offsets[sent_idx]; | |
| 205 | uint sent_len = sent_lengths[sent_idx]; | |
| 206 | uint pos_in_sent = tid - sent_start; | |
| 207 | ||
| 208 | /* Verify we're actually in this sentence */ | |
| 209 | if (pos_in_sent >= sent_len) return; | |
| 210 | ||
| 211 | uint max_j = min(pos_in_sent + window_size, sent_len - 1); | |
| 212 | ||
| 213 | for (uint j = pos_in_sent + 1; j <= max_j; j++) | |
| 214 | { | |
| 215 | uint word_j = flat_words[sent_start + j]; | |
| 216 | if (word_i == word_j) continue; | |
| 217 | ||
| 218 | uint lo = min(word_i, word_j); | |
| 219 | uint hi = max(word_i, word_j); | |
| 220 | ulong key = ((ulong)lo << 32) | (ulong)hi; | |
| 221 | ||
| 222 | ulong ht_cap = PAIR_HT_CAPACITY; | |
| 223 | ulong mask = ht_cap - 1; | |
| 224 | ulong slot = ht_hash(key) & mask; | |
| 225 | uint pair_idx = HT_EMPTY_VALUE; | |
| 226 | ||
| 227 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 228 | { | |
| 229 | ulong prev = atom_cmpxchg(&pht_keys[slot], HT_EMPTY_KEY, key); | |
| 230 | ||
| 231 | if (prev == HT_EMPTY_KEY) | |
| 232 | { | |
| 233 | uint idx = atomic_add(pair_next_free, 1U); | |
| 234 | if (idx < PAIR_CAPACITY) { | |
| 235 | pair_word_a[idx] = lo; | |
| 236 | pair_word_b[idx] = hi; | |
| 237 | pair_count[idx] = 0.0; | |
| 238 | pair_mi[idx] = 0.0; | |
| 239 | pair_flags[idx] = 0; | |
| 240 | mem_fence(CLK_GLOBAL_MEM_FENCE); | |
| 241 | pht_values[slot] = idx; | |
| 242 | pair_idx = idx; | |
| 243 | } | |
| 244 | break; | |
| 245 | } | |
| 246 | if (prev == key) | |
| 247 | { | |
| 248 | uint val = pht_values[slot]; | |
| 249 | while (val == HT_EMPTY_VALUE) { | |
| 250 | val = pht_values[slot]; | |
| 251 | } | |
| 252 | pair_idx = val; | |
| 253 | break; | |
| 254 | } | |
| 255 | ||
| 256 | slot = (slot + 1) & mask; | |
| 257 | } | |
| 258 | ||
| 259 | if (pair_idx == HT_EMPTY_VALUE) continue; | |
| 260 | ||
| 261 | atomic_add_double(&pair_count[pair_idx], 1.0); | |
| 262 | atomic_add_double(&word_count[word_i], 1.0); | |
| 263 | atomic_add_double(&word_count[word_j], 1.0); | |
| 264 | pair_flags[pair_idx] = 1; | |
| 265 | atomic_add(total_pair_count, 1U); | |
| 266 | } | |
| 267 | } | |
| 268 | ||
| 269 | /* ═══════════════════════════════════════════════════════════════ | |
| 270 | * READBACK: Dump pair data for verification | |
| 271 | * | |
| 272 | * Reads pair pool entries into flat output arrays. | |
| 273 | * ═══════════════════════════════════════════════════════════════ */ | |
| 274 | ||
| 275 | __kernel void read_pairs( | |
| 276 | __global const uint* pair_word_a, | |
| 277 | __global const uint* pair_word_b, | |
| 278 | __global const double* pair_count, | |
| 279 | __global const double* pair_mi, | |
| 280 | __global const uint* pair_flags, | |
| 281 | __global uint* out_word_a, | |
| 282 | __global uint* out_word_b, | |
| 283 | __global double* out_count, | |
| 284 | __global double* out_mi, | |
| 285 | __global uint* out_flags, | |
| 286 | const uint num_pairs) | |
| 287 | { | |
| 288 | uint tid = get_global_id(0); | |
| 289 | if (tid >= num_pairs) return; | |
| 290 | ||
| 291 | out_word_a[tid] = pair_word_a[tid]; | |
| 292 | out_word_b[tid] = pair_word_b[tid]; | |
| 293 | out_count[tid] = pair_count[tid]; | |
| 294 | out_mi[tid] = pair_mi[tid]; | |
| 295 | out_flags[tid] = pair_flags[tid]; | |
| 296 | } |
| … | … | @@ -0,0 +1,303 @@ |
| 1 | /* | |
| 2 | * gpu-hashtable.cl -- Lock-free GPU hash table using linear probing | |
| 3 | * | |
| 4 | * Open-addressing hash table for mapping 64-bit keys to 32-bit values. | |
| 5 | * Designed for GPU-resident AtomSpace: maps atom content hashes to | |
| 6 | * pool indices (slots in flat SoA arrays). | |
| 7 | * | |
| 8 | * Based on: nosferalatu/SimpleGPUHashTable (CUDA) and | |
| 9 | * LANL/CompactHash (OpenCL) | |
| 10 | * | |
| 11 | * Key properties: | |
| 12 | * - Lock-free concurrent insert/lookup via atomic CAS | |
| 13 | * - Linear probing with power-of-two table size | |
| 14 | * - 64-bit keys (atom content hashes) | |
| 15 | * - 32-bit values (pool indices) | |
| 16 | * - Sentinel key 0xFFFFFFFFFFFFFFFF marks empty slots | |
| 17 | * - Sentinel value 0xFFFFFFFF marks deleted entries | |
| 18 | * - 50% load factor recommended (trade memory for speed) | |
| 19 | * | |
| 20 | * Requires: cl_khr_int64_base_atomics | |
| 21 | */ | |
| 22 | ||
| 23 | #pragma OPENCL EXTENSION cl_khr_int64_base_atomics : enable | |
| 24 | ||
| 25 | /* ─── Constants ─── */ | |
| 26 | ||
| 27 | /* Empty slot sentinel — no valid atom hash should equal this */ | |
| 28 | #define HT_EMPTY_KEY 0xFFFFFFFFFFFFFFFFUL | |
| 29 | /* Deleted value sentinel — key stays, value marks as deleted */ | |
| 30 | #define HT_EMPTY_VALUE 0xFFFFFFFFU | |
| 31 | /* Max probes before giving up (prevents infinite loops on full table) */ | |
| 32 | #define HT_MAX_PROBES 4096 | |
| 33 | ||
| 34 | /* ─── Hash function ─── */ | |
| 35 | ||
| 36 | /* | |
| 37 | * 64-bit finalizer from splitmix64 / Murmur3. | |
| 38 | * Produces well-distributed hashes from sequential or clustered keys. | |
| 39 | */ | |
| 40 | inline ulong ht_hash(ulong key) | |
| 41 | { | |
| 42 | key ^= key >> 30; | |
| 43 | key *= 0xBF58476D1CE4E5B9UL; | |
| 44 | key ^= key >> 27; | |
| 45 | key *= 0x94D049BB133111EBUL; | |
| 46 | key ^= key >> 31; | |
| 47 | return key; | |
| 48 | } | |
| 49 | ||
| 50 | /* ─── Insert ─── */ | |
| 51 | ||
| 52 | /* | |
| 53 | * Insert key-value pairs into the hash table. | |
| 54 | * | |
| 55 | * Each work-item handles one (key, value) pair. | |
| 56 | * Uses atomic CAS on the key slot: if the slot is empty or already | |
| 57 | * contains our key, we write the value. Otherwise linear-probe forward. | |
| 58 | * | |
| 59 | * Args: | |
| 60 | * table_keys - key array [capacity] (ulong) | |
| 61 | * table_values - value array [capacity] (uint) | |
| 62 | * capacity - table size (must be power of 2) | |
| 63 | * in_keys - keys to insert [num_items] | |
| 64 | * in_values - values to insert [num_items] | |
| 65 | * num_items - number of items to insert | |
| 66 | */ | |
| 67 | __kernel void ht_insert( | |
| 68 | __global volatile ulong* table_keys, | |
| 69 | __global uint* table_values, | |
| 70 | const ulong capacity, | |
| 71 | __global const ulong* in_keys, | |
| 72 | __global const uint* in_values, | |
| 73 | const uint num_items) | |
| 74 | { | |
| 75 | uint tid = get_global_id(0); | |
| 76 | if (tid >= num_items) return; | |
| 77 | ||
| 78 | ulong key = in_keys[tid]; | |
| 79 | uint value = in_values[tid]; | |
| 80 | ||
| 81 | /* Don't insert the sentinel key */ | |
| 82 | if (key == HT_EMPTY_KEY) return; | |
| 83 | ||
| 84 | ulong mask = capacity - 1; /* power-of-2 masking */ | |
| 85 | ulong slot = ht_hash(key) & mask; | |
| 86 | ||
| 87 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 88 | { | |
| 89 | /* Try to claim this slot with our key */ | |
| 90 | ulong prev = atom_cmpxchg( | |
| 91 | &table_keys[slot], HT_EMPTY_KEY, key); | |
| 92 | ||
| 93 | if (prev == HT_EMPTY_KEY || prev == key) | |
| 94 | { | |
| 95 | /* Slot was empty or already ours — write value */ | |
| 96 | table_values[slot] = value; | |
| 97 | return; | |
| 98 | } | |
| 99 | ||
| 100 | /* Slot taken by another key — linear probe */ | |
| 101 | slot = (slot + 1) & mask; | |
| 102 | } | |
| 103 | /* Table full or too many collisions — item not inserted */ | |
| 104 | } | |
| 105 | ||
| 106 | /* ─── Lookup ─── */ | |
| 107 | ||
| 108 | /* | |
| 109 | * Look up keys in the hash table, returning their values. | |
| 110 | * | |
| 111 | * Each work-item looks up one key. If found, writes the value | |
| 112 | * to out_values[tid]. If not found, writes HT_EMPTY_VALUE. | |
| 113 | * | |
| 114 | * Args: | |
| 115 | * table_keys - key array [capacity] (ulong) | |
| 116 | * table_values - value array [capacity] (uint) | |
| 117 | * capacity - table size (must be power of 2) | |
| 118 | * query_keys - keys to look up [num_queries] | |
| 119 | * out_values - results written here [num_queries] | |
| 120 | * num_queries - number of lookups | |
| 121 | */ | |
| 122 | __kernel void ht_lookup( | |
| 123 | __global const ulong* table_keys, | |
| 124 | __global const uint* table_values, | |
| 125 | const ulong capacity, | |
| 126 | __global const ulong* query_keys, | |
| 127 | __global uint* out_values, | |
| 128 | const uint num_queries) | |
| 129 | { | |
| 130 | uint tid = get_global_id(0); | |
| 131 | if (tid >= num_queries) return; | |
| 132 | ||
| 133 | ulong key = query_keys[tid]; | |
| 134 | ulong mask = capacity - 1; | |
| 135 | ulong slot = ht_hash(key) & mask; | |
| 136 | ||
| 137 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 138 | { | |
| 139 | ulong slot_key = table_keys[slot]; | |
| 140 | ||
| 141 | if (slot_key == key) | |
| 142 | { | |
| 143 | /* Found — read value */ | |
| 144 | out_values[tid] = table_values[slot]; | |
| 145 | return; | |
| 146 | } | |
| 147 | if (slot_key == HT_EMPTY_KEY) | |
| 148 | { | |
| 149 | /* Empty slot — key not in table */ | |
| 150 | out_values[tid] = HT_EMPTY_VALUE; | |
| 151 | return; | |
| 152 | } | |
| 153 | ||
| 154 | /* Different key — linear probe */ | |
| 155 | slot = (slot + 1) & mask; | |
| 156 | } | |
| 157 | /* Max probes exhausted — not found */ | |
| 158 | out_values[tid] = HT_EMPTY_VALUE; | |
| 159 | } | |
| 160 | ||
| 161 | /* ─── Delete ─── */ | |
| 162 | ||
| 163 | /* | |
| 164 | * Delete keys from the hash table. | |
| 165 | * | |
| 166 | * Tombstone deletion: key stays in table, value set to HT_EMPTY_VALUE. | |
| 167 | * Preserves probe chains for other keys. Deleted slots are NOT reused | |
| 168 | * for new inserts (keeps the algorithm simple). | |
| 169 | * | |
| 170 | * Args: | |
| 171 | * table_keys - key array [capacity] (ulong) | |
| 172 | * table_values - value array [capacity] (uint) | |
| 173 | * capacity - table size (must be power of 2) | |
| 174 | * del_keys - keys to delete [num_deletes] | |
| 175 | * num_deletes - number of deletions | |
| 176 | */ | |
| 177 | __kernel void ht_delete( | |
| 178 | __global const ulong* table_keys, | |
| 179 | __global uint* table_values, | |
| 180 | const ulong capacity, | |
| 181 | __global const ulong* del_keys, | |
| 182 | const uint num_deletes) | |
| 183 | { | |
| 184 | uint tid = get_global_id(0); | |
| 185 | if (tid >= num_deletes) return; | |
| 186 | ||
| 187 | ulong key = del_keys[tid]; | |
| 188 | ulong mask = capacity - 1; | |
| 189 | ulong slot = ht_hash(key) & mask; | |
| 190 | ||
| 191 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 192 | { | |
| 193 | ulong slot_key = table_keys[slot]; | |
| 194 | ||
| 195 | if (slot_key == key) | |
| 196 | { | |
| 197 | /* Found — tombstone the value */ | |
| 198 | table_values[slot] = HT_EMPTY_VALUE; | |
| 199 | return; | |
| 200 | } | |
| 201 | if (slot_key == HT_EMPTY_KEY) | |
| 202 | { | |
| 203 | /* Not in table */ | |
| 204 | return; | |
| 205 | } | |
| 206 | ||
| 207 | slot = (slot + 1) & mask; | |
| 208 | } | |
| 209 | } | |
| 210 | ||
| 211 | /* ─── Insert-or-increment ─── */ | |
| 212 | ||
| 213 | /* | |
| 214 | * Insert key with value, or atomically increment existing value. | |
| 215 | * This is the core operation for AtomSpace counting: if the pair | |
| 216 | * atom already exists, increment its count. Otherwise create it. | |
| 217 | * | |
| 218 | * IMPORTANT: table_values must be initialized to 0 (not 0xFF) | |
| 219 | * before using this kernel. Both the "new slot" and "existing slot" | |
| 220 | * paths use atomic_add(1), which avoids the race between CAS on | |
| 221 | * the key and write to the value. | |
| 222 | * | |
| 223 | * Args: | |
| 224 | * table_keys - key array [capacity] (ulong) | |
| 225 | * table_values - value array [capacity] (uint, init to 0) | |
| 226 | * capacity - table size (must be power of 2) | |
| 227 | * in_keys - keys to insert/increment [num_items] | |
| 228 | * num_items - number of items | |
| 229 | */ | |
| 230 | __kernel void ht_insert_or_increment( | |
| 231 | __global volatile ulong* table_keys, | |
| 232 | __global volatile uint* table_values, | |
| 233 | const ulong capacity, | |
| 234 | __global const ulong* in_keys, | |
| 235 | const uint num_items) | |
| 236 | { | |
| 237 | uint tid = get_global_id(0); | |
| 238 | if (tid >= num_items) return; | |
| 239 | ||
| 240 | ulong key = in_keys[tid]; | |
| 241 | if (key == HT_EMPTY_KEY) return; | |
| 242 | ||
| 243 | ulong mask = capacity - 1; | |
| 244 | ulong slot = ht_hash(key) & mask; | |
| 245 | ||
| 246 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 247 | { | |
| 248 | ulong prev = atom_cmpxchg( | |
| 249 | &table_keys[slot], HT_EMPTY_KEY, key); | |
| 250 | ||
| 251 | if (prev == HT_EMPTY_KEY || prev == key) | |
| 252 | { | |
| 253 | /* Slot is ours (new or existing) — atomic increment. | |
| 254 | * Both paths do the same thing: no race between | |
| 255 | * key placement and value update. */ | |
| 256 | atomic_add(&table_values[slot], 1U); | |
| 257 | return; | |
| 258 | } | |
| 259 | ||
| 260 | slot = (slot + 1) & mask; | |
| 261 | } | |
| 262 | } | |
| 263 | ||
| 264 | /* ─── Iterate (compact non-empty entries) ─── */ | |
| 265 | ||
| 266 | /* | |
| 267 | * Collect all non-empty, non-deleted entries into output arrays. | |
| 268 | * Uses atomic counter to pack results contiguously. | |
| 269 | * | |
| 270 | * Args: | |
| 271 | * table_keys - key array [capacity] (ulong) | |
| 272 | * table_values - value array [capacity] (uint) | |
| 273 | * capacity - table size | |
| 274 | * out_keys - output keys [max_output] | |
| 275 | * out_values - output values [max_output] | |
| 276 | * out_count - atomic counter (single uint, initialized to 0) | |
| 277 | * max_output - max entries to output | |
| 278 | */ | |
| 279 | __kernel void ht_iterate( | |
| 280 | __global const ulong* table_keys, | |
| 281 | __global const uint* table_values, | |
| 282 | const ulong capacity, | |
| 283 | __global ulong* out_keys, | |
| 284 | __global uint* out_values, | |
| 285 | __global uint* out_count, | |
| 286 | const uint max_output) | |
| 287 | { | |
| 288 | uint tid = get_global_id(0); | |
| 289 | if (tid >= capacity) return; | |
| 290 | ||
| 291 | ulong key = table_keys[tid]; | |
| 292 | uint val = table_values[tid]; | |
| 293 | ||
| 294 | if (key != HT_EMPTY_KEY && val != HT_EMPTY_VALUE) | |
| 295 | { | |
| 296 | uint idx = atomic_add(out_count, 1U); | |
| 297 | if (idx < max_output) | |
| 298 | { | |
| 299 | out_keys[idx] = key; | |
| 300 | out_values[idx] = val; | |
| 301 | } | |
| 302 | } | |
| 303 | } |
| … | … | @@ -0,0 +1,224 @@ |
| 1 | /* | |
| 2 | * gpu-mi.cl -- MI computation on GPU-resident atom pools | |
| 3 | * | |
| 4 | * Computes mutual information directly from pair pool and word pool | |
| 5 | * buffers already resident in GPU memory. No CPU↔GPU data transfer | |
| 6 | * needed — the counting kernel (gpu-counting.cl) populates the same | |
| 7 | * buffers that this kernel reads. | |
| 8 | * | |
| 9 | * MI(x,y) = log2(count(x,y) * N / (count(x,*) * count(*,y))) | |
| 10 | * | |
| 11 | * Where: | |
| 12 | * count(x,y) = pair_count[pair_idx] | |
| 13 | * count(x,*) = word_count[pair_word_a[pair_idx]] (left marginal) | |
| 14 | * count(*,y) = word_count[pair_word_b[pair_idx]] (right marginal) | |
| 15 | * N = total pair observations (passed as scalar arg) | |
| 16 | * | |
| 17 | * Appended after gpu-hashtable.cl, gpu-atomspace.cl, gpu-counting.cl | |
| 18 | * at load time. | |
| 19 | */ | |
| 20 | ||
| 21 | /* ═══════════════════════════════════════════════════════════════ | |
| 22 | * COMPUTE MI FOR ALL PAIRS | |
| 23 | * | |
| 24 | * One thread per pair in the pool. Reads pair count and word | |
| 25 | * marginals directly from GPU-resident SoA arrays. | |
| 26 | * | |
| 27 | * Pairs with count < 1 get MI = 0. | |
| 28 | * ═══════════════════════════════════════════════════════════════ */ | |
| 29 | ||
| 30 | __kernel void compute_mi_resident( | |
| 31 | __global const double* pair_count, | |
| 32 | __global const uint* pair_word_a, | |
| 33 | __global const uint* pair_word_b, | |
| 34 | __global double* pair_mi, | |
| 35 | __global const double* word_count, | |
| 36 | const double n_total, | |
| 37 | const uint num_pairs) | |
| 38 | { | |
| 39 | uint tid = get_global_id(0); | |
| 40 | if (tid >= num_pairs) return; | |
| 41 | ||
| 42 | double count = pair_count[tid]; | |
| 43 | if (count < 1.0) { | |
| 44 | pair_mi[tid] = 0.0; | |
| 45 | return; | |
| 46 | } | |
| 47 | ||
| 48 | uint wa = pair_word_a[tid]; | |
| 49 | uint wb = pair_word_b[tid]; | |
| 50 | double left = word_count[wa]; | |
| 51 | double right = word_count[wb]; | |
| 52 | ||
| 53 | /* MI = log2(count * N / (left * right)) | |
| 54 | * = (ln(count) + ln(N) - ln(left) - ln(right)) / ln(2) */ | |
| 55 | double eps = 1e-10; | |
| 56 | left = fmax(left, eps); | |
| 57 | right = fmax(right, eps); | |
| 58 | double n = fmax(n_total, eps); | |
| 59 | ||
| 60 | double log2_factor = 1.4426950408889634; | |
| 61 | double mi = (log(count) + log(n) - log(left) - log(right)) * log2_factor; | |
| 62 | ||
| 63 | pair_mi[tid] = mi; | |
| 64 | } | |
| 65 | ||
| 66 | /* ═══════════════════════════════════════════════════════════════ | |
| 67 | * COMPUTE MI FOR DIRTY PAIRS ONLY | |
| 68 | * | |
| 69 | * Same as above but only processes pairs where flags == 1. | |
| 70 | * Clears the dirty flag after computation. | |
| 71 | * | |
| 72 | * Use this for incremental MI updates after new sentences | |
| 73 | * are counted — avoids recomputing all 2M+ pairs. | |
| 74 | * ═══════════════════════════════════════════════════════════════ */ | |
| 75 | ||
| 76 | __kernel void compute_mi_dirty( | |
| 77 | __global const double* pair_count, | |
| 78 | __global const uint* pair_word_a, | |
| 79 | __global const uint* pair_word_b, | |
| 80 | __global double* pair_mi, | |
| 81 | __global volatile uint* pair_flags, | |
| 82 | __global const double* word_count, | |
| 83 | const double n_total, | |
| 84 | const uint num_pairs) | |
| 85 | { | |
| 86 | uint tid = get_global_id(0); | |
| 87 | if (tid >= num_pairs) return; | |
| 88 | ||
| 89 | if (pair_flags[tid] != 1) return; | |
| 90 | ||
| 91 | double count = pair_count[tid]; | |
| 92 | if (count < 1.0) { | |
| 93 | pair_mi[tid] = 0.0; | |
| 94 | pair_flags[tid] = 0; | |
| 95 | return; | |
| 96 | } | |
| 97 | ||
| 98 | uint wa = pair_word_a[tid]; | |
| 99 | uint wb = pair_word_b[tid]; | |
| 100 | double left = word_count[wa]; | |
| 101 | double right = word_count[wb]; | |
| 102 | ||
| 103 | double eps = 1e-10; | |
| 104 | left = fmax(left, eps); | |
| 105 | right = fmax(right, eps); | |
| 106 | double n = fmax(n_total, eps); | |
| 107 | ||
| 108 | double log2_factor = 1.4426950408889634; | |
| 109 | double mi = (log(count) + log(n) - log(left) - log(right)) * log2_factor; | |
| 110 | ||
| 111 | pair_mi[tid] = mi; | |
| 112 | pair_flags[tid] = 0; | |
| 113 | } | |
| 114 | ||
| 115 | /* ═══════════════════════════════════════════════════════════════ | |
| 116 | * MI STATISTICS | |
| 117 | * | |
| 118 | * Count pairs by MI value. Useful for diagnostics and | |
| 119 | * determining warm-start thresholds. | |
| 120 | * | |
| 121 | * Outputs (all single-uint atomic counters, init to 0): | |
| 122 | * count_nonzero — pairs with count > 0 | |
| 123 | * count_positive_mi — pairs with MI > 0 | |
| 124 | * count_above_thresh — pairs with MI > threshold | |
| 125 | * ═══════════════════════════════════════════════════════════════ */ | |
| 126 | ||
| 127 | __kernel void mi_stats( | |
| 128 | __global const double* pair_mi, | |
| 129 | __global const double* pair_count, | |
| 130 | const uint num_pairs, | |
| 131 | const double threshold, | |
| 132 | __global volatile uint* count_nonzero, | |
| 133 | __global volatile uint* count_positive_mi, | |
| 134 | __global volatile uint* count_above_thresh) | |
| 135 | { | |
| 136 | uint tid = get_global_id(0); | |
| 137 | if (tid >= num_pairs) return; | |
| 138 | ||
| 139 | double cnt = pair_count[tid]; | |
| 140 | if (cnt < 0.5) return; | |
| 141 | ||
| 142 | atomic_add(count_nonzero, 1U); | |
| 143 | ||
| 144 | double mi = pair_mi[tid]; | |
| 145 | if (mi > 0.0) | |
| 146 | atomic_add(count_positive_mi, 1U); | |
| 147 | if (mi > threshold) | |
| 148 | atomic_add(count_above_thresh, 1U); | |
| 149 | } | |
| 150 | ||
| 151 | /* ═══════════════════════════════════════════════════════════════ | |
| 152 | * MI THRESHOLD FILTER | |
| 153 | * | |
| 154 | * Compact pairs above a MI threshold into contiguous output | |
| 155 | * arrays. Returns (pair_index, mi_value) for each passing pair. | |
| 156 | * | |
| 157 | * Use cases: | |
| 158 | * - Warm-start bootstrap (only load high-MI pairs) | |
| 159 | * - Building neighbor index for cosine similarity | |
| 160 | * - Exporting significant pairs for visualization | |
| 161 | * | |
| 162 | * out_count must be initialized to 0 before kernel launch. | |
| 163 | * ═══════════════════════════════════════════════════════════════ */ | |
| 164 | ||
| 165 | __kernel void mi_filter( | |
| 166 | __global const double* pair_mi, | |
| 167 | __global const double* pair_count, | |
| 168 | const uint num_pairs, | |
| 169 | const double mi_threshold, | |
| 170 | __global uint* out_pair_indices, | |
| 171 | __global double* out_mi_values, | |
| 172 | __global volatile uint* out_count, | |
| 173 | const uint max_output) | |
| 174 | { | |
| 175 | uint tid = get_global_id(0); | |
| 176 | if (tid >= num_pairs) return; | |
| 177 | ||
| 178 | double cnt = pair_count[tid]; | |
| 179 | double mi = pair_mi[tid]; | |
| 180 | ||
| 181 | if (cnt > 0.5 && mi > mi_threshold) { | |
| 182 | uint idx = atomic_add(out_count, 1U); | |
| 183 | if (idx < max_output) { | |
| 184 | out_pair_indices[idx] = tid; | |
| 185 | out_mi_values[idx] = mi; | |
| 186 | } | |
| 187 | } | |
| 188 | } | |
| 189 | ||
| 190 | /* ═══════════════════════════════════════════════════════════════ | |
| 191 | * READBACK: Dump pair data with MI for verification | |
| 192 | * | |
| 193 | * Extended version of read_pairs from gpu-counting.cl that | |
| 194 | * also includes word indices and MI values. | |
| 195 | * ═══════════════════════════════════════════════════════════════ */ | |
| 196 | ||
| 197 | __kernel void read_pairs_with_mi( | |
| 198 | __global const uint* pair_word_a, | |
| 199 | __global const uint* pair_word_b, | |
| 200 | __global const double* pair_count, | |
| 201 | __global const double* pair_mi, | |
| 202 | __global const uint* pair_flags, | |
| 203 | __global const double* word_count, | |
| 204 | __global uint* out_word_a, | |
| 205 | __global uint* out_word_b, | |
| 206 | __global double* out_count, | |
| 207 | __global double* out_mi, | |
| 208 | __global double* out_left_marginal, | |
| 209 | __global double* out_right_marginal, | |
| 210 | const uint num_pairs) | |
| 211 | { | |
| 212 | uint tid = get_global_id(0); | |
| 213 | if (tid >= num_pairs) return; | |
| 214 | ||
| 215 | uint wa = pair_word_a[tid]; | |
| 216 | uint wb = pair_word_b[tid]; | |
| 217 | ||
| 218 | out_word_a[tid] = wa; | |
| 219 | out_word_b[tid] = wb; | |
| 220 | out_count[tid] = pair_count[tid]; | |
| 221 | out_mi[tid] = pair_mi[tid]; | |
| 222 | out_left_marginal[tid] = word_count[wa]; | |
| 223 | out_right_marginal[tid] = word_count[wb]; | |
| 224 | } |
| … | … | @@ -0,0 +1,317 @@ |
| 1 | /* | |
| 2 | * gpu-sections.cl -- Section extraction from MST/PMFG edges on GPU | |
| 3 | * | |
| 4 | * Given MST edges for a batch of sentences (as position pairs within | |
| 5 | * each sentence), extract sections (word + disjunct) and increment | |
| 6 | * SectionPool counts. Disjunct hashing is done entirely on GPU — | |
| 7 | * no strings involved. | |
| 8 | * | |
| 9 | * A section = (word, disjunct), where the disjunct is the set of | |
| 10 | * connectors for that word in the parse. A connector = (partner_word, | |
| 11 | * direction), where direction is '+' (right) or '-' (left). | |
| 12 | * | |
| 13 | * On GPU: connector = (word_pool_idx, direction_bit). | |
| 14 | * Disjunct = FNV-1a hash of sorted connector sequence. | |
| 15 | * | |
| 16 | * Appended after gpu-hashtable.cl, gpu-atomspace.cl at load time. | |
| 17 | */ | |
| 18 | ||
| 19 | /* Maximum connectors per word position (degree limit). | |
| 20 | * PMFG on a 20-word sentence has at most 54 edges; per word, | |
| 21 | * planar graph average degree < 6. 32 is very safe. */ | |
| 22 | #define MAX_CONNECTORS 32 | |
| 23 | ||
| 24 | /* Connector direction bits */ | |
| 25 | #define DIR_LEFT 0U /* partner is to the LEFT of this word */ | |
| 26 | #define DIR_RIGHT 1U /* partner is to the RIGHT of this word */ | |
| 27 | ||
| 28 | /* ═══════════════════════════════════════════════════════════════ | |
| 29 | * FNV-1a HASH for disjunct encoding | |
| 30 | * | |
| 31 | * Hash a sorted sequence of (word_pool_idx, direction) pairs | |
| 32 | * into a single 64-bit disjunct hash. | |
| 33 | * ═══════════════════════════════════════════════════════════════ */ | |
| 34 | ||
| 35 | inline ulong fnv1a_init(void) | |
| 36 | { | |
| 37 | return 0xcbf29ce484222325UL; | |
| 38 | } | |
| 39 | ||
| 40 | inline ulong fnv1a_mix(ulong hash, ulong val) | |
| 41 | { | |
| 42 | hash ^= val; | |
| 43 | hash *= 0x100000001b3UL; | |
| 44 | return hash; | |
| 45 | } | |
| 46 | ||
| 47 | inline ulong hash_disjunct(uint* conn_words, uint* conn_dirs, uint count) | |
| 48 | { | |
| 49 | ulong h = fnv1a_init(); | |
| 50 | for (uint i = 0; i < count; i++) { | |
| 51 | /* Encode: (word_pool_idx << 1) | direction_bit */ | |
| 52 | ulong encoded = ((ulong)conn_words[i] << 1) | (ulong)conn_dirs[i]; | |
| 53 | h = fnv1a_mix(h, encoded); | |
| 54 | } | |
| 55 | /* Ensure it's never the hash table sentinel */ | |
| 56 | if (h == HT_EMPTY_KEY) h = 0; | |
| 57 | return h; | |
| 58 | } | |
| 59 | ||
| 60 | /* ═══════════════════════════════════════════════════════════════ | |
| 61 | * INSERTION SORT for connectors | |
| 62 | * | |
| 63 | * Sort by: direction first (LEFT=0 before RIGHT=1), | |
| 64 | * then by word_pool_idx (ascending). | |
| 65 | * | |
| 66 | * This gives a deterministic disjunct representation: | |
| 67 | * all left connectors (sorted by partner) then all right | |
| 68 | * connectors (sorted by partner). | |
| 69 | * ═══════════════════════════════════════════════════════════════ */ | |
| 70 | ||
| 71 | inline void sort_connectors(uint* conn_words, uint* conn_dirs, uint count) | |
| 72 | { | |
| 73 | /* Insertion sort — count is small (typically < 10) */ | |
| 74 | for (uint i = 1; i < count; i++) { | |
| 75 | uint w = conn_words[i]; | |
| 76 | uint d = conn_dirs[i]; | |
| 77 | uint j = i; | |
| 78 | while (j > 0) { | |
| 79 | /* Compare: direction first, then word index */ | |
| 80 | int swap = 0; | |
| 81 | if (conn_dirs[j-1] > d) | |
| 82 | swap = 1; | |
| 83 | else if (conn_dirs[j-1] == d && conn_words[j-1] > w) | |
| 84 | swap = 1; | |
| 85 | ||
| 86 | if (!swap) break; | |
| 87 | ||
| 88 | conn_words[j] = conn_words[j-1]; | |
| 89 | conn_dirs[j] = conn_dirs[j-1]; | |
| 90 | j--; | |
| 91 | } | |
| 92 | conn_words[j] = w; | |
| 93 | conn_dirs[j] = d; | |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | /* ═══════════════════════════════════════════════════════════════ | |
| 98 | * EXTRACT SECTIONS FROM MST EDGES | |
| 99 | * | |
| 100 | * One thread per word position across all sentences. | |
| 101 | * Each thread: | |
| 102 | * 1. Finds its sentence (binary search) | |
| 103 | * 2. Scans MST edges for this sentence | |
| 104 | * 3. Collects connectors (partner word + direction) | |
| 105 | * 4. Sorts connectors deterministically | |
| 106 | * 5. Hashes sorted sequence → disjunct_hash | |
| 107 | * 6. Find-or-create section in SectionPool | |
| 108 | * 7. Atomically increments section count | |
| 109 | * | |
| 110 | * Words with no edges (isolated) produce no section. | |
| 111 | * | |
| 112 | * Args: | |
| 113 | * -- Sentence data -- | |
| 114 | * flat_words - word pool indices [total_words] | |
| 115 | * sent_offsets - start of each sentence in flat_words [num_sentences] | |
| 116 | * sent_lengths - length of each sentence [num_sentences] | |
| 117 | * num_sentences - number of sentences | |
| 118 | * total_words - total words across all sentences | |
| 119 | * | |
| 120 | * -- MST edge data -- | |
| 121 | * edge_p1 - position 1 within sentence [total_edges] | |
| 122 | * edge_p2 - position 2 within sentence [total_edges] | |
| 123 | * edge_offsets - start of each sentence's edges [num_sentences] | |
| 124 | * edge_counts - number of edges per sentence [num_sentences] | |
| 125 | * | |
| 126 | * -- Section pool + hash table -- | |
| 127 | * sht_keys, sht_values - section hash table | |
| 128 | * sec_word - section pool: word indices | |
| 129 | * sec_disjunct_hash - section pool: disjunct hashes | |
| 130 | * sec_count - section pool: counts | |
| 131 | * sec_next_free - section bump allocator | |
| 132 | * | |
| 133 | * -- Stats -- | |
| 134 | * total_sections_created - atomic counter for new sections | |
| 135 | * ═══════════════════════════════════════════════════════════════ */ | |
| 136 | ||
| 137 | __kernel void extract_sections( | |
| 138 | /* sentence data */ | |
| 139 | __global const uint* flat_words, | |
| 140 | __global const uint* sent_offsets, | |
| 141 | __global const uint* sent_lengths, | |
| 142 | const uint num_sentences, | |
| 143 | const uint total_words, | |
| 144 | /* MST edge data */ | |
| 145 | __global const uint* edge_p1, | |
| 146 | __global const uint* edge_p2, | |
| 147 | __global const uint* edge_offsets, | |
| 148 | __global const uint* edge_counts, | |
| 149 | /* section hash table */ | |
| 150 | __global volatile ulong* sht_keys, | |
| 151 | __global volatile uint* sht_values, | |
| 152 | /* section pool SoA */ | |
| 153 | __global uint* sec_word, | |
| 154 | __global ulong* sec_disjunct_hash, | |
| 155 | __global volatile double* sec_count, | |
| 156 | __global volatile uint* sec_next_free, | |
| 157 | /* stats */ | |
| 158 | __global volatile uint* total_sections_created) | |
| 159 | { | |
| 160 | uint tid = get_global_id(0); | |
| 161 | if (tid >= total_words) return; | |
| 162 | ||
| 163 | /* ── Find which sentence this word belongs to (binary search) ── */ | |
| 164 | ||
| 165 | uint lo_s = 0, hi_s = num_sentences; | |
| 166 | while (lo_s < hi_s) { | |
| 167 | uint mid = (lo_s + hi_s) / 2; | |
| 168 | if (sent_offsets[mid] + sent_lengths[mid] <= tid) | |
| 169 | lo_s = mid + 1; | |
| 170 | else | |
| 171 | hi_s = mid; | |
| 172 | } | |
| 173 | uint sent_idx = lo_s; | |
| 174 | if (sent_idx >= num_sentences) return; | |
| 175 | ||
| 176 | uint sent_start = sent_offsets[sent_idx]; | |
| 177 | uint sent_len = sent_lengths[sent_idx]; | |
| 178 | uint pos_in_sent = tid - sent_start; | |
| 179 | ||
| 180 | /* Verify we're in this sentence */ | |
| 181 | if (pos_in_sent >= sent_len) return; | |
| 182 | ||
| 183 | uint my_word = flat_words[tid]; | |
| 184 | ||
| 185 | /* ── Collect connectors from MST edges ── */ | |
| 186 | ||
| 187 | uint conn_words[MAX_CONNECTORS]; | |
| 188 | uint conn_dirs[MAX_CONNECTORS]; | |
| 189 | uint conn_count = 0; | |
| 190 | ||
| 191 | uint e_start = edge_offsets[sent_idx]; | |
| 192 | uint e_count = edge_counts[sent_idx]; | |
| 193 | ||
| 194 | for (uint e = 0; e < e_count && conn_count < MAX_CONNECTORS; e++) | |
| 195 | { | |
| 196 | uint p1 = edge_p1[e_start + e]; | |
| 197 | uint p2 = edge_p2[e_start + e]; | |
| 198 | ||
| 199 | if (p1 == pos_in_sent) | |
| 200 | { | |
| 201 | /* Edge goes from me to p2 → p2 is to the right */ | |
| 202 | uint partner_word = flat_words[sent_start + p2]; | |
| 203 | conn_words[conn_count] = partner_word; | |
| 204 | conn_dirs[conn_count] = (p2 > pos_in_sent) ? DIR_RIGHT : DIR_LEFT; | |
| 205 | conn_count++; | |
| 206 | } | |
| 207 | else if (p2 == pos_in_sent) | |
| 208 | { | |
| 209 | /* Edge goes from p1 to me → p1 is to the left */ | |
| 210 | uint partner_word = flat_words[sent_start + p1]; | |
| 211 | conn_words[conn_count] = partner_word; | |
| 212 | conn_dirs[conn_count] = (p1 < pos_in_sent) ? DIR_LEFT : DIR_RIGHT; | |
| 213 | conn_count++; | |
| 214 | } | |
| 215 | } | |
| 216 | ||
| 217 | /* No connectors = isolated word → no section */ | |
| 218 | if (conn_count == 0) return; | |
| 219 | ||
| 220 | /* ── Sort connectors deterministically ── */ | |
| 221 | ||
| 222 | sort_connectors(conn_words, conn_dirs, conn_count); | |
| 223 | ||
| 224 | /* ── Hash sorted connectors → disjunct_hash ── */ | |
| 225 | ||
| 226 | ulong djh = hash_disjunct(conn_words, conn_dirs, conn_count); | |
| 227 | ||
| 228 | /* ── Find or create section in SectionPool ── */ | |
| 229 | ||
| 230 | ulong key = section_key(my_word, djh); | |
| 231 | ||
| 232 | ulong ht_cap = SECTION_HT_CAPACITY; | |
| 233 | ulong mask = ht_cap - 1; | |
| 234 | ulong slot = ht_hash(key) & mask; | |
| 235 | uint sec_idx = HT_EMPTY_VALUE; | |
| 236 | ||
| 237 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 238 | { | |
| 239 | ulong prev = atom_cmpxchg(&sht_keys[slot], HT_EMPTY_KEY, key); | |
| 240 | ||
| 241 | if (prev == HT_EMPTY_KEY) | |
| 242 | { | |
| 243 | /* New section — allocate from pool */ | |
| 244 | uint idx = atomic_add(sec_next_free, 1U); | |
| 245 | if (idx < SECTION_CAPACITY) { | |
| 246 | sec_word[idx] = my_word; | |
| 247 | sec_disjunct_hash[idx] = djh; | |
| 248 | sec_count[idx] = 0.0; | |
| 249 | mem_fence(CLK_GLOBAL_MEM_FENCE); | |
| 250 | sht_values[slot] = idx; | |
| 251 | sec_idx = idx; | |
| 252 | atomic_add(total_sections_created, 1U); | |
| 253 | } | |
| 254 | break; | |
| 255 | } | |
| 256 | if (prev == key) | |
| 257 | { | |
| 258 | /* Existing section — spin for value */ | |
| 259 | uint val = sht_values[slot]; | |
| 260 | while (val == HT_EMPTY_VALUE) { | |
| 261 | val = sht_values[slot]; | |
| 262 | } | |
| 263 | sec_idx = val; | |
| 264 | break; | |
| 265 | } | |
| 266 | ||
| 267 | slot = (slot + 1) & mask; | |
| 268 | } | |
| 269 | ||
| 270 | if (sec_idx == HT_EMPTY_VALUE) return; | |
| 271 | ||
| 272 | /* ── Increment section count ── */ | |
| 273 | ||
| 274 | atomic_add_double(&sec_count[sec_idx], 1.0); | |
| 275 | } | |
| 276 | ||
| 277 | /* ═══════════════════════════════════════════════════════════════ | |
| 278 | * READBACK: Dump section data for verification | |
| 279 | * | |
| 280 | * Reads section pool entries into flat output arrays. | |
| 281 | * ═══════════════════════════════════════════════════════════════ */ | |
| 282 | ||
| 283 | __kernel void read_sections( | |
| 284 | __global const uint* sec_word, | |
| 285 | __global const ulong* sec_disjunct_hash, | |
| 286 | __global const double* sec_count, | |
| 287 | __global uint* out_word, | |
| 288 | __global ulong* out_disjunct_hash, | |
| 289 | __global double* out_count, | |
| 290 | const uint num_sections) | |
| 291 | { | |
| 292 | uint tid = get_global_id(0); | |
| 293 | if (tid >= num_sections) return; | |
| 294 | ||
| 295 | out_word[tid] = sec_word[tid]; | |
| 296 | out_disjunct_hash[tid] = sec_disjunct_hash[tid]; | |
| 297 | out_count[tid] = sec_count[tid]; | |
| 298 | } | |
| 299 | ||
| 300 | /* ═══════════════════════════════════════════════════════════════ | |
| 301 | * EXTRACT SECTIONS + COUNT PAIRS (combined kernel) | |
| 302 | * | |
| 303 | * Same as extract_sections but also counts section pairs | |
| 304 | * within a window (for Level 1 vocabulary learning). | |
| 305 | * | |
| 306 | * Each word position produces one section. Adjacent sections | |
| 307 | * (within pair_window) form section pairs. Section pair counts | |
| 308 | * are stored via the pair pool (reusing pair_find_or_create | |
| 309 | * with section indices instead of word indices). | |
| 310 | * | |
| 311 | * This is Phase 4's full pipeline: edges → sections → section pairs | |
| 312 | * all in one kernel launch. | |
| 313 | * ═══════════════════════════════════════════════════════════════ */ | |
| 314 | ||
| 315 | /* Note: Section pair counting is deferred to Phase 5, where the | |
| 316 | * section pool is used for cosine similarity. The basic extract_sections | |
| 317 | * kernel above is sufficient for Phase 4. */ |
| … | … | @@ -0,0 +1,232 @@ |
| 1 | /* | |
| 2 | * gpu-substitute.cl -- Class substitution on GPU-resident atom pools | |
| 3 | * | |
| 4 | * After clustering merges words into classes, this kernel scans | |
| 5 | * PairPool and SectionPool, replacing word indices with class IDs. | |
| 6 | * Duplicate pairs (same class pair after substitution) are merged | |
| 7 | * by rebuilding the pair hash table. | |
| 8 | * | |
| 9 | * Pipeline: | |
| 10 | * 1. assign_classes — batch set word_class_id for merged words | |
| 11 | * 2. substitute_pairs — replace word indices with class IDs in pairs | |
| 12 | * 3. rebuild_pair_index — rebuild HT, merge duplicate pairs | |
| 13 | * 4. substitute_section_words — replace section head words with class IDs | |
| 14 | * | |
| 15 | * Appended after gpu-hashtable.cl, gpu-atomspace.cl at load time. | |
| 16 | */ | |
| 17 | ||
| 18 | /* ═══════════════════════════════════════════════════════════════ | |
| 19 | * STEP 1: ASSIGN CLASSES | |
| 20 | * | |
| 21 | * Batch-set word_class_id for a list of words. | |
| 22 | * Called after each clustering round with the new merges. | |
| 23 | * | |
| 24 | * class_id = 0 means "no class" (unclassified). | |
| 25 | * ═══════════════════════════════════════════════════════════════ */ | |
| 26 | ||
| 27 | __kernel void assign_classes( | |
| 28 | __global uint* word_class_id, | |
| 29 | __global const uint* in_word_indices, | |
| 30 | __global const uint* in_class_ids, | |
| 31 | const uint num_assignments) | |
| 32 | { | |
| 33 | uint tid = get_global_id(0); | |
| 34 | if (tid >= num_assignments) return; | |
| 35 | ||
| 36 | uint word_idx = in_word_indices[tid]; | |
| 37 | uint class_id = in_class_ids[tid]; | |
| 38 | if (word_idx < WORD_CAPACITY) | |
| 39 | word_class_id[word_idx] = class_id; | |
| 40 | } | |
| 41 | ||
| 42 | /* ═══════════════════════════════════════════════════════════════ | |
| 43 | * STEP 2: SUBSTITUTE PAIRS | |
| 44 | * | |
| 45 | * One thread per pair. For each pair: | |
| 46 | * - Look up class_id for word_a and word_b | |
| 47 | * - If classified (class_id != 0): replace with class_id | |
| 48 | * - Maintain canonical order (lo < hi) | |
| 49 | * - Self-pairs (both words → same class) are eliminated | |
| 50 | * - Mark changed pairs as dirty for MI recompute | |
| 51 | * | |
| 52 | * After this kernel, the pair hash table is INVALID (keys changed). | |
| 53 | * Must run rebuild_pair_index to restore it. | |
| 54 | * ═══════════════════════════════════════════════════════════════ */ | |
| 55 | ||
| 56 | __kernel void substitute_pairs( | |
| 57 | __global uint* pair_word_a, | |
| 58 | __global uint* pair_word_b, | |
| 59 | __global volatile double* pair_count, | |
| 60 | __global double* pair_mi, | |
| 61 | __global uint* pair_flags, | |
| 62 | __global const uint* word_class_id, | |
| 63 | __global volatile uint* num_changed, | |
| 64 | __global volatile uint* num_eliminated, | |
| 65 | const uint num_pairs) | |
| 66 | { | |
| 67 | uint tid = get_global_id(0); | |
| 68 | if (tid >= num_pairs) return; | |
| 69 | ||
| 70 | double count = pair_count[tid]; | |
| 71 | if (count < 0.5) return; /* skip empty pairs */ | |
| 72 | ||
| 73 | uint wa = pair_word_a[tid]; | |
| 74 | uint wb = pair_word_b[tid]; | |
| 75 | ||
| 76 | uint ca = word_class_id[wa]; | |
| 77 | uint cb = word_class_id[wb]; | |
| 78 | ||
| 79 | uint new_a = (ca != 0) ? ca : wa; | |
| 80 | uint new_b = (cb != 0) ? cb : wb; | |
| 81 | ||
| 82 | /* Self-pair after substitution: both words map to same class → drop */ | |
| 83 | if (new_a == new_b) { | |
| 84 | pair_count[tid] = 0.0; | |
| 85 | pair_mi[tid] = 0.0; | |
| 86 | pair_flags[tid] = 0; | |
| 87 | atomic_add(num_eliminated, 1U); | |
| 88 | return; | |
| 89 | } | |
| 90 | ||
| 91 | /* Canonical order */ | |
| 92 | uint lo = min(new_a, new_b); | |
| 93 | uint hi = max(new_a, new_b); | |
| 94 | ||
| 95 | if (lo != wa || hi != wb) { | |
| 96 | pair_word_a[tid] = lo; | |
| 97 | pair_word_b[tid] = hi; | |
| 98 | pair_flags[tid] = 1; /* dirty: needs MI recompute */ | |
| 99 | atomic_add(num_changed, 1U); | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| 103 | /* ═══════════════════════════════════════════════════════════════ | |
| 104 | * STEP 3: REBUILD PAIR INDEX | |
| 105 | * | |
| 106 | * After substitute_pairs changed pair keys, the pair hash table | |
| 107 | * is stale. This kernel rebuilds it AND merges duplicate pairs. | |
| 108 | * | |
| 109 | * Algorithm: one thread per pair. | |
| 110 | * - Compute key from (word_a, word_b) | |
| 111 | * - CAS into hash table | |
| 112 | * - If we claim the slot: we're the "primary" copy | |
| 113 | * - If slot already has our key: duplicate! | |
| 114 | * → atomically add our count to the primary's count | |
| 115 | * → zero ourselves (mark as merged) | |
| 116 | * | |
| 117 | * IMPORTANT: The pair hash table must be cleared (filled with | |
| 118 | * 0xFF) before running this kernel. | |
| 119 | * ═══════════════════════════════════════════════════════════════ */ | |
| 120 | ||
| 121 | __kernel void rebuild_pair_index( | |
| 122 | __global const uint* pair_word_a, | |
| 123 | __global const uint* pair_word_b, | |
| 124 | __global volatile double* pair_count, | |
| 125 | __global double* pair_mi, | |
| 126 | __global uint* pair_flags, | |
| 127 | __global volatile ulong* pht_keys, | |
| 128 | __global volatile uint* pht_values, | |
| 129 | __global volatile uint* num_merged, | |
| 130 | const uint num_pairs) | |
| 131 | { | |
| 132 | uint tid = get_global_id(0); | |
| 133 | if (tid >= num_pairs) return; | |
| 134 | ||
| 135 | double count = pair_count[tid]; | |
| 136 | if (count < 0.5) return; /* skip empty/eliminated pairs */ | |
| 137 | ||
| 138 | uint wa = pair_word_a[tid]; | |
| 139 | uint wb = pair_word_b[tid]; | |
| 140 | uint lo = min(wa, wb); | |
| 141 | uint hi = max(wa, wb); | |
| 142 | ulong key = ((ulong)lo << 32) | (ulong)hi; | |
| 143 | ||
| 144 | ulong cap = PAIR_HT_CAPACITY; | |
| 145 | ulong mask = cap - 1; | |
| 146 | ulong slot = ht_hash(key) & mask; | |
| 147 | ||
| 148 | for (uint probe = 0; probe < HT_MAX_PROBES; probe++) | |
| 149 | { | |
| 150 | ulong prev = atom_cmpxchg(&pht_keys[slot], HT_EMPTY_KEY, key); | |
| 151 | ||
| 152 | if (prev == HT_EMPTY_KEY) | |
| 153 | { | |
| 154 | /* We claim this slot — we're the primary copy */ | |
| 155 | mem_fence(CLK_GLOBAL_MEM_FENCE); | |
| 156 | pht_values[slot] = tid; | |
| 157 | return; | |
| 158 | } | |
| 159 | if (prev == key) | |
| 160 | { | |
| 161 | /* Duplicate! Merge into the primary. */ | |
| 162 | uint primary = pht_values[slot]; | |
| 163 | while (primary == HT_EMPTY_VALUE) { | |
| 164 | primary = pht_values[slot]; | |
| 165 | } | |
| 166 | ||
| 167 | /* Add our count to primary */ | |
| 168 | atomic_add_double(&pair_count[primary], count); | |
| 169 | ||
| 170 | /* Mark primary as dirty */ | |
| 171 | pair_flags[primary] = 1; | |
| 172 | ||
| 173 | /* Zero ourselves */ | |
| 174 | pair_count[tid] = 0.0; | |
| 175 | pair_mi[tid] = 0.0; | |
| 176 | pair_flags[tid] = 0; | |
| 177 | ||
| 178 | atomic_add(num_merged, 1U); | |
| 179 | return; | |
| 180 | } | |
| 181 | ||
| 182 | slot = (slot + 1) & mask; | |
| 183 | } | |
| 184 | } | |
| 185 | ||
| 186 | /* ═══════════════════════════════════════════════════════════════ | |
| 187 | * STEP 4: SUBSTITUTE SECTION WORDS | |
| 188 | * | |
| 189 | * Replace section head word with its class ID. | |
| 190 | * | |
| 191 | * NOTE: This only replaces the section's word field. The | |
| 192 | * disjunct_hash (which encodes connector words) is NOT updated | |
| 193 | * here. Full connector rewriting happens naturally on the next | |
| 194 | * call to extract_sections (Phase 4), which uses the updated | |
| 195 | * word pool indices. This matches the iterative pipeline: | |
| 196 | * each round substitutes, then re-extracts fresh sections. | |
| 197 | * ═══════════════════════════════════════════════════════════════ */ | |
| 198 | ||
| 199 | __kernel void substitute_section_words( | |
| 200 | __global uint* sec_word, | |
| 201 | __global const double* sec_count, | |
| 202 | __global const uint* word_class_id, | |
| 203 | __global volatile uint* num_changed, | |
| 204 | const uint num_sections) | |
| 205 | { | |
| 206 | uint tid = get_global_id(0); | |
| 207 | if (tid >= num_sections) return; | |
| 208 | ||
| 209 | if (sec_count[tid] < 0.5) return; | |
| 210 | ||
| 211 | uint word = sec_word[tid]; | |
| 212 | uint cls = word_class_id[word]; | |
| 213 | ||
| 214 | if (cls != 0 && cls != word) { | |
| 215 | sec_word[tid] = cls; | |
| 216 | atomic_add(num_changed, 1U); | |
| 217 | } | |
| 218 | } | |
| 219 | ||
| 220 | /* ═══════════════════════════════════════════════════════════════ | |
| 221 | * READBACK: Dump class assignments and pair data | |
| 222 | * ═══════════════════════════════════════════════════════════════ */ | |
| 223 | ||
| 224 | __kernel void read_class_assignments( | |
| 225 | __global const uint* word_class_id, | |
| 226 | __global uint* out_classes, | |
| 227 | const uint num_words) | |
| 228 | { | |
| 229 | uint tid = get_global_id(0); | |
| 230 | if (tid >= num_words) return; | |
| 231 | out_classes[tid] = word_class_id[tid]; | |
| 232 | } |
| … | … | @@ -0,0 +1,739 @@ |
| 1 | /* | |
| 2 | * test-atomspace.c -- Test GPU-resident AtomSpace pools | |
| 3 | * | |
| 4 | * Compile: gcc -O2 -o test-atomspace test-atomspace.c -lOpenCL -lm | |
| 5 | * Run: ./test-atomspace | |
| 6 | * | |
| 7 | * Tests: | |
| 8 | * 1. Create words (find-or-create with dedup) | |
| 9 | * 2. Create pairs (find-or-create from word indices) | |
| 10 | * 3. Create sections | |
| 11 | * 4. Count pairs (atomic double increment + marginals) | |
| 12 | * 5. Count sections | |
| 13 | * 6. Verify counts read back correctly | |
| 14 | * 7. Performance: create + count rates | |
| 15 | */ | |
| 16 | ||
| 17 | #include <stdio.h> | |
| 18 | #include <stdlib.h> | |
| 19 | #include <stdint.h> | |
| 20 | #include <string.h> | |
| 21 | #include <math.h> | |
| 22 | #include <time.h> | |
| 23 | #include <CL/cl.h> | |
| 24 | ||
| 25 | /* ─── Pool capacities ─── */ | |
| 26 | ||
| 27 | #define WORD_CAPACITY (128 * 1024) /* 128K words */ | |
| 28 | #define PAIR_CAPACITY (4 * 1024 * 1024) /* 4M pairs */ | |
| 29 | #define SECTION_CAPACITY (1024 * 1024) /* 1M sections */ | |
| 30 | ||
| 31 | /* Hash table sizes (2x pool capacity for 50% load) */ | |
| 32 | #define WORD_HT_CAPACITY (256 * 1024) | |
| 33 | #define PAIR_HT_CAPACITY (8 * 1024 * 1024) | |
| 34 | #define SECTION_HT_CAPACITY (2 * 1024 * 1024) | |
| 35 | ||
| 36 | #define HT_EMPTY_KEY 0xFFFFFFFFFFFFFFFFULL | |
| 37 | #define HT_EMPTY_VALUE 0xFFFFFFFFU | |
| 38 | ||
| 39 | /* ─── Error checking ─── */ | |
| 40 | ||
| 41 | #define CL_CHECK(err, msg) do { \ | |
| 42 | if ((err) != CL_SUCCESS) { \ | |
| 43 | fprintf(stderr, "OpenCL error %d at %s:%d: %s\n", \ | |
| 44 | (err), __FILE__, __LINE__, (msg)); \ | |
| 45 | exit(1); \ | |
| 46 | } \ | |
| 47 | } while(0) | |
| 48 | ||
| 49 | /* ─── Read file ─── */ | |
| 50 | ||
| 51 | char* read_file(const char* path, size_t* len) | |
| 52 | { | |
| 53 | FILE* f = fopen(path, "r"); | |
| 54 | if (!f) { fprintf(stderr, "Cannot open %s\n", path); exit(1); } | |
| 55 | fseek(f, 0, SEEK_END); | |
| 56 | *len = ftell(f); | |
| 57 | fseek(f, 0, SEEK_SET); | |
| 58 | char* buf = malloc(*len + 1); | |
| 59 | size_t n = fread(buf, 1, *len, f); | |
| 60 | buf[n] = '\0'; | |
| 61 | *len = n; | |
| 62 | fclose(f); | |
| 63 | return buf; | |
| 64 | } | |
| 65 | ||
| 66 | /* ─── Simple hash for word names (CPU-side) ─── */ | |
| 67 | ||
| 68 | uint64_t hash_word(const char* name) | |
| 69 | { | |
| 70 | uint64_t h = 0x12345678DEADBEEFULL; | |
| 71 | while (*name) { | |
| 72 | h ^= (uint64_t)*name++; | |
| 73 | h *= 0xBF58476D1CE4E5B9ULL; | |
| 74 | h ^= h >> 31; | |
| 75 | } | |
| 76 | return h; | |
| 77 | } | |
| 78 | ||
| 79 | /* ─── Timing ─── */ | |
| 80 | ||
| 81 | double now_ms(void) | |
| 82 | { | |
| 83 | struct timespec ts; | |
| 84 | clock_gettime(CLOCK_MONOTONIC, &ts); | |
| 85 | return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0; | |
| 86 | } | |
| 87 | ||
| 88 | /* ─── Main ─── */ | |
| 89 | ||
| 90 | int main(int argc, char** argv) | |
| 91 | { | |
| 92 | cl_int err; | |
| 93 | ||
| 94 | printf("=== GPU AtomSpace Pool Test ===\n\n"); | |
| 95 | ||
| 96 | /* ─── OpenCL setup ─── */ | |
| 97 | ||
| 98 | cl_platform_id platform; | |
| 99 | err = clGetPlatformIDs(1, &platform, NULL); | |
| 100 | CL_CHECK(err, "platform"); | |
| 101 | ||
| 102 | cl_device_id device; | |
| 103 | err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL); | |
| 104 | CL_CHECK(err, "device"); | |
| 105 | ||
| 106 | char dev_name[256]; | |
| 107 | clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(dev_name), dev_name, NULL); | |
| 108 | printf("GPU: %s\n", dev_name); | |
| 109 | ||
| 110 | cl_context ctx = clCreateContext(NULL, 1, &device, NULL, NULL, &err); | |
| 111 | CL_CHECK(err, "context"); | |
| 112 | ||
| 113 | cl_command_queue queue = clCreateCommandQueue(ctx, device, 0, &err); | |
| 114 | CL_CHECK(err, "queue"); | |
| 115 | ||
| 116 | /* ─── Load and concatenate kernel sources ─── */ | |
| 117 | ||
| 118 | size_t ht_len, as_len; | |
| 119 | char* ht_src = read_file("gpu-hashtable.cl", &ht_len); | |
| 120 | char* as_src = read_file("gpu-atomspace.cl", &as_len); | |
| 121 | ||
| 122 | /* Concatenate: hashtable first, then atomspace */ | |
| 123 | size_t total_len = ht_len + 1 + as_len; | |
| 124 | char* combined = malloc(total_len + 1); | |
| 125 | memcpy(combined, ht_src, ht_len); | |
| 126 | combined[ht_len] = '\n'; | |
| 127 | memcpy(combined + ht_len + 1, as_src, as_len); | |
| 128 | combined[total_len] = '\0'; | |
| 129 | ||
| 130 | cl_program program = clCreateProgramWithSource(ctx, 1, | |
| 131 | (const char**)&combined, &total_len, &err); | |
| 132 | CL_CHECK(err, "create program"); | |
| 133 | ||
| 134 | /* Build with capacity defines */ | |
| 135 | char build_opts[512]; | |
| 136 | snprintf(build_opts, sizeof(build_opts), | |
| 137 | "-cl-std=CL1.2 " | |
| 138 | "-DWORD_CAPACITY=%d " | |
| 139 | "-DPAIR_CAPACITY=%d " | |
| 140 | "-DSECTION_CAPACITY=%d " | |
| 141 | "-DWORD_HT_CAPACITY=%d " | |
| 142 | "-DPAIR_HT_CAPACITY=%d " | |
| 143 | "-DSECTION_HT_CAPACITY=%d", | |
| 144 | WORD_CAPACITY, PAIR_CAPACITY, SECTION_CAPACITY, | |
| 145 | WORD_HT_CAPACITY, PAIR_HT_CAPACITY, SECTION_HT_CAPACITY); | |
| 146 | ||
| 147 | err = clBuildProgram(program, 1, &device, build_opts, NULL, NULL); | |
| 148 | if (err != CL_SUCCESS) { | |
| 149 | char log[16384]; | |
| 150 | clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, | |
| 151 | sizeof(log), log, NULL); | |
| 152 | fprintf(stderr, "Build error:\n%s\n", log); | |
| 153 | return 1; | |
| 154 | } | |
| 155 | printf("Kernels compiled successfully\n\n"); | |
| 156 | ||
| 157 | /* ─── Create kernels ─── */ | |
| 158 | ||
| 159 | cl_kernel k_word_foc = clCreateKernel(program, "word_find_or_create", &err); | |
| 160 | CL_CHECK(err, "kernel word_find_or_create"); | |
| 161 | cl_kernel k_pair_foc = clCreateKernel(program, "pair_find_or_create", &err); | |
| 162 | CL_CHECK(err, "kernel pair_find_or_create"); | |
| 163 | cl_kernel k_sec_foc = clCreateKernel(program, "section_find_or_create", &err); | |
| 164 | CL_CHECK(err, "kernel section_find_or_create"); | |
| 165 | cl_kernel k_count_pairs = clCreateKernel(program, "count_pairs", &err); | |
| 166 | CL_CHECK(err, "kernel count_pairs"); | |
| 167 | cl_kernel k_count_sec = clCreateKernel(program, "count_sections", &err); | |
| 168 | CL_CHECK(err, "kernel count_sections"); | |
| 169 | cl_kernel k_stats = clCreateKernel(program, "pool_stats", &err); | |
| 170 | CL_CHECK(err, "kernel pool_stats"); | |
| 171 | ||
| 172 | size_t local_size = 256; | |
| 173 | ||
| 174 | /* ═══ ALLOCATE GPU BUFFERS ═══ */ | |
| 175 | ||
| 176 | printf("Allocating GPU buffers...\n"); | |
| 177 | uint8_t pat_ff = 0xFF; | |
| 178 | uint8_t pat_00 = 0x00; | |
| 179 | ||
| 180 | /* Word hash table */ | |
| 181 | cl_mem wht_keys = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 182 | sizeof(uint64_t) * WORD_HT_CAPACITY, NULL, &err); | |
| 183 | CL_CHECK(err, "wht_keys"); | |
| 184 | cl_mem wht_values = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 185 | sizeof(uint32_t) * WORD_HT_CAPACITY, NULL, &err); | |
| 186 | CL_CHECK(err, "wht_values"); | |
| 187 | ||
| 188 | /* Word pool SoA */ | |
| 189 | cl_mem word_name_hash = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 190 | sizeof(uint64_t) * WORD_CAPACITY, NULL, &err); | |
| 191 | cl_mem word_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 192 | sizeof(double) * WORD_CAPACITY, NULL, &err); | |
| 193 | cl_mem word_class_id = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 194 | sizeof(uint32_t) * WORD_CAPACITY, NULL, &err); | |
| 195 | ||
| 196 | /* Word bump allocator */ | |
| 197 | uint32_t zero = 0; | |
| 198 | cl_mem word_next_free = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 199 | sizeof(uint32_t), &zero, &err); | |
| 200 | ||
| 201 | /* Pair hash table */ | |
| 202 | cl_mem pht_keys = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 203 | sizeof(uint64_t) * PAIR_HT_CAPACITY, NULL, &err); | |
| 204 | cl_mem pht_values = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 205 | sizeof(uint32_t) * PAIR_HT_CAPACITY, NULL, &err); | |
| 206 | ||
| 207 | /* Pair pool SoA */ | |
| 208 | cl_mem pair_word_a = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 209 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 210 | cl_mem pair_word_b = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 211 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 212 | cl_mem pair_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 213 | sizeof(double) * PAIR_CAPACITY, NULL, &err); | |
| 214 | cl_mem pair_mi = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 215 | sizeof(double) * PAIR_CAPACITY, NULL, &err); | |
| 216 | cl_mem pair_flags = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 217 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 218 | ||
| 219 | /* Pair bump allocator */ | |
| 220 | cl_mem pair_next_free = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 221 | sizeof(uint32_t), &zero, &err); | |
| 222 | ||
| 223 | /* Section hash table */ | |
| 224 | cl_mem sht_keys = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 225 | sizeof(uint64_t) * SECTION_HT_CAPACITY, NULL, &err); | |
| 226 | cl_mem sht_values = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 227 | sizeof(uint32_t) * SECTION_HT_CAPACITY, NULL, &err); | |
| 228 | ||
| 229 | /* Section pool SoA */ | |
| 230 | cl_mem sec_word = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 231 | sizeof(uint32_t) * SECTION_CAPACITY, NULL, &err); | |
| 232 | cl_mem sec_disjunct_hash = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 233 | sizeof(uint64_t) * SECTION_CAPACITY, NULL, &err); | |
| 234 | cl_mem sec_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 235 | sizeof(double) * SECTION_CAPACITY, NULL, &err); | |
| 236 | ||
| 237 | /* Section bump allocator */ | |
| 238 | cl_mem sec_next_free = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 239 | sizeof(uint32_t), &zero, &err); | |
| 240 | ||
| 241 | /* Stats output */ | |
| 242 | cl_mem d_stats = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 243 | sizeof(uint32_t) * 3, NULL, &err); | |
| 244 | ||
| 245 | /* Initialize hash tables to empty */ | |
| 246 | clEnqueueFillBuffer(queue, wht_keys, &pat_ff, 1, 0, | |
| 247 | sizeof(uint64_t) * WORD_HT_CAPACITY, 0, NULL, NULL); | |
| 248 | clEnqueueFillBuffer(queue, wht_values, &pat_ff, 1, 0, | |
| 249 | sizeof(uint32_t) * WORD_HT_CAPACITY, 0, NULL, NULL); | |
| 250 | clEnqueueFillBuffer(queue, pht_keys, &pat_ff, 1, 0, | |
| 251 | sizeof(uint64_t) * PAIR_HT_CAPACITY, 0, NULL, NULL); | |
| 252 | clEnqueueFillBuffer(queue, pht_values, &pat_ff, 1, 0, | |
| 253 | sizeof(uint32_t) * PAIR_HT_CAPACITY, 0, NULL, NULL); | |
| 254 | clEnqueueFillBuffer(queue, sht_keys, &pat_ff, 1, 0, | |
| 255 | sizeof(uint64_t) * SECTION_HT_CAPACITY, 0, NULL, NULL); | |
| 256 | clEnqueueFillBuffer(queue, sht_values, &pat_ff, 1, 0, | |
| 257 | sizeof(uint32_t) * SECTION_HT_CAPACITY, 0, NULL, NULL); | |
| 258 | ||
| 259 | /* Initialize pool arrays to 0 */ | |
| 260 | clEnqueueFillBuffer(queue, word_count, &pat_00, 1, 0, | |
| 261 | sizeof(double) * WORD_CAPACITY, 0, NULL, NULL); | |
| 262 | clEnqueueFillBuffer(queue, word_class_id, &pat_00, 1, 0, | |
| 263 | sizeof(uint32_t) * WORD_CAPACITY, 0, NULL, NULL); | |
| 264 | clEnqueueFillBuffer(queue, pair_count, &pat_00, 1, 0, | |
| 265 | sizeof(double) * PAIR_CAPACITY, 0, NULL, NULL); | |
| 266 | clEnqueueFillBuffer(queue, pair_mi, &pat_00, 1, 0, | |
| 267 | sizeof(double) * PAIR_CAPACITY, 0, NULL, NULL); | |
| 268 | clEnqueueFillBuffer(queue, pair_flags, &pat_00, 1, 0, | |
| 269 | sizeof(uint32_t) * PAIR_CAPACITY, 0, NULL, NULL); | |
| 270 | clEnqueueFillBuffer(queue, sec_count, &pat_00, 1, 0, | |
| 271 | sizeof(double) * SECTION_CAPACITY, 0, NULL, NULL); | |
| 272 | clFinish(queue); | |
| 273 | ||
| 274 | /* Calculate total GPU memory */ | |
| 275 | size_t total_mem = 0; | |
| 276 | total_mem += sizeof(uint64_t) * WORD_HT_CAPACITY; /* wht_keys */ | |
| 277 | total_mem += sizeof(uint32_t) * WORD_HT_CAPACITY; /* wht_values */ | |
| 278 | total_mem += sizeof(uint64_t) * WORD_CAPACITY; /* word_name_hash */ | |
| 279 | total_mem += sizeof(double) * WORD_CAPACITY; /* word_count */ | |
| 280 | total_mem += sizeof(uint32_t) * WORD_CAPACITY; /* word_class_id */ | |
| 281 | total_mem += sizeof(uint64_t) * PAIR_HT_CAPACITY; /* pht_keys */ | |
| 282 | total_mem += sizeof(uint32_t) * PAIR_HT_CAPACITY; /* pht_values */ | |
| 283 | total_mem += sizeof(uint32_t) * PAIR_CAPACITY; /* pair_word_a */ | |
| 284 | total_mem += sizeof(uint32_t) * PAIR_CAPACITY; /* pair_word_b */ | |
| 285 | total_mem += sizeof(double) * PAIR_CAPACITY; /* pair_count */ | |
| 286 | total_mem += sizeof(double) * PAIR_CAPACITY; /* pair_mi */ | |
| 287 | total_mem += sizeof(uint32_t) * PAIR_CAPACITY; /* pair_flags */ | |
| 288 | total_mem += sizeof(uint64_t) * SECTION_HT_CAPACITY;/* sht_keys */ | |
| 289 | total_mem += sizeof(uint32_t) * SECTION_HT_CAPACITY;/* sht_values */ | |
| 290 | total_mem += sizeof(uint32_t) * SECTION_CAPACITY; /* sec_word */ | |
| 291 | total_mem += sizeof(uint64_t) * SECTION_CAPACITY; /* sec_disjunct_hash */ | |
| 292 | total_mem += sizeof(double) * SECTION_CAPACITY; /* sec_count */ | |
| 293 | printf("Total GPU memory: %zu MB\n\n", total_mem / (1024*1024)); | |
| 294 | ||
| 295 | /* ═══ TEST 1: Create words ═══ */ | |
| 296 | ||
| 297 | printf("--- Test 1: Create words ---\n"); | |
| 298 | ||
| 299 | const char* test_words[] = { | |
| 300 | "the", "of", "and", "to", "a", "in", "was", "he", "she", "it", | |
| 301 | "that", "is", "for", "his", "with", "her", "had", "not", "at", "on", | |
| 302 | /* Duplicates to test dedup */ | |
| 303 | "the", "of", "and", "the", "he", "she" | |
| 304 | }; | |
| 305 | int num_words_in = 26; | |
| 306 | int num_unique_words = 20; | |
| 307 | ||
| 308 | uint64_t* h_word_hashes = malloc(sizeof(uint64_t) * num_words_in); | |
| 309 | for (int i = 0; i < num_words_in; i++) { | |
| 310 | h_word_hashes[i] = hash_word(test_words[i]); | |
| 311 | } | |
| 312 | ||
| 313 | cl_mem d_word_hashes = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 314 | sizeof(uint64_t) * num_words_in, h_word_hashes, &err); | |
| 315 | uint32_t* h_word_indices = malloc(sizeof(uint32_t) * num_words_in); | |
| 316 | cl_mem d_word_out = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 317 | sizeof(uint32_t) * num_words_in, NULL, &err); | |
| 318 | ||
| 319 | cl_uint nw = num_words_in; | |
| 320 | clSetKernelArg(k_word_foc, 0, sizeof(cl_mem), &wht_keys); | |
| 321 | clSetKernelArg(k_word_foc, 1, sizeof(cl_mem), &wht_values); | |
| 322 | clSetKernelArg(k_word_foc, 2, sizeof(cl_mem), &word_name_hash); | |
| 323 | clSetKernelArg(k_word_foc, 3, sizeof(cl_mem), &word_count); | |
| 324 | clSetKernelArg(k_word_foc, 4, sizeof(cl_mem), &word_class_id); | |
| 325 | clSetKernelArg(k_word_foc, 5, sizeof(cl_mem), &word_next_free); | |
| 326 | clSetKernelArg(k_word_foc, 6, sizeof(cl_mem), &d_word_hashes); | |
| 327 | clSetKernelArg(k_word_foc, 7, sizeof(cl_mem), &d_word_out); | |
| 328 | clSetKernelArg(k_word_foc, 8, sizeof(cl_uint), &nw); | |
| 329 | ||
| 330 | double t0 = now_ms(); | |
| 331 | size_t gs = ((num_words_in + local_size - 1) / local_size) * local_size; | |
| 332 | err = clEnqueueNDRangeKernel(queue, k_word_foc, 1, NULL, | |
| 333 | &gs, &local_size, 0, NULL, NULL); | |
| 334 | CL_CHECK(err, "enqueue word_foc"); | |
| 335 | clFinish(queue); | |
| 336 | double t1 = now_ms(); | |
| 337 | ||
| 338 | clEnqueueReadBuffer(queue, d_word_out, CL_TRUE, 0, | |
| 339 | sizeof(uint32_t) * num_words_in, h_word_indices, 0, NULL, NULL); | |
| 340 | ||
| 341 | printf(" Created %d words (with %d dups) in %.2f ms\n", | |
| 342 | num_words_in, num_words_in - num_unique_words, t1-t0); | |
| 343 | ||
| 344 | /* Check dedup: "the" appears at indices 0, 20, 23 — should all get same pool index */ | |
| 345 | int dedup_ok = (h_word_indices[0] == h_word_indices[20] && | |
| 346 | h_word_indices[0] == h_word_indices[23]); | |
| 347 | printf(" 'the' dedup: idx[0]=%u idx[20]=%u idx[23]=%u %s\n", | |
| 348 | h_word_indices[0], h_word_indices[20], h_word_indices[23], | |
| 349 | dedup_ok ? "PASS" : "FAIL"); | |
| 350 | ||
| 351 | /* "he"=7, "he"=24 */ | |
| 352 | int dedup2 = (h_word_indices[7] == h_word_indices[24]); | |
| 353 | printf(" 'he' dedup: idx[7]=%u idx[24]=%u %s\n", | |
| 354 | h_word_indices[7], h_word_indices[24], | |
| 355 | dedup2 ? "PASS" : "FAIL"); | |
| 356 | ||
| 357 | /* Check pool stats */ | |
| 358 | clSetKernelArg(k_stats, 0, sizeof(cl_mem), &word_next_free); | |
| 359 | clSetKernelArg(k_stats, 1, sizeof(cl_mem), &pair_next_free); | |
| 360 | clSetKernelArg(k_stats, 2, sizeof(cl_mem), &sec_next_free); | |
| 361 | clSetKernelArg(k_stats, 3, sizeof(cl_mem), &d_stats); | |
| 362 | ||
| 363 | size_t one = 1; | |
| 364 | clEnqueueNDRangeKernel(queue, k_stats, 1, NULL, &one, &one, 0, NULL, NULL); | |
| 365 | uint32_t stats[3]; | |
| 366 | clEnqueueReadBuffer(queue, d_stats, CL_TRUE, 0, sizeof(stats), stats, 0, NULL, NULL); | |
| 367 | ||
| 368 | printf(" Pool: %u words, %u pairs, %u sections\n", stats[0], stats[1], stats[2]); | |
| 369 | printf(" %s\n\n", (stats[0] == num_unique_words) ? "PASS" : "FAIL"); | |
| 370 | ||
| 371 | /* ═══ TEST 2: Create pairs ═══ */ | |
| 372 | ||
| 373 | printf("--- Test 2: Create pairs ---\n"); | |
| 374 | ||
| 375 | /* Create pairs for a sentence: "the cat was on the mat" | |
| 376 | * Words: the(0), cat(?), was(6), on(19), mat(?) | |
| 377 | * We need to create cat and mat first */ | |
| 378 | uint64_t extra_hashes[2] = { hash_word("cat"), hash_word("mat") }; | |
| 379 | cl_mem d_extra = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 380 | sizeof(uint64_t) * 2, extra_hashes, &err); | |
| 381 | cl_mem d_extra_out = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 382 | sizeof(uint32_t) * 2, NULL, &err); | |
| 383 | ||
| 384 | cl_uint n_extra = 2; | |
| 385 | clSetKernelArg(k_word_foc, 6, sizeof(cl_mem), &d_extra); | |
| 386 | clSetKernelArg(k_word_foc, 7, sizeof(cl_mem), &d_extra_out); | |
| 387 | clSetKernelArg(k_word_foc, 8, sizeof(cl_uint), &n_extra); | |
| 388 | gs = ((2 + local_size - 1) / local_size) * local_size; | |
| 389 | clEnqueueNDRangeKernel(queue, k_word_foc, 1, NULL, &gs, &local_size, 0, NULL, NULL); | |
| 390 | uint32_t extra_indices[2]; | |
| 391 | clEnqueueReadBuffer(queue, d_extra_out, CL_TRUE, 0, | |
| 392 | sizeof(uint32_t) * 2, extra_indices, 0, NULL, NULL); | |
| 393 | ||
| 394 | uint32_t idx_the = h_word_indices[0]; | |
| 395 | uint32_t idx_cat = extra_indices[0]; | |
| 396 | uint32_t idx_was = h_word_indices[6]; | |
| 397 | uint32_t idx_on = h_word_indices[19]; | |
| 398 | uint32_t idx_mat = extra_indices[1]; | |
| 399 | ||
| 400 | printf(" Word indices: the=%u cat=%u was=%u on=%u mat=%u\n", | |
| 401 | idx_the, idx_cat, idx_was, idx_on, idx_mat); | |
| 402 | ||
| 403 | /* Create pairs within window=2: (the,cat) (the,was) (cat,was) (cat,on) (was,on) (was,the_2) | |
| 404 | * Plus (on,the_2) (on,mat) (the_2,mat) */ | |
| 405 | int num_pairs_in = 9; | |
| 406 | uint32_t h_pair_a[] = {idx_the, idx_the, idx_cat, idx_cat, idx_was, idx_was, idx_on, idx_on, idx_the}; | |
| 407 | uint32_t h_pair_b[] = {idx_cat, idx_was, idx_was, idx_on, idx_on, idx_the, idx_the,idx_mat,idx_mat}; | |
| 408 | ||
| 409 | /* Add duplicates to test dedup */ | |
| 410 | int num_with_dups = 12; | |
| 411 | uint32_t h_pair_a2[12], h_pair_b2[12]; | |
| 412 | memcpy(h_pair_a2, h_pair_a, sizeof(uint32_t) * 9); | |
| 413 | memcpy(h_pair_b2, h_pair_b, sizeof(uint32_t) * 9); | |
| 414 | /* Duplicate first 3 pairs */ | |
| 415 | h_pair_a2[9] = idx_the; h_pair_b2[9] = idx_cat; | |
| 416 | h_pair_a2[10] = idx_the; h_pair_b2[10] = idx_was; | |
| 417 | h_pair_a2[11] = idx_cat; h_pair_b2[11] = idx_was; | |
| 418 | ||
| 419 | cl_mem d_pair_a = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 420 | sizeof(uint32_t) * num_with_dups, h_pair_a2, &err); | |
| 421 | cl_mem d_pair_b = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 422 | sizeof(uint32_t) * num_with_dups, h_pair_b2, &err); | |
| 423 | cl_mem d_pair_out = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 424 | sizeof(uint32_t) * num_with_dups, NULL, &err); | |
| 425 | ||
| 426 | cl_uint np = num_with_dups; | |
| 427 | clSetKernelArg(k_pair_foc, 0, sizeof(cl_mem), &pht_keys); | |
| 428 | clSetKernelArg(k_pair_foc, 1, sizeof(cl_mem), &pht_values); | |
| 429 | clSetKernelArg(k_pair_foc, 2, sizeof(cl_mem), &pair_word_a); | |
| 430 | clSetKernelArg(k_pair_foc, 3, sizeof(cl_mem), &pair_word_b); | |
| 431 | clSetKernelArg(k_pair_foc, 4, sizeof(cl_mem), &pair_count); | |
| 432 | clSetKernelArg(k_pair_foc, 5, sizeof(cl_mem), &pair_mi); | |
| 433 | clSetKernelArg(k_pair_foc, 6, sizeof(cl_mem), &pair_flags); | |
| 434 | clSetKernelArg(k_pair_foc, 7, sizeof(cl_mem), &pair_next_free); | |
| 435 | clSetKernelArg(k_pair_foc, 8, sizeof(cl_mem), &d_pair_a); | |
| 436 | clSetKernelArg(k_pair_foc, 9, sizeof(cl_mem), &d_pair_b); | |
| 437 | clSetKernelArg(k_pair_foc, 10, sizeof(cl_mem), &d_pair_out); | |
| 438 | clSetKernelArg(k_pair_foc, 11, sizeof(cl_uint), &np); | |
| 439 | ||
| 440 | t0 = now_ms(); | |
| 441 | gs = ((num_with_dups + local_size - 1) / local_size) * local_size; | |
| 442 | clEnqueueNDRangeKernel(queue, k_pair_foc, 1, NULL, &gs, &local_size, 0, NULL, NULL); | |
| 443 | clFinish(queue); | |
| 444 | t1 = now_ms(); | |
| 445 | ||
| 446 | uint32_t h_pair_indices[12]; | |
| 447 | clEnqueueReadBuffer(queue, d_pair_out, CL_TRUE, 0, | |
| 448 | sizeof(uint32_t) * num_with_dups, h_pair_indices, 0, NULL, NULL); | |
| 449 | ||
| 450 | /* Pair dedup: indices 0 and 9 should match (the,cat) */ | |
| 451 | int pair_dedup = (h_pair_indices[0] == h_pair_indices[9] && | |
| 452 | h_pair_indices[1] == h_pair_indices[10] && | |
| 453 | h_pair_indices[2] == h_pair_indices[11]); | |
| 454 | printf(" Created %d pairs (3 dups) in %.2f ms\n", num_with_dups, t1-t0); | |
| 455 | printf(" Pair dedup: %s\n", pair_dedup ? "PASS" : "FAIL"); | |
| 456 | ||
| 457 | /* Note: pair(the,was) and pair(was,the) should be the same (canonical order) */ | |
| 458 | int canon = (h_pair_indices[1] == h_pair_indices[5]); | |
| 459 | printf(" Canonical order (the,was)==(was,the): idx=%u==%u %s\n", | |
| 460 | h_pair_indices[1], h_pair_indices[5], canon ? "PASS" : "FAIL"); | |
| 461 | ||
| 462 | clEnqueueNDRangeKernel(queue, k_stats, 1, NULL, &one, &one, 0, NULL, NULL); | |
| 463 | clEnqueueReadBuffer(queue, d_stats, CL_TRUE, 0, sizeof(stats), stats, 0, NULL, NULL); | |
| 464 | /* 9 pairs but (the,was) and (was,the) deduplicate to same, and | |
| 465 | * (on,the) and (the,on) too. Let's count unique: */ | |
| 466 | printf(" Pool: %u words, %u pairs, %u sections\n", stats[0], stats[1], stats[2]); | |
| 467 | ||
| 468 | int num_unique_pairs = stats[1]; | |
| 469 | printf(" %s\n\n", (num_unique_pairs > 0 && num_unique_pairs <= num_pairs_in) ? "PASS" : "FAIL"); | |
| 470 | ||
| 471 | /* ═══ TEST 3: Create sections ═══ */ | |
| 472 | ||
| 473 | printf("--- Test 3: Create sections ---\n"); | |
| 474 | ||
| 475 | /* Simulate sections: word + disjunct hash */ | |
| 476 | int num_sections = 8; | |
| 477 | uint32_t h_sec_words[] = {idx_the, idx_the, idx_cat, idx_cat, idx_was, idx_was, idx_on, idx_mat}; | |
| 478 | uint64_t h_sec_dhash[] = { | |
| 479 | 0x1111111111111111ULL, /* the: cat+ */ | |
| 480 | 0x2222222222222222ULL, /* the: was+ */ | |
| 481 | 0x3333333333333333ULL, /* cat: the- was+ */ | |
| 482 | 0x4444444444444444ULL, /* cat: was- on+ */ | |
| 483 | 0x5555555555555555ULL, /* was: cat- on+ */ | |
| 484 | 0x2222222222222222ULL, /* was: same disjunct as "the: was+" — different word, different section */ | |
| 485 | 0x6666666666666666ULL, /* on: was- the+ */ | |
| 486 | 0x7777777777777777ULL, /* mat: on- the+ */ | |
| 487 | }; | |
| 488 | ||
| 489 | cl_mem d_sec_w = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 490 | sizeof(uint32_t) * num_sections, h_sec_words, &err); | |
| 491 | cl_mem d_sec_d = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 492 | sizeof(uint64_t) * num_sections, h_sec_dhash, &err); | |
| 493 | cl_mem d_sec_out = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 494 | sizeof(uint32_t) * num_sections, NULL, &err); | |
| 495 | ||
| 496 | cl_uint ns = num_sections; | |
| 497 | clSetKernelArg(k_sec_foc, 0, sizeof(cl_mem), &sht_keys); | |
| 498 | clSetKernelArg(k_sec_foc, 1, sizeof(cl_mem), &sht_values); | |
| 499 | clSetKernelArg(k_sec_foc, 2, sizeof(cl_mem), &sec_word); | |
| 500 | clSetKernelArg(k_sec_foc, 3, sizeof(cl_mem), &sec_disjunct_hash); | |
| 501 | clSetKernelArg(k_sec_foc, 4, sizeof(cl_mem), &sec_count); | |
| 502 | clSetKernelArg(k_sec_foc, 5, sizeof(cl_mem), &sec_next_free); | |
| 503 | clSetKernelArg(k_sec_foc, 6, sizeof(cl_mem), &d_sec_w); | |
| 504 | clSetKernelArg(k_sec_foc, 7, sizeof(cl_mem), &d_sec_d); | |
| 505 | clSetKernelArg(k_sec_foc, 8, sizeof(cl_mem), &d_sec_out); | |
| 506 | clSetKernelArg(k_sec_foc, 9, sizeof(cl_uint), &ns); | |
| 507 | ||
| 508 | t0 = now_ms(); | |
| 509 | gs = ((num_sections + local_size - 1) / local_size) * local_size; | |
| 510 | clEnqueueNDRangeKernel(queue, k_sec_foc, 1, NULL, &gs, &local_size, 0, NULL, NULL); | |
| 511 | clFinish(queue); | |
| 512 | t1 = now_ms(); | |
| 513 | ||
| 514 | uint32_t h_sec_indices[8]; | |
| 515 | clEnqueueReadBuffer(queue, d_sec_out, CL_TRUE, 0, | |
| 516 | sizeof(uint32_t) * num_sections, h_sec_indices, 0, NULL, NULL); | |
| 517 | ||
| 518 | printf(" Created %d sections in %.2f ms\n", num_sections, t1-t0); | |
| 519 | ||
| 520 | /* Section(the, 0x222..) != Section(was, 0x222..) — different word, different section */ | |
| 521 | int sec_diff = (h_sec_indices[1] != h_sec_indices[5]); | |
| 522 | printf(" Different word same disjunct = different section: %s\n", | |
| 523 | sec_diff ? "PASS" : "FAIL"); | |
| 524 | ||
| 525 | clEnqueueNDRangeKernel(queue, k_stats, 1, NULL, &one, &one, 0, NULL, NULL); | |
| 526 | clEnqueueReadBuffer(queue, d_stats, CL_TRUE, 0, sizeof(stats), stats, 0, NULL, NULL); | |
| 527 | printf(" Pool: %u words, %u pairs, %u sections\n", stats[0], stats[1], stats[2]); | |
| 528 | printf(" %s\n\n", (stats[2] == (uint32_t)num_sections) ? "PASS" : "FAIL"); | |
| 529 | ||
| 530 | /* ═══ TEST 4: Count pairs ═══ */ | |
| 531 | ||
| 532 | printf("--- Test 4: Count pairs (atomic double increment) ---\n"); | |
| 533 | ||
| 534 | /* Count pair 0 (the,cat) 100 times */ | |
| 535 | int count_n = 100; | |
| 536 | uint32_t* h_count_indices = malloc(sizeof(uint32_t) * count_n); | |
| 537 | for (int i = 0; i < count_n; i++) { | |
| 538 | h_count_indices[i] = h_pair_indices[0]; /* (the,cat) */ | |
| 539 | } | |
| 540 | ||
| 541 | cl_mem d_count_idx = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 542 | sizeof(uint32_t) * count_n, h_count_indices, &err); | |
| 543 | ||
| 544 | cl_uint cn = count_n; | |
| 545 | clSetKernelArg(k_count_pairs, 0, sizeof(cl_mem), &pair_count); | |
| 546 | clSetKernelArg(k_count_pairs, 1, sizeof(cl_mem), &pair_word_a); | |
| 547 | clSetKernelArg(k_count_pairs, 2, sizeof(cl_mem), &pair_word_b); | |
| 548 | clSetKernelArg(k_count_pairs, 3, sizeof(cl_mem), &word_count); | |
| 549 | clSetKernelArg(k_count_pairs, 4, sizeof(cl_mem), &pair_flags); | |
| 550 | clSetKernelArg(k_count_pairs, 5, sizeof(cl_mem), &d_count_idx); | |
| 551 | clSetKernelArg(k_count_pairs, 6, sizeof(cl_uint), &cn); | |
| 552 | ||
| 553 | t0 = now_ms(); | |
| 554 | gs = ((count_n + local_size - 1) / local_size) * local_size; | |
| 555 | clEnqueueNDRangeKernel(queue, k_count_pairs, 1, NULL, &gs, &local_size, 0, NULL, NULL); | |
| 556 | clFinish(queue); | |
| 557 | t1 = now_ms(); | |
| 558 | ||
| 559 | /* Read back the pair count and word marginals */ | |
| 560 | double h_pair_c; | |
| 561 | clEnqueueReadBuffer(queue, pair_count, CL_TRUE, | |
| 562 | sizeof(double) * h_pair_indices[0], sizeof(double), &h_pair_c, 0, NULL, NULL); | |
| 563 | ||
| 564 | double h_wc_the, h_wc_cat; | |
| 565 | clEnqueueReadBuffer(queue, word_count, CL_TRUE, | |
| 566 | sizeof(double) * idx_the, sizeof(double), &h_wc_the, 0, NULL, NULL); | |
| 567 | clEnqueueReadBuffer(queue, word_count, CL_TRUE, | |
| 568 | sizeof(double) * idx_cat, sizeof(double), &h_wc_cat, 0, NULL, NULL); | |
| 569 | ||
| 570 | printf(" Counted pair(the,cat) %d times in %.2f ms\n", count_n, t1-t0); | |
| 571 | printf(" pair_count = %.1f (expected %.1f) %s\n", | |
| 572 | h_pair_c, (double)count_n, | |
| 573 | (fabs(h_pair_c - count_n) < 0.5) ? "PASS" : "FAIL"); | |
| 574 | printf(" word_count[the] = %.1f word_count[cat] = %.1f (both expect %.1f) %s\n", | |
| 575 | h_wc_the, h_wc_cat, (double)count_n, | |
| 576 | (fabs(h_wc_the - count_n) < 0.5 && fabs(h_wc_cat - count_n) < 0.5) | |
| 577 | ? "PASS" : "FAIL"); | |
| 578 | ||
| 579 | /* Check dirty flag */ | |
| 580 | uint32_t h_flag; | |
| 581 | clEnqueueReadBuffer(queue, pair_flags, CL_TRUE, | |
| 582 | sizeof(uint32_t) * h_pair_indices[0], sizeof(uint32_t), &h_flag, 0, NULL, NULL); | |
| 583 | printf(" Dirty flag = %u (expected 1) %s\n\n", h_flag, (h_flag == 1) ? "PASS" : "FAIL"); | |
| 584 | ||
| 585 | /* ═══ TEST 5: Count sections ═══ */ | |
| 586 | ||
| 587 | printf("--- Test 5: Count sections ---\n"); | |
| 588 | ||
| 589 | /* Count section 0 (the: cat+) 50 times */ | |
| 590 | int sec_count_n = 50; | |
| 591 | uint32_t* h_sec_cnt_idx = malloc(sizeof(uint32_t) * sec_count_n); | |
| 592 | for (int i = 0; i < sec_count_n; i++) { | |
| 593 | h_sec_cnt_idx[i] = h_sec_indices[0]; | |
| 594 | } | |
| 595 | ||
| 596 | cl_mem d_sec_cnt_idx = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 597 | sizeof(uint32_t) * sec_count_n, h_sec_cnt_idx, &err); | |
| 598 | ||
| 599 | cl_uint scn = sec_count_n; | |
| 600 | clSetKernelArg(k_count_sec, 0, sizeof(cl_mem), &sec_count); | |
| 601 | clSetKernelArg(k_count_sec, 1, sizeof(cl_mem), &d_sec_cnt_idx); | |
| 602 | clSetKernelArg(k_count_sec, 2, sizeof(cl_uint), &scn); | |
| 603 | ||
| 604 | t0 = now_ms(); | |
| 605 | gs = ((sec_count_n + local_size - 1) / local_size) * local_size; | |
| 606 | clEnqueueNDRangeKernel(queue, k_count_sec, 1, NULL, &gs, &local_size, 0, NULL, NULL); | |
| 607 | clFinish(queue); | |
| 608 | t1 = now_ms(); | |
| 609 | ||
| 610 | double h_sec_c; | |
| 611 | clEnqueueReadBuffer(queue, sec_count, CL_TRUE, | |
| 612 | sizeof(double) * h_sec_indices[0], sizeof(double), &h_sec_c, 0, NULL, NULL); | |
| 613 | ||
| 614 | printf(" Counted section 0 %d times in %.2f ms\n", sec_count_n, t1-t0); | |
| 615 | printf(" sec_count = %.1f (expected %.1f) %s\n\n", | |
| 616 | h_sec_c, (double)sec_count_n, | |
| 617 | (fabs(h_sec_c - sec_count_n) < 0.5) ? "PASS" : "FAIL"); | |
| 618 | ||
| 619 | /* ═══ TEST 6: Bulk performance ═══ */ | |
| 620 | ||
| 621 | printf("--- Test 6: Bulk performance ---\n"); | |
| 622 | ||
| 623 | /* Create 100K words */ | |
| 624 | int bulk_words = 100000; | |
| 625 | uint64_t* h_bulk_hashes = malloc(sizeof(uint64_t) * bulk_words); | |
| 626 | uint64_t rng = 0xDEADBEEFCAFEBABEULL; | |
| 627 | for (int i = 0; i < bulk_words; i++) { | |
| 628 | rng += 0x9E3779B97F4A7C15ULL; | |
| 629 | uint64_t z = rng; | |
| 630 | z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; | |
| 631 | z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; | |
| 632 | z = z ^ (z >> 31); | |
| 633 | if (z == HT_EMPTY_KEY) z = 0; | |
| 634 | h_bulk_hashes[i] = z; | |
| 635 | } | |
| 636 | ||
| 637 | cl_mem d_bulk = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 638 | sizeof(uint64_t) * bulk_words, h_bulk_hashes, &err); | |
| 639 | cl_mem d_bulk_out = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 640 | sizeof(uint32_t) * bulk_words, NULL, &err); | |
| 641 | ||
| 642 | cl_uint bw = bulk_words; | |
| 643 | clSetKernelArg(k_word_foc, 6, sizeof(cl_mem), &d_bulk); | |
| 644 | clSetKernelArg(k_word_foc, 7, sizeof(cl_mem), &d_bulk_out); | |
| 645 | clSetKernelArg(k_word_foc, 8, sizeof(cl_uint), &bw); | |
| 646 | ||
| 647 | t0 = now_ms(); | |
| 648 | gs = ((bulk_words + local_size - 1) / local_size) * local_size; | |
| 649 | clEnqueueNDRangeKernel(queue, k_word_foc, 1, NULL, &gs, &local_size, 0, NULL, NULL); | |
| 650 | clFinish(queue); | |
| 651 | t1 = now_ms(); | |
| 652 | printf(" 100K words created in %.1f ms (%.1f M/sec)\n", | |
| 653 | t1-t0, bulk_words / ((t1-t0) / 1000.0) / 1e6); | |
| 654 | ||
| 655 | /* Create 1M pairs from random word indices */ | |
| 656 | int bulk_pairs = 1000000; | |
| 657 | uint32_t* h_bulk_pa = malloc(sizeof(uint32_t) * bulk_pairs); | |
| 658 | uint32_t* h_bulk_pb = malloc(sizeof(uint32_t) * bulk_pairs); | |
| 659 | for (int i = 0; i < bulk_pairs; i++) { | |
| 660 | rng += 0x9E3779B97F4A7C15ULL; | |
| 661 | h_bulk_pa[i] = (uint32_t)(rng >> 32) % bulk_words; | |
| 662 | h_bulk_pb[i] = (uint32_t)(rng & 0xFFFFFFFF) % bulk_words; | |
| 663 | } | |
| 664 | ||
| 665 | cl_mem d_bulk_pa = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 666 | sizeof(uint32_t) * bulk_pairs, h_bulk_pa, &err); | |
| 667 | cl_mem d_bulk_pb = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 668 | sizeof(uint32_t) * bulk_pairs, h_bulk_pb, &err); | |
| 669 | cl_mem d_bulk_po = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 670 | sizeof(uint32_t) * bulk_pairs, NULL, &err); | |
| 671 | ||
| 672 | cl_uint bp = bulk_pairs; | |
| 673 | clSetKernelArg(k_pair_foc, 8, sizeof(cl_mem), &d_bulk_pa); | |
| 674 | clSetKernelArg(k_pair_foc, 9, sizeof(cl_mem), &d_bulk_pb); | |
| 675 | clSetKernelArg(k_pair_foc, 10, sizeof(cl_mem), &d_bulk_po); | |
| 676 | clSetKernelArg(k_pair_foc, 11, sizeof(cl_uint), &bp); | |
| 677 | ||
| 678 | t0 = now_ms(); | |
| 679 | gs = ((bulk_pairs + local_size - 1) / local_size) * local_size; | |
| 680 | clEnqueueNDRangeKernel(queue, k_pair_foc, 1, NULL, &gs, &local_size, 0, NULL, NULL); | |
| 681 | clFinish(queue); | |
| 682 | t1 = now_ms(); | |
| 683 | printf(" 1M pairs created in %.1f ms (%.1f M/sec)\n", | |
| 684 | t1-t0, bulk_pairs / ((t1-t0) / 1000.0) / 1e6); | |
| 685 | ||
| 686 | /* Final stats */ | |
| 687 | clEnqueueNDRangeKernel(queue, k_stats, 1, NULL, &one, &one, 0, NULL, NULL); | |
| 688 | clEnqueueReadBuffer(queue, d_stats, CL_TRUE, 0, sizeof(stats), stats, 0, NULL, NULL); | |
| 689 | printf(" Final pool: %u words, %u pairs, %u sections\n\n", stats[0], stats[1], stats[2]); | |
| 690 | ||
| 691 | printf("=== All tests complete ===\n"); | |
| 692 | ||
| 693 | /* ─── Cleanup ─── */ | |
| 694 | ||
| 695 | clReleaseMemObject(wht_keys); | |
| 696 | clReleaseMemObject(wht_values); | |
| 697 | clReleaseMemObject(word_name_hash); | |
| 698 | clReleaseMemObject(word_count); | |
| 699 | clReleaseMemObject(word_class_id); | |
| 700 | clReleaseMemObject(word_next_free); | |
| 701 | clReleaseMemObject(pht_keys); | |
| 702 | clReleaseMemObject(pht_values); | |
| 703 | clReleaseMemObject(pair_word_a); | |
| 704 | clReleaseMemObject(pair_word_b); | |
| 705 | clReleaseMemObject(pair_count); | |
| 706 | clReleaseMemObject(pair_mi); | |
| 707 | clReleaseMemObject(pair_flags); | |
| 708 | clReleaseMemObject(pair_next_free); | |
| 709 | clReleaseMemObject(sht_keys); | |
| 710 | clReleaseMemObject(sht_values); | |
| 711 | clReleaseMemObject(sec_word); | |
| 712 | clReleaseMemObject(sec_disjunct_hash); | |
| 713 | clReleaseMemObject(sec_count); | |
| 714 | clReleaseMemObject(sec_next_free); | |
| 715 | clReleaseMemObject(d_stats); | |
| 716 | /* ... remaining temp buffers */ | |
| 717 | clReleaseKernel(k_word_foc); | |
| 718 | clReleaseKernel(k_pair_foc); | |
| 719 | clReleaseKernel(k_sec_foc); | |
| 720 | clReleaseKernel(k_count_pairs); | |
| 721 | clReleaseKernel(k_count_sec); | |
| 722 | clReleaseKernel(k_stats); | |
| 723 | clReleaseProgram(program); | |
| 724 | clReleaseCommandQueue(queue); | |
| 725 | clReleaseContext(ctx); | |
| 726 | ||
| 727 | free(h_word_hashes); | |
| 728 | free(h_word_indices); | |
| 729 | free(h_count_indices); | |
| 730 | free(h_sec_cnt_idx); | |
| 731 | free(h_bulk_hashes); | |
| 732 | free(h_bulk_pa); | |
| 733 | free(h_bulk_pb); | |
| 734 | free(combined); | |
| 735 | free(ht_src); | |
| 736 | free(as_src); | |
| 737 | ||
| 738 | return 0; | |
| 739 | } |
| … | … | @@ -0,0 +1,954 @@ |
| 1 | /* | |
| 2 | * test-cosine.c -- Test GPU cosine similarity and candidate generation | |
| 3 | * | |
| 4 | * Compile: gcc -O2 -o test-cosine test-cosine.c -lOpenCL -lm | |
| 5 | * Run: ./test-cosine | |
| 6 | * | |
| 7 | * Tests: | |
| 8 | * 1. Known cosine (2 words, 4 sections, exact verification) | |
| 9 | * 2. Three words — all pairwise cosines | |
| 10 | * 3. Identical vectors → cosine = 1.0 | |
| 11 | * 4. No shared disjuncts → 0 candidates | |
| 12 | * 5. Filter candidates above threshold | |
| 13 | * 6. Benchmark: 1000 sentences → sections → cosines (full pipeline) | |
| 14 | */ | |
| 15 | ||
| 16 | #include <stdio.h> | |
| 17 | #include <stdlib.h> | |
| 18 | #include <stdint.h> | |
| 19 | #include <string.h> | |
| 20 | #include <math.h> | |
| 21 | #include <time.h> | |
| 22 | #include <CL/cl.h> | |
| 23 | ||
| 24 | /* ─── Pool capacities ─── */ | |
| 25 | ||
| 26 | #define WORD_CAPACITY (128 * 1024) | |
| 27 | #define PAIR_CAPACITY (4 * 1024 * 1024) | |
| 28 | #define SECTION_CAPACITY (1024 * 1024) | |
| 29 | #define WORD_HT_CAPACITY (256 * 1024) | |
| 30 | #define PAIR_HT_CAPACITY (8 * 1024 * 1024) | |
| 31 | #define SECTION_HT_CAPACITY (2 * 1024 * 1024) | |
| 32 | ||
| 33 | /* Phase 5 capacities */ | |
| 34 | #define DJH_HT_CAPACITY (2 * 1024 * 1024) | |
| 35 | #define CANDIDATE_CAPACITY (512 * 1024) | |
| 36 | #define CANDIDATE_HT_CAPACITY (1024 * 1024) | |
| 37 | ||
| 38 | #define HT_EMPTY_KEY 0xFFFFFFFFFFFFFFFFULL | |
| 39 | #define HT_EMPTY_VALUE 0xFFFFFFFFU | |
| 40 | ||
| 41 | /* ─── Error checking ─── */ | |
| 42 | ||
| 43 | #define CL_CHECK(err, msg) do { \ | |
| 44 | if ((err) != CL_SUCCESS) { \ | |
| 45 | fprintf(stderr, "OpenCL error %d at %s:%d: %s\n", \ | |
| 46 | (err), __FILE__, __LINE__, (msg)); \ | |
| 47 | exit(1); \ | |
| 48 | } \ | |
| 49 | } while(0) | |
| 50 | ||
| 51 | /* ─── Read file ─── */ | |
| 52 | ||
| 53 | char* read_file(const char* path, size_t* len) | |
| 54 | { | |
| 55 | FILE* f = fopen(path, "r"); | |
| 56 | if (!f) { fprintf(stderr, "Cannot open %s\n", path); exit(1); } | |
| 57 | fseek(f, 0, SEEK_END); | |
| 58 | *len = ftell(f); | |
| 59 | fseek(f, 0, SEEK_SET); | |
| 60 | char* buf = malloc(*len + 1); | |
| 61 | size_t n = fread(buf, 1, *len, f); | |
| 62 | buf[n] = '\0'; | |
| 63 | *len = n; | |
| 64 | fclose(f); | |
| 65 | return buf; | |
| 66 | } | |
| 67 | ||
| 68 | /* ─── Timing ─── */ | |
| 69 | ||
| 70 | double now_ms(void) | |
| 71 | { | |
| 72 | struct timespec ts; | |
| 73 | clock_gettime(CLOCK_MONOTONIC, &ts); | |
| 74 | return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0; | |
| 75 | } | |
| 76 | ||
| 77 | /* ─── Helper: Concatenate sources ─── */ | |
| 78 | ||
| 79 | char* concat_sources(const char** srcs, size_t* lens, int n, size_t* total) | |
| 80 | { | |
| 81 | *total = 0; | |
| 82 | for (int i = 0; i < n; i++) *total += lens[i] + 1; | |
| 83 | char* buf = malloc(*total + 1); | |
| 84 | size_t pos = 0; | |
| 85 | for (int i = 0; i < n; i++) { | |
| 86 | memcpy(buf + pos, srcs[i], lens[i]); | |
| 87 | pos += lens[i]; | |
| 88 | buf[pos++] = '\n'; | |
| 89 | } | |
| 90 | buf[pos] = '\0'; | |
| 91 | *total = pos; | |
| 92 | return buf; | |
| 93 | } | |
| 94 | ||
| 95 | /* ─── Helper: reset cosine pipeline buffers ─── */ | |
| 96 | ||
| 97 | void reset_cosine_buffers(cl_command_queue queue, | |
| 98 | cl_mem djh_ht_keys, cl_mem djh_ht_values, | |
| 99 | cl_mem sec_chain_next, cl_mem word_norm_sq, | |
| 100 | cl_mem cand_ht_keys, cl_mem cand_ht_values, | |
| 101 | cl_mem cand_dot, cl_mem cand_cosine, | |
| 102 | cl_mem cand_next_free) | |
| 103 | { | |
| 104 | uint8_t pat_ff = 0xFF; | |
| 105 | uint8_t pat_00 = 0x00; | |
| 106 | uint32_t zero = 0; | |
| 107 | ||
| 108 | /* Disjunct reverse index HT */ | |
| 109 | clEnqueueFillBuffer(queue, djh_ht_keys, &pat_ff, 1, 0, | |
| 110 | sizeof(uint64_t) * DJH_HT_CAPACITY, 0, NULL, NULL); | |
| 111 | clEnqueueFillBuffer(queue, djh_ht_values, &pat_ff, 1, 0, | |
| 112 | sizeof(uint32_t) * DJH_HT_CAPACITY, 0, NULL, NULL); | |
| 113 | ||
| 114 | /* Section chain pointers */ | |
| 115 | clEnqueueFillBuffer(queue, sec_chain_next, &pat_ff, 1, 0, | |
| 116 | sizeof(uint32_t) * SECTION_CAPACITY, 0, NULL, NULL); | |
| 117 | ||
| 118 | /* Word norms */ | |
| 119 | clEnqueueFillBuffer(queue, word_norm_sq, &pat_00, 1, 0, | |
| 120 | sizeof(double) * WORD_CAPACITY, 0, NULL, NULL); | |
| 121 | ||
| 122 | /* Candidate HT */ | |
| 123 | clEnqueueFillBuffer(queue, cand_ht_keys, &pat_ff, 1, 0, | |
| 124 | sizeof(uint64_t) * CANDIDATE_HT_CAPACITY, 0, NULL, NULL); | |
| 125 | clEnqueueFillBuffer(queue, cand_ht_values, &pat_ff, 1, 0, | |
| 126 | sizeof(uint32_t) * CANDIDATE_HT_CAPACITY, 0, NULL, NULL); | |
| 127 | ||
| 128 | /* Candidate pool */ | |
| 129 | clEnqueueFillBuffer(queue, cand_dot, &pat_00, 1, 0, | |
| 130 | sizeof(double) * CANDIDATE_CAPACITY, 0, NULL, NULL); | |
| 131 | clEnqueueFillBuffer(queue, cand_cosine, &pat_00, 1, 0, | |
| 132 | sizeof(double) * CANDIDATE_CAPACITY, 0, NULL, NULL); | |
| 133 | clEnqueueWriteBuffer(queue, cand_next_free, CL_FALSE, 0, | |
| 134 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 135 | ||
| 136 | clFinish(queue); | |
| 137 | } | |
| 138 | ||
| 139 | /* ─── Helper: reset section pool ─── */ | |
| 140 | ||
| 141 | void reset_section_pool(cl_command_queue queue, | |
| 142 | cl_mem sht_keys, cl_mem sht_values, | |
| 143 | cl_mem sec_count, cl_mem sec_next_free) | |
| 144 | { | |
| 145 | uint8_t pat_ff = 0xFF; | |
| 146 | uint8_t pat_00 = 0x00; | |
| 147 | uint32_t zero = 0; | |
| 148 | ||
| 149 | clEnqueueFillBuffer(queue, sht_keys, &pat_ff, 1, 0, | |
| 150 | sizeof(uint64_t) * SECTION_HT_CAPACITY, 0, NULL, NULL); | |
| 151 | clEnqueueFillBuffer(queue, sht_values, &pat_ff, 1, 0, | |
| 152 | sizeof(uint32_t) * SECTION_HT_CAPACITY, 0, NULL, NULL); | |
| 153 | clEnqueueFillBuffer(queue, sec_count, &pat_00, 1, 0, | |
| 154 | sizeof(double) * SECTION_CAPACITY, 0, NULL, NULL); | |
| 155 | clEnqueueWriteBuffer(queue, sec_next_free, CL_FALSE, 0, | |
| 156 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 157 | clFinish(queue); | |
| 158 | } | |
| 159 | ||
| 160 | /* ─── Helper: manually populate sections ─── */ | |
| 161 | ||
| 162 | void upload_sections(cl_command_queue queue, | |
| 163 | cl_mem sec_word, cl_mem sec_disjunct_hash, cl_mem sec_count, | |
| 164 | cl_mem sec_next_free, | |
| 165 | uint32_t* words, uint64_t* djhs, double* counts, uint32_t n) | |
| 166 | { | |
| 167 | clEnqueueWriteBuffer(queue, sec_word, CL_FALSE, 0, | |
| 168 | sizeof(uint32_t) * n, words, 0, NULL, NULL); | |
| 169 | clEnqueueWriteBuffer(queue, sec_disjunct_hash, CL_FALSE, 0, | |
| 170 | sizeof(uint64_t) * n, djhs, 0, NULL, NULL); | |
| 171 | clEnqueueWriteBuffer(queue, sec_count, CL_FALSE, 0, | |
| 172 | sizeof(double) * n, counts, 0, NULL, NULL); | |
| 173 | clEnqueueWriteBuffer(queue, sec_next_free, CL_FALSE, 0, | |
| 174 | sizeof(uint32_t), &n, 0, NULL, NULL); | |
| 175 | clFinish(queue); | |
| 176 | } | |
| 177 | ||
| 178 | /* ─── Helper: run cosine pipeline ─── */ | |
| 179 | ||
| 180 | void run_cosine_pipeline(cl_command_queue queue, | |
| 181 | cl_kernel k_norms, cl_kernel k_chains, | |
| 182 | cl_kernel k_dots, cl_kernel k_cosines, | |
| 183 | uint32_t num_sections, | |
| 184 | cl_mem cand_next_free, uint32_t* out_num_candidates) | |
| 185 | { | |
| 186 | size_t local = 256; | |
| 187 | size_t gs; | |
| 188 | cl_int err; | |
| 189 | ||
| 190 | /* Step 1: Word norms */ | |
| 191 | gs = ((num_sections + local - 1) / local) * local; | |
| 192 | err = clEnqueueNDRangeKernel(queue, k_norms, 1, NULL, | |
| 193 | &gs, &local, 0, NULL, NULL); | |
| 194 | CL_CHECK(err, "enqueue compute_word_norms"); | |
| 195 | ||
| 196 | /* Step 2: Disjunct chains */ | |
| 197 | err = clEnqueueNDRangeKernel(queue, k_chains, 1, NULL, | |
| 198 | &gs, &local, 0, NULL, NULL); | |
| 199 | CL_CHECK(err, "enqueue build_disjunct_chains"); | |
| 200 | ||
| 201 | /* Step 3: Dot products */ | |
| 202 | err = clEnqueueNDRangeKernel(queue, k_dots, 1, NULL, | |
| 203 | &gs, &local, 0, NULL, NULL); | |
| 204 | CL_CHECK(err, "enqueue accumulate_dot_products"); | |
| 205 | ||
| 206 | clFinish(queue); | |
| 207 | ||
| 208 | /* Read candidate count */ | |
| 209 | clEnqueueReadBuffer(queue, cand_next_free, CL_TRUE, 0, | |
| 210 | sizeof(uint32_t), out_num_candidates, 0, NULL, NULL); | |
| 211 | ||
| 212 | if (*out_num_candidates == 0) return; | |
| 213 | ||
| 214 | /* Step 4: Cosines */ | |
| 215 | gs = ((*out_num_candidates + local - 1) / local) * local; | |
| 216 | cl_uint nc = *out_num_candidates; | |
| 217 | clSetKernelArg(k_cosines, 5, sizeof(cl_uint), &nc); | |
| 218 | err = clEnqueueNDRangeKernel(queue, k_cosines, 1, NULL, | |
| 219 | &gs, &local, 0, NULL, NULL); | |
| 220 | CL_CHECK(err, "enqueue compute_cosines"); | |
| 221 | ||
| 222 | clFinish(queue); | |
| 223 | } | |
| 224 | ||
| 225 | /* ─── Main ─── */ | |
| 226 | ||
| 227 | int main(int argc, char** argv) | |
| 228 | { | |
| 229 | cl_int err; | |
| 230 | int pass_count = 0, fail_count = 0; | |
| 231 | ||
| 232 | printf("=== GPU Cosine Similarity Test ===\n\n"); | |
| 233 | ||
| 234 | /* ─── OpenCL setup ─── */ | |
| 235 | ||
| 236 | cl_platform_id platform; | |
| 237 | err = clGetPlatformIDs(1, &platform, NULL); | |
| 238 | CL_CHECK(err, "platform"); | |
| 239 | ||
| 240 | cl_device_id device; | |
| 241 | err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL); | |
| 242 | CL_CHECK(err, "device"); | |
| 243 | ||
| 244 | char dev_name[256]; | |
| 245 | clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(dev_name), dev_name, NULL); | |
| 246 | printf("GPU: %s\n", dev_name); | |
| 247 | ||
| 248 | cl_context ctx = clCreateContext(NULL, 1, &device, NULL, NULL, &err); | |
| 249 | CL_CHECK(err, "context"); | |
| 250 | ||
| 251 | cl_command_queue queue = clCreateCommandQueue(ctx, device, 0, &err); | |
| 252 | CL_CHECK(err, "queue"); | |
| 253 | ||
| 254 | /* ─── Load and concatenate kernel sources ─── */ | |
| 255 | ||
| 256 | size_t len_ht, len_as, len_sc, len_cos; | |
| 257 | char* src_ht = read_file("gpu-hashtable.cl", &len_ht); | |
| 258 | char* src_as = read_file("gpu-atomspace.cl", &len_as); | |
| 259 | char* src_sc = read_file("gpu-sections.cl", &len_sc); | |
| 260 | char* src_cos = read_file("gpu-cosine.cl", &len_cos); | |
| 261 | ||
| 262 | const char* srcs[] = {src_ht, src_as, src_sc, src_cos}; | |
| 263 | size_t lens[] = {len_ht, len_as, len_sc, len_cos}; | |
| 264 | size_t total_len; | |
| 265 | char* combined = concat_sources(srcs, lens, 4, &total_len); | |
| 266 | ||
| 267 | cl_program program = clCreateProgramWithSource(ctx, 1, | |
| 268 | (const char**)&combined, &total_len, &err); | |
| 269 | CL_CHECK(err, "create program"); | |
| 270 | ||
| 271 | char build_opts[1024]; | |
| 272 | snprintf(build_opts, sizeof(build_opts), | |
| 273 | "-cl-std=CL1.2 " | |
| 274 | "-DWORD_CAPACITY=%d " | |
| 275 | "-DPAIR_CAPACITY=%d " | |
| 276 | "-DSECTION_CAPACITY=%d " | |
| 277 | "-DWORD_HT_CAPACITY=%d " | |
| 278 | "-DPAIR_HT_CAPACITY=%d " | |
| 279 | "-DSECTION_HT_CAPACITY=%d " | |
| 280 | "-DDJH_HT_CAPACITY=%d " | |
| 281 | "-DCANDIDATE_CAPACITY=%d " | |
| 282 | "-DCANDIDATE_HT_CAPACITY=%d", | |
| 283 | WORD_CAPACITY, PAIR_CAPACITY, SECTION_CAPACITY, | |
| 284 | WORD_HT_CAPACITY, PAIR_HT_CAPACITY, SECTION_HT_CAPACITY, | |
| 285 | DJH_HT_CAPACITY, CANDIDATE_CAPACITY, CANDIDATE_HT_CAPACITY); | |
| 286 | ||
| 287 | err = clBuildProgram(program, 1, &device, build_opts, NULL, NULL); | |
| 288 | if (err != CL_SUCCESS) { | |
| 289 | char log[16384]; | |
| 290 | clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, | |
| 291 | sizeof(log), log, NULL); | |
| 292 | fprintf(stderr, "Build error:\n%s\n", log); | |
| 293 | return 1; | |
| 294 | } | |
| 295 | printf("Kernels compiled successfully\n\n"); | |
| 296 | ||
| 297 | /* ─── Create kernels ─── */ | |
| 298 | ||
| 299 | cl_kernel k_norms = clCreateKernel(program, "compute_word_norms", &err); | |
| 300 | CL_CHECK(err, "kernel compute_word_norms"); | |
| 301 | cl_kernel k_chains = clCreateKernel(program, "build_disjunct_chains", &err); | |
| 302 | CL_CHECK(err, "kernel build_disjunct_chains"); | |
| 303 | cl_kernel k_dots = clCreateKernel(program, "accumulate_dot_products", &err); | |
| 304 | CL_CHECK(err, "kernel accumulate_dot_products"); | |
| 305 | cl_kernel k_cosines = clCreateKernel(program, "compute_cosines", &err); | |
| 306 | CL_CHECK(err, "kernel compute_cosines"); | |
| 307 | cl_kernel k_filter = clCreateKernel(program, "filter_candidates", &err); | |
| 308 | CL_CHECK(err, "kernel filter_candidates"); | |
| 309 | cl_kernel k_extract = clCreateKernel(program, "extract_sections", &err); | |
| 310 | CL_CHECK(err, "kernel extract_sections"); | |
| 311 | ||
| 312 | /* ─── Allocate GPU buffers ─── */ | |
| 313 | ||
| 314 | printf("Allocating GPU buffers...\n"); | |
| 315 | uint32_t zero = 0; | |
| 316 | ||
| 317 | /* Section pool */ | |
| 318 | cl_mem sec_word = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 319 | sizeof(uint32_t) * SECTION_CAPACITY, NULL, &err); | |
| 320 | cl_mem sec_disjunct_hash = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 321 | sizeof(uint64_t) * SECTION_CAPACITY, NULL, &err); | |
| 322 | cl_mem sec_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 323 | sizeof(double) * SECTION_CAPACITY, NULL, &err); | |
| 324 | cl_mem sec_next_free = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 325 | sizeof(uint32_t), &zero, &err); | |
| 326 | ||
| 327 | /* Section hash table (for extract_sections) */ | |
| 328 | cl_mem sht_keys = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 329 | sizeof(uint64_t) * SECTION_HT_CAPACITY, NULL, &err); | |
| 330 | cl_mem sht_values = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 331 | sizeof(uint32_t) * SECTION_HT_CAPACITY, NULL, &err); | |
| 332 | ||
| 333 | /* Total sections counter (for extract_sections) */ | |
| 334 | cl_mem total_sections_created = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 335 | sizeof(uint32_t), &zero, &err); | |
| 336 | ||
| 337 | /* Disjunct reverse index HT */ | |
| 338 | cl_mem djh_ht_keys = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 339 | sizeof(uint64_t) * DJH_HT_CAPACITY, NULL, &err); | |
| 340 | CL_CHECK(err, "djh_ht_keys"); | |
| 341 | cl_mem djh_ht_values = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 342 | sizeof(uint32_t) * DJH_HT_CAPACITY, NULL, &err); | |
| 343 | CL_CHECK(err, "djh_ht_values"); | |
| 344 | ||
| 345 | /* Section chain pointers */ | |
| 346 | cl_mem sec_chain_next = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 347 | sizeof(uint32_t) * SECTION_CAPACITY, NULL, &err); | |
| 348 | CL_CHECK(err, "sec_chain_next"); | |
| 349 | ||
| 350 | /* Word norms */ | |
| 351 | cl_mem word_norm_sq = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 352 | sizeof(double) * WORD_CAPACITY, NULL, &err); | |
| 353 | CL_CHECK(err, "word_norm_sq"); | |
| 354 | ||
| 355 | /* Candidate HT */ | |
| 356 | cl_mem cand_ht_keys = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 357 | sizeof(uint64_t) * CANDIDATE_HT_CAPACITY, NULL, &err); | |
| 358 | cl_mem cand_ht_values = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 359 | sizeof(uint32_t) * CANDIDATE_HT_CAPACITY, NULL, &err); | |
| 360 | ||
| 361 | /* Candidate pool */ | |
| 362 | cl_mem cand_word_a = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 363 | sizeof(uint32_t) * CANDIDATE_CAPACITY, NULL, &err); | |
| 364 | cl_mem cand_word_b = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 365 | sizeof(uint32_t) * CANDIDATE_CAPACITY, NULL, &err); | |
| 366 | cl_mem cand_dot = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 367 | sizeof(double) * CANDIDATE_CAPACITY, NULL, &err); | |
| 368 | cl_mem cand_cosine = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 369 | sizeof(double) * CANDIDATE_CAPACITY, NULL, &err); | |
| 370 | cl_mem cand_next_free = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 371 | sizeof(uint32_t), &zero, &err); | |
| 372 | ||
| 373 | /* ─── Set kernel args (persistent across tests) ─── */ | |
| 374 | ||
| 375 | /* compute_word_norms: (sec_word, sec_count, word_norm_sq, num_sections) */ | |
| 376 | clSetKernelArg(k_norms, 0, sizeof(cl_mem), &sec_word); | |
| 377 | clSetKernelArg(k_norms, 1, sizeof(cl_mem), &sec_count); | |
| 378 | clSetKernelArg(k_norms, 2, sizeof(cl_mem), &word_norm_sq); | |
| 379 | /* arg 3 = num_sections — set per test */ | |
| 380 | ||
| 381 | /* build_disjunct_chains: (sec_djh, sec_count, djh_ht_keys, djh_ht_values, | |
| 382 | * sec_chain_next, num_sections) */ | |
| 383 | clSetKernelArg(k_chains, 0, sizeof(cl_mem), &sec_disjunct_hash); | |
| 384 | clSetKernelArg(k_chains, 1, sizeof(cl_mem), &sec_count); | |
| 385 | clSetKernelArg(k_chains, 2, sizeof(cl_mem), &djh_ht_keys); | |
| 386 | clSetKernelArg(k_chains, 3, sizeof(cl_mem), &djh_ht_values); | |
| 387 | clSetKernelArg(k_chains, 4, sizeof(cl_mem), &sec_chain_next); | |
| 388 | /* arg 5 = num_sections — set per test */ | |
| 389 | ||
| 390 | /* accumulate_dot_products: 14 args */ | |
| 391 | clSetKernelArg(k_dots, 0, sizeof(cl_mem), &sec_word); | |
| 392 | clSetKernelArg(k_dots, 1, sizeof(cl_mem), &sec_disjunct_hash); | |
| 393 | clSetKernelArg(k_dots, 2, sizeof(cl_mem), &sec_count); | |
| 394 | clSetKernelArg(k_dots, 3, sizeof(cl_mem), &djh_ht_keys); | |
| 395 | clSetKernelArg(k_dots, 4, sizeof(cl_mem), &djh_ht_values); | |
| 396 | clSetKernelArg(k_dots, 5, sizeof(cl_mem), &sec_chain_next); | |
| 397 | clSetKernelArg(k_dots, 6, sizeof(cl_mem), &cand_ht_keys); | |
| 398 | clSetKernelArg(k_dots, 7, sizeof(cl_mem), &cand_ht_values); | |
| 399 | clSetKernelArg(k_dots, 8, sizeof(cl_mem), &cand_word_a); | |
| 400 | clSetKernelArg(k_dots, 9, sizeof(cl_mem), &cand_word_b); | |
| 401 | clSetKernelArg(k_dots, 10, sizeof(cl_mem), &cand_dot); | |
| 402 | clSetKernelArg(k_dots, 11, sizeof(cl_mem), &cand_next_free); | |
| 403 | /* arg 12 = num_sections — set per test */ | |
| 404 | ||
| 405 | /* compute_cosines: (cand_word_a, cand_word_b, cand_dot, cand_cosine, | |
| 406 | * word_norm_sq, num_candidates) */ | |
| 407 | clSetKernelArg(k_cosines, 0, sizeof(cl_mem), &cand_word_a); | |
| 408 | clSetKernelArg(k_cosines, 1, sizeof(cl_mem), &cand_word_b); | |
| 409 | clSetKernelArg(k_cosines, 2, sizeof(cl_mem), &cand_dot); | |
| 410 | clSetKernelArg(k_cosines, 3, sizeof(cl_mem), &cand_cosine); | |
| 411 | clSetKernelArg(k_cosines, 4, sizeof(cl_mem), &word_norm_sq); | |
| 412 | /* arg 5 = num_candidates — set in pipeline */ | |
| 413 | ||
| 414 | /* filter_candidates */ | |
| 415 | clSetKernelArg(k_filter, 0, sizeof(cl_mem), &cand_word_a); | |
| 416 | clSetKernelArg(k_filter, 1, sizeof(cl_mem), &cand_word_b); | |
| 417 | clSetKernelArg(k_filter, 2, sizeof(cl_mem), &cand_cosine); | |
| 418 | /* args 3-8 set per test */ | |
| 419 | ||
| 420 | printf("GPU buffers ready\n\n"); | |
| 421 | ||
| 422 | /* ═══════════════════════════════════════════════════════════════ | |
| 423 | * TEST 1: Known cosine (2 words, 4 sections) | |
| 424 | * | |
| 425 | * Section 0: word=0, djh=0x100, count=3.0 | |
| 426 | * Section 1: word=0, djh=0x200, count=4.0 | |
| 427 | * Section 2: word=1, djh=0x100, count=5.0 | |
| 428 | * Section 3: word=1, djh=0x300, count=2.0 | |
| 429 | * | |
| 430 | * word 0: {0x100: 3, 0x200: 4} → norm² = 25, norm = 5 | |
| 431 | * word 1: {0x100: 5, 0x300: 2} → norm² = 29, norm = √29 | |
| 432 | * Shared: 0x100 → dot = 3×5 = 15 | |
| 433 | * Cosine = 15 / (5 × √29) = 0.5571 | |
| 434 | * ═══════════════════════════════════════════════════════════════ */ | |
| 435 | ||
| 436 | printf("--- Test 1: Known cosine (2 words, 4 sections) ---\n"); | |
| 437 | ||
| 438 | reset_cosine_buffers(queue, djh_ht_keys, djh_ht_values, | |
| 439 | sec_chain_next, word_norm_sq, cand_ht_keys, cand_ht_values, | |
| 440 | cand_dot, cand_cosine, cand_next_free); | |
| 441 | ||
| 442 | { | |
| 443 | uint32_t sw[] = {0, 0, 1, 1}; | |
| 444 | uint64_t sd[] = {0x100, 0x200, 0x100, 0x300}; | |
| 445 | double sc[] = {3.0, 4.0, 5.0, 2.0}; | |
| 446 | cl_uint ns = 4; | |
| 447 | ||
| 448 | upload_sections(queue, sec_word, sec_disjunct_hash, sec_count, | |
| 449 | sec_next_free, sw, sd, sc, ns); | |
| 450 | ||
| 451 | /* Set num_sections for each kernel */ | |
| 452 | clSetKernelArg(k_norms, 3, sizeof(cl_uint), &ns); | |
| 453 | clSetKernelArg(k_chains, 5, sizeof(cl_uint), &ns); | |
| 454 | clSetKernelArg(k_dots, 12, sizeof(cl_uint), &ns); | |
| 455 | ||
| 456 | uint32_t num_cands = 0; | |
| 457 | double t0 = now_ms(); | |
| 458 | run_cosine_pipeline(queue, k_norms, k_chains, k_dots, k_cosines, | |
| 459 | ns, cand_next_free, &num_cands); | |
| 460 | double t1 = now_ms(); | |
| 461 | ||
| 462 | /* Read results */ | |
| 463 | uint32_t h_wa[4], h_wb[4]; | |
| 464 | double h_dot[4], h_cos[4]; | |
| 465 | if (num_cands > 0) { | |
| 466 | clEnqueueReadBuffer(queue, cand_word_a, CL_TRUE, 0, | |
| 467 | sizeof(uint32_t) * num_cands, h_wa, 0, NULL, NULL); | |
| 468 | clEnqueueReadBuffer(queue, cand_word_b, CL_TRUE, 0, | |
| 469 | sizeof(uint32_t) * num_cands, h_wb, 0, NULL, NULL); | |
| 470 | clEnqueueReadBuffer(queue, cand_dot, CL_TRUE, 0, | |
| 471 | sizeof(double) * num_cands, h_dot, 0, NULL, NULL); | |
| 472 | clEnqueueReadBuffer(queue, cand_cosine, CL_TRUE, 0, | |
| 473 | sizeof(double) * num_cands, h_cos, 0, NULL, NULL); | |
| 474 | } | |
| 475 | ||
| 476 | /* Expected: 1 candidate pair (0,1) with dot=15.0, cosine≈0.5571 */ | |
| 477 | double expected_cos = 15.0 / (5.0 * sqrt(29.0)); | |
| 478 | ||
| 479 | printf(" Candidates: %u (expected 1)\n", num_cands); | |
| 480 | if (num_cands > 0) { | |
| 481 | printf(" Pair: (%u, %u) dot=%.1f cosine=%.4f\n", | |
| 482 | h_wa[0], h_wb[0], h_dot[0], h_cos[0]); | |
| 483 | printf(" Expected: (0, 1) dot=15.0 cosine=%.4f\n", expected_cos); | |
| 484 | } | |
| 485 | printf(" Time: %.2f ms\n", t1 - t0); | |
| 486 | ||
| 487 | int pass = (num_cands == 1) && | |
| 488 | (h_wa[0] == 0) && (h_wb[0] == 1) && | |
| 489 | (fabs(h_dot[0] - 15.0) < 0.01) && | |
| 490 | (fabs(h_cos[0] - expected_cos) < 0.001); | |
| 491 | printf(" %s\n\n", pass ? "PASS" : "FAIL"); | |
| 492 | if (pass) pass_count++; else fail_count++; | |
| 493 | } | |
| 494 | ||
| 495 | /* ═══════════════════════════════════════════════════════════════ | |
| 496 | * TEST 2: Three words — all pairwise cosines | |
| 497 | * | |
| 498 | * word 0: {X=0x10: 1, Y=0x20: 2} norm² = 5 | |
| 499 | * word 1: {X=0x10: 3, Z=0x30: 1} norm² = 10 | |
| 500 | * word 2: {Y=0x20: 2, Z=0x30: 4} norm² = 20 | |
| 501 | * | |
| 502 | * dot(0,1) = 1×3 = 3 cos = 3/√50 ≈ 0.4243 | |
| 503 | * dot(0,2) = 2×2 = 4 cos = 4/√100 = 0.4000 | |
| 504 | * dot(1,2) = 1×4 = 4 cos = 4/√200 ≈ 0.2828 | |
| 505 | * ═══════════════════════════════════════════════════════════════ */ | |
| 506 | ||
| 507 | printf("--- Test 2: Three words, all pairwise cosines ---\n"); | |
| 508 | ||
| 509 | reset_cosine_buffers(queue, djh_ht_keys, djh_ht_values, | |
| 510 | sec_chain_next, word_norm_sq, cand_ht_keys, cand_ht_values, | |
| 511 | cand_dot, cand_cosine, cand_next_free); | |
| 512 | ||
| 513 | { | |
| 514 | uint32_t sw[] = {0, 0, 1, 1, 2, 2}; | |
| 515 | uint64_t sd[] = {0x10, 0x20, 0x10, 0x30, 0x20, 0x30}; | |
| 516 | double sc[] = {1.0, 2.0, 3.0, 1.0, 2.0, 4.0}; | |
| 517 | cl_uint ns = 6; | |
| 518 | ||
| 519 | upload_sections(queue, sec_word, sec_disjunct_hash, sec_count, | |
| 520 | sec_next_free, sw, sd, sc, ns); | |
| 521 | ||
| 522 | clSetKernelArg(k_norms, 3, sizeof(cl_uint), &ns); | |
| 523 | clSetKernelArg(k_chains, 5, sizeof(cl_uint), &ns); | |
| 524 | clSetKernelArg(k_dots, 12, sizeof(cl_uint), &ns); | |
| 525 | ||
| 526 | uint32_t num_cands = 0; | |
| 527 | double t0 = now_ms(); | |
| 528 | run_cosine_pipeline(queue, k_norms, k_chains, k_dots, k_cosines, | |
| 529 | ns, cand_next_free, &num_cands); | |
| 530 | double t1 = now_ms(); | |
| 531 | ||
| 532 | uint32_t h_wa[8], h_wb[8]; | |
| 533 | double h_dot[8], h_cos[8]; | |
| 534 | if (num_cands > 0) { | |
| 535 | clEnqueueReadBuffer(queue, cand_word_a, CL_TRUE, 0, | |
| 536 | sizeof(uint32_t) * num_cands, h_wa, 0, NULL, NULL); | |
| 537 | clEnqueueReadBuffer(queue, cand_word_b, CL_TRUE, 0, | |
| 538 | sizeof(uint32_t) * num_cands, h_wb, 0, NULL, NULL); | |
| 539 | clEnqueueReadBuffer(queue, cand_dot, CL_TRUE, 0, | |
| 540 | sizeof(double) * num_cands, h_dot, 0, NULL, NULL); | |
| 541 | clEnqueueReadBuffer(queue, cand_cosine, CL_TRUE, 0, | |
| 542 | sizeof(double) * num_cands, h_cos, 0, NULL, NULL); | |
| 543 | } | |
| 544 | ||
| 545 | double exp_01 = 3.0 / sqrt(50.0); | |
| 546 | double exp_02 = 4.0 / sqrt(100.0); | |
| 547 | double exp_12 = 4.0 / sqrt(200.0); | |
| 548 | ||
| 549 | printf(" Candidates: %u (expected 3)\n", num_cands); | |
| 550 | ||
| 551 | /* Find each pair in results */ | |
| 552 | double got_01 = -1, got_02 = -1, got_12 = -1; | |
| 553 | double got_dot_01 = -1, got_dot_02 = -1, got_dot_12 = -1; | |
| 554 | for (uint32_t i = 0; i < num_cands; i++) { | |
| 555 | if (h_wa[i] == 0 && h_wb[i] == 1) | |
| 556 | { got_01 = h_cos[i]; got_dot_01 = h_dot[i]; } | |
| 557 | if (h_wa[i] == 0 && h_wb[i] == 2) | |
| 558 | { got_02 = h_cos[i]; got_dot_02 = h_dot[i]; } | |
| 559 | if (h_wa[i] == 1 && h_wb[i] == 2) | |
| 560 | { got_12 = h_cos[i]; got_dot_12 = h_dot[i]; } | |
| 561 | } | |
| 562 | ||
| 563 | printf(" (0,1): dot=%.1f cos=%.4f (exp dot=3.0 cos=%.4f)\n", | |
| 564 | got_dot_01, got_01, exp_01); | |
| 565 | printf(" (0,2): dot=%.1f cos=%.4f (exp dot=4.0 cos=%.4f)\n", | |
| 566 | got_dot_02, got_02, exp_02); | |
| 567 | printf(" (1,2): dot=%.1f cos=%.4f (exp dot=4.0 cos=%.4f)\n", | |
| 568 | got_dot_12, got_12, exp_12); | |
| 569 | printf(" Time: %.2f ms\n", t1 - t0); | |
| 570 | ||
| 571 | int pass = (num_cands == 3) && | |
| 572 | (fabs(got_01 - exp_01) < 0.001) && | |
| 573 | (fabs(got_02 - exp_02) < 0.001) && | |
| 574 | (fabs(got_12 - exp_12) < 0.001); | |
| 575 | printf(" %s\n\n", pass ? "PASS" : "FAIL"); | |
| 576 | if (pass) pass_count++; else fail_count++; | |
| 577 | } | |
| 578 | ||
| 579 | /* ═══════════════════════════════════════════════════════════════ | |
| 580 | * TEST 3: Identical vectors → cosine = 1.0 | |
| 581 | * | |
| 582 | * word 0: {X=0x10: 3, Y=0x20: 4} | |
| 583 | * word 1: {X=0x10: 3, Y=0x20: 4} | |
| 584 | * | |
| 585 | * dot = 9+16 = 25, norms = 5 each, cosine = 25/25 = 1.0 | |
| 586 | * ═══════════════════════════════════════════════════════════════ */ | |
| 587 | ||
| 588 | printf("--- Test 3: Identical vectors → cosine = 1.0 ---\n"); | |
| 589 | ||
| 590 | reset_cosine_buffers(queue, djh_ht_keys, djh_ht_values, | |
| 591 | sec_chain_next, word_norm_sq, cand_ht_keys, cand_ht_values, | |
| 592 | cand_dot, cand_cosine, cand_next_free); | |
| 593 | ||
| 594 | { | |
| 595 | uint32_t sw[] = {0, 0, 1, 1}; | |
| 596 | uint64_t sd[] = {0x10, 0x20, 0x10, 0x20}; | |
| 597 | double sc[] = {3.0, 4.0, 3.0, 4.0}; | |
| 598 | cl_uint ns = 4; | |
| 599 | ||
| 600 | upload_sections(queue, sec_word, sec_disjunct_hash, sec_count, | |
| 601 | sec_next_free, sw, sd, sc, ns); | |
| 602 | ||
| 603 | clSetKernelArg(k_norms, 3, sizeof(cl_uint), &ns); | |
| 604 | clSetKernelArg(k_chains, 5, sizeof(cl_uint), &ns); | |
| 605 | clSetKernelArg(k_dots, 12, sizeof(cl_uint), &ns); | |
| 606 | ||
| 607 | uint32_t num_cands = 0; | |
| 608 | run_cosine_pipeline(queue, k_norms, k_chains, k_dots, k_cosines, | |
| 609 | ns, cand_next_free, &num_cands); | |
| 610 | ||
| 611 | double h_cos = 0; | |
| 612 | if (num_cands > 0) { | |
| 613 | clEnqueueReadBuffer(queue, cand_cosine, CL_TRUE, 0, | |
| 614 | sizeof(double), &h_cos, 0, NULL, NULL); | |
| 615 | } | |
| 616 | ||
| 617 | printf(" Candidates: %u (expected 1)\n", num_cands); | |
| 618 | printf(" Cosine: %.4f (expected 1.0000)\n", h_cos); | |
| 619 | ||
| 620 | int pass = (num_cands == 1) && (fabs(h_cos - 1.0) < 0.001); | |
| 621 | printf(" %s\n\n", pass ? "PASS" : "FAIL"); | |
| 622 | if (pass) pass_count++; else fail_count++; | |
| 623 | } | |
| 624 | ||
| 625 | /* ═══════════════════════════════════════════════════════════════ | |
| 626 | * TEST 4: No shared disjuncts → 0 candidates | |
| 627 | * | |
| 628 | * word 0: {X=0x10: 1} | |
| 629 | * word 1: {Y=0x20: 1} | |
| 630 | * | |
| 631 | * No shared disjuncts → no chain overlap → 0 candidates | |
| 632 | * ═══════════════════════════════════════════════════════════════ */ | |
| 633 | ||
| 634 | printf("--- Test 4: No shared disjuncts → 0 candidates ---\n"); | |
| 635 | ||
| 636 | reset_cosine_buffers(queue, djh_ht_keys, djh_ht_values, | |
| 637 | sec_chain_next, word_norm_sq, cand_ht_keys, cand_ht_values, | |
| 638 | cand_dot, cand_cosine, cand_next_free); | |
| 639 | ||
| 640 | { | |
| 641 | uint32_t sw[] = {0, 1}; | |
| 642 | uint64_t sd[] = {0x10, 0x20}; | |
| 643 | double sc[] = {1.0, 1.0}; | |
| 644 | cl_uint ns = 2; | |
| 645 | ||
| 646 | upload_sections(queue, sec_word, sec_disjunct_hash, sec_count, | |
| 647 | sec_next_free, sw, sd, sc, ns); | |
| 648 | ||
| 649 | clSetKernelArg(k_norms, 3, sizeof(cl_uint), &ns); | |
| 650 | clSetKernelArg(k_chains, 5, sizeof(cl_uint), &ns); | |
| 651 | clSetKernelArg(k_dots, 12, sizeof(cl_uint), &ns); | |
| 652 | ||
| 653 | uint32_t num_cands = 0; | |
| 654 | run_cosine_pipeline(queue, k_norms, k_chains, k_dots, k_cosines, | |
| 655 | ns, cand_next_free, &num_cands); | |
| 656 | ||
| 657 | printf(" Candidates: %u (expected 0)\n", num_cands); | |
| 658 | ||
| 659 | int pass = (num_cands == 0); | |
| 660 | printf(" %s\n\n", pass ? "PASS" : "FAIL"); | |
| 661 | if (pass) pass_count++; else fail_count++; | |
| 662 | } | |
| 663 | ||
| 664 | /* ═══════════════════════════════════════════════════════════════ | |
| 665 | * TEST 5: Filter candidates above threshold | |
| 666 | * | |
| 667 | * Reuse test 2's scenario (3 words): | |
| 668 | * cos(0,1) ≈ 0.4243 | |
| 669 | * cos(0,2) = 0.4000 | |
| 670 | * cos(1,2) ≈ 0.2828 | |
| 671 | * | |
| 672 | * Filter at 0.35 → should get 2 candidates (0,1) and (0,2) | |
| 673 | * ═══════════════════════════════════════════════════════════════ */ | |
| 674 | ||
| 675 | printf("--- Test 5: Filter candidates above threshold ---\n"); | |
| 676 | ||
| 677 | reset_cosine_buffers(queue, djh_ht_keys, djh_ht_values, | |
| 678 | sec_chain_next, word_norm_sq, cand_ht_keys, cand_ht_values, | |
| 679 | cand_dot, cand_cosine, cand_next_free); | |
| 680 | ||
| 681 | { | |
| 682 | uint32_t sw[] = {0, 0, 1, 1, 2, 2}; | |
| 683 | uint64_t sd[] = {0x10, 0x20, 0x10, 0x30, 0x20, 0x30}; | |
| 684 | double sc[] = {1.0, 2.0, 3.0, 1.0, 2.0, 4.0}; | |
| 685 | cl_uint ns = 6; | |
| 686 | ||
| 687 | upload_sections(queue, sec_word, sec_disjunct_hash, sec_count, | |
| 688 | sec_next_free, sw, sd, sc, ns); | |
| 689 | ||
| 690 | clSetKernelArg(k_norms, 3, sizeof(cl_uint), &ns); | |
| 691 | clSetKernelArg(k_chains, 5, sizeof(cl_uint), &ns); | |
| 692 | clSetKernelArg(k_dots, 12, sizeof(cl_uint), &ns); | |
| 693 | ||
| 694 | uint32_t num_cands = 0; | |
| 695 | run_cosine_pipeline(queue, k_norms, k_chains, k_dots, k_cosines, | |
| 696 | ns, cand_next_free, &num_cands); | |
| 697 | ||
| 698 | /* Now filter */ | |
| 699 | cl_double threshold = 0.35; | |
| 700 | cl_uint max_output = 64; | |
| 701 | cl_mem out_wa = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 702 | sizeof(uint32_t) * max_output, NULL, &err); | |
| 703 | cl_mem out_wb = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 704 | sizeof(uint32_t) * max_output, NULL, &err); | |
| 705 | cl_mem out_cos = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 706 | sizeof(double) * max_output, NULL, &err); | |
| 707 | cl_mem out_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 708 | sizeof(uint32_t), &zero, &err); | |
| 709 | ||
| 710 | clSetKernelArg(k_filter, 3, sizeof(cl_uint), &num_cands); | |
| 711 | clSetKernelArg(k_filter, 4, sizeof(cl_double), &threshold); | |
| 712 | clSetKernelArg(k_filter, 5, sizeof(cl_mem), &out_wa); | |
| 713 | clSetKernelArg(k_filter, 6, sizeof(cl_mem), &out_wb); | |
| 714 | clSetKernelArg(k_filter, 7, sizeof(cl_mem), &out_cos); | |
| 715 | clSetKernelArg(k_filter, 8, sizeof(cl_mem), &out_count); | |
| 716 | clSetKernelArg(k_filter, 9, sizeof(cl_uint), &max_output); | |
| 717 | ||
| 718 | size_t local = 256; | |
| 719 | size_t gs = ((num_cands + local - 1) / local) * local; | |
| 720 | err = clEnqueueNDRangeKernel(queue, k_filter, 1, NULL, | |
| 721 | &gs, &local, 0, NULL, NULL); | |
| 722 | CL_CHECK(err, "enqueue filter"); | |
| 723 | clFinish(queue); | |
| 724 | ||
| 725 | uint32_t n_filtered; | |
| 726 | clEnqueueReadBuffer(queue, out_count, CL_TRUE, 0, | |
| 727 | sizeof(uint32_t), &n_filtered, 0, NULL, NULL); | |
| 728 | ||
| 729 | uint32_t fwa[8], fwb[8]; | |
| 730 | double fcos[8]; | |
| 731 | if (n_filtered > 0) { | |
| 732 | clEnqueueReadBuffer(queue, out_wa, CL_TRUE, 0, | |
| 733 | sizeof(uint32_t) * n_filtered, fwa, 0, NULL, NULL); | |
| 734 | clEnqueueReadBuffer(queue, out_wb, CL_TRUE, 0, | |
| 735 | sizeof(uint32_t) * n_filtered, fwb, 0, NULL, NULL); | |
| 736 | clEnqueueReadBuffer(queue, out_cos, CL_TRUE, 0, | |
| 737 | sizeof(double) * n_filtered, fcos, 0, NULL, NULL); | |
| 738 | } | |
| 739 | ||
| 740 | printf(" Total candidates: %u, filtered (>0.35): %u (expected 2)\n", | |
| 741 | num_cands, n_filtered); | |
| 742 | for (uint32_t i = 0; i < n_filtered; i++) { | |
| 743 | printf(" (%u, %u) cos=%.4f\n", fwa[i], fwb[i], fcos[i]); | |
| 744 | } | |
| 745 | ||
| 746 | /* cos(1,2) ≈ 0.2828 should be filtered out */ | |
| 747 | int pass = (n_filtered == 2); | |
| 748 | printf(" %s\n\n", pass ? "PASS" : "FAIL"); | |
| 749 | if (pass) pass_count++; else fail_count++; | |
| 750 | ||
| 751 | clReleaseMemObject(out_wa); | |
| 752 | clReleaseMemObject(out_wb); | |
| 753 | clReleaseMemObject(out_cos); | |
| 754 | clReleaseMemObject(out_count); | |
| 755 | } | |
| 756 | ||
| 757 | /* ═══════════════════════════════════════════════════════════════ | |
| 758 | * TEST 6: Benchmark — 1000 sentences → sections → cosines | |
| 759 | * | |
| 760 | * Full pipeline: | |
| 761 | * extract_sections (Phase 4) → cosine pipeline (Phase 5) | |
| 762 | * | |
| 763 | * 1000 sentences, 10-20 words each, chain MST parse. | |
| 764 | * 500 word vocabulary for realistic disjunct sharing. | |
| 765 | * ═══════════════════════════════════════════════════════════════ */ | |
| 766 | ||
| 767 | printf("--- Test 6: Benchmark (1000 sentences, full pipeline) ---\n"); | |
| 768 | ||
| 769 | /* Reset everything */ | |
| 770 | reset_section_pool(queue, sht_keys, sht_values, sec_count, sec_next_free); | |
| 771 | reset_cosine_buffers(queue, djh_ht_keys, djh_ht_values, | |
| 772 | sec_chain_next, word_norm_sq, cand_ht_keys, cand_ht_values, | |
| 773 | cand_dot, cand_cosine, cand_next_free); | |
| 774 | { | |
| 775 | uint32_t tsec_zero = 0; | |
| 776 | clEnqueueWriteBuffer(queue, total_sections_created, CL_TRUE, 0, | |
| 777 | sizeof(uint32_t), &tsec_zero, 0, NULL, NULL); | |
| 778 | } | |
| 779 | ||
| 780 | srand(42); | |
| 781 | uint32_t bench_ns = 1000; | |
| 782 | uint32_t vocab_size = 500; | |
| 783 | ||
| 784 | uint32_t max_words = bench_ns * 25; | |
| 785 | uint32_t max_edges = bench_ns * 25; | |
| 786 | uint32_t* b_words = malloc(sizeof(uint32_t) * max_words); | |
| 787 | uint32_t* b_sent_offsets = malloc(sizeof(uint32_t) * bench_ns); | |
| 788 | uint32_t* b_sent_lengths = malloc(sizeof(uint32_t) * bench_ns); | |
| 789 | uint32_t* b_edge_p1 = malloc(sizeof(uint32_t) * max_edges); | |
| 790 | uint32_t* b_edge_p2 = malloc(sizeof(uint32_t) * max_edges); | |
| 791 | uint32_t* b_edge_offsets = malloc(sizeof(uint32_t) * bench_ns); | |
| 792 | uint32_t* b_edge_counts = malloc(sizeof(uint32_t) * bench_ns); | |
| 793 | ||
| 794 | uint32_t word_pos = 0, edge_pos = 0; | |
| 795 | for (uint32_t s = 0; s < bench_ns; s++) { | |
| 796 | uint32_t slen = 10 + (rand() % 11); | |
| 797 | b_sent_offsets[s] = word_pos; | |
| 798 | b_sent_lengths[s] = slen; | |
| 799 | b_edge_offsets[s] = edge_pos; | |
| 800 | b_edge_counts[s] = slen - 1; | |
| 801 | ||
| 802 | for (uint32_t w = 0; w < slen; w++) | |
| 803 | b_words[word_pos++] = rand() % vocab_size; | |
| 804 | for (uint32_t e = 0; e < slen - 1; e++) { | |
| 805 | b_edge_p1[edge_pos] = e; | |
| 806 | b_edge_p2[edge_pos] = e + 1; | |
| 807 | edge_pos++; | |
| 808 | } | |
| 809 | } | |
| 810 | ||
| 811 | printf(" Sentences: %u, words: %u, edges: %u\n", bench_ns, word_pos, edge_pos); | |
| 812 | ||
| 813 | cl_mem d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 814 | sizeof(uint32_t) * word_pos, b_words, &err); | |
| 815 | cl_mem d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 816 | sizeof(uint32_t) * bench_ns, b_sent_offsets, &err); | |
| 817 | cl_mem d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 818 | sizeof(uint32_t) * bench_ns, b_sent_lengths, &err); | |
| 819 | cl_mem d_edge_p1 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 820 | sizeof(uint32_t) * edge_pos, b_edge_p1, &err); | |
| 821 | cl_mem d_edge_p2 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 822 | sizeof(uint32_t) * edge_pos, b_edge_p2, &err); | |
| 823 | cl_mem d_edge_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 824 | sizeof(uint32_t) * bench_ns, b_edge_offsets, &err); | |
| 825 | cl_mem d_edge_counts = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 826 | sizeof(uint32_t) * bench_ns, b_edge_counts, &err); | |
| 827 | ||
| 828 | /* Set extract_sections args */ | |
| 829 | cl_uint tw = word_pos; | |
| 830 | clSetKernelArg(k_extract, 0, sizeof(cl_mem), &d_flat_words); | |
| 831 | clSetKernelArg(k_extract, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 832 | clSetKernelArg(k_extract, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 833 | clSetKernelArg(k_extract, 3, sizeof(cl_uint), &bench_ns); | |
| 834 | clSetKernelArg(k_extract, 4, sizeof(cl_uint), &tw); | |
| 835 | clSetKernelArg(k_extract, 5, sizeof(cl_mem), &d_edge_p1); | |
| 836 | clSetKernelArg(k_extract, 6, sizeof(cl_mem), &d_edge_p2); | |
| 837 | clSetKernelArg(k_extract, 7, sizeof(cl_mem), &d_edge_offsets); | |
| 838 | clSetKernelArg(k_extract, 8, sizeof(cl_mem), &d_edge_counts); | |
| 839 | clSetKernelArg(k_extract, 9, sizeof(cl_mem), &sht_keys); | |
| 840 | clSetKernelArg(k_extract, 10, sizeof(cl_mem), &sht_values); | |
| 841 | clSetKernelArg(k_extract, 11, sizeof(cl_mem), &sec_word); | |
| 842 | clSetKernelArg(k_extract, 12, sizeof(cl_mem), &sec_disjunct_hash); | |
| 843 | clSetKernelArg(k_extract, 13, sizeof(cl_mem), &sec_count); | |
| 844 | clSetKernelArg(k_extract, 14, sizeof(cl_mem), &sec_next_free); | |
| 845 | clSetKernelArg(k_extract, 15, sizeof(cl_mem), &total_sections_created); | |
| 846 | ||
| 847 | size_t local = 256; | |
| 848 | size_t gs; | |
| 849 | ||
| 850 | /* Phase 4: Extract sections */ | |
| 851 | double t_start = now_ms(); | |
| 852 | ||
| 853 | gs = ((tw + local - 1) / local) * local; | |
| 854 | err = clEnqueueNDRangeKernel(queue, k_extract, 1, NULL, | |
| 855 | &gs, &local, 0, NULL, NULL); | |
| 856 | CL_CHECK(err, "enqueue extract"); | |
| 857 | clFinish(queue); | |
| 858 | ||
| 859 | double t_extract = now_ms(); | |
| 860 | ||
| 861 | /* Read section count */ | |
| 862 | uint32_t h_num_sections; | |
| 863 | clEnqueueReadBuffer(queue, sec_next_free, CL_TRUE, 0, | |
| 864 | sizeof(uint32_t), &h_num_sections, 0, NULL, NULL); | |
| 865 | ||
| 866 | printf(" Sections extracted: %u (%.2f ms)\n", | |
| 867 | h_num_sections, t_extract - t_start); | |
| 868 | ||
| 869 | /* Phase 5: Cosine pipeline */ | |
| 870 | clSetKernelArg(k_norms, 3, sizeof(cl_uint), &h_num_sections); | |
| 871 | clSetKernelArg(k_chains, 5, sizeof(cl_uint), &h_num_sections); | |
| 872 | clSetKernelArg(k_dots, 12, sizeof(cl_uint), &h_num_sections); | |
| 873 | ||
| 874 | double t_cos_start = now_ms(); | |
| 875 | ||
| 876 | uint32_t num_cands = 0; | |
| 877 | run_cosine_pipeline(queue, k_norms, k_chains, k_dots, k_cosines, | |
| 878 | h_num_sections, cand_next_free, &num_cands); | |
| 879 | ||
| 880 | double t_cos_end = now_ms(); | |
| 881 | ||
| 882 | double total_time = t_cos_end - t_start; | |
| 883 | double cos_time = t_cos_end - t_cos_start; | |
| 884 | ||
| 885 | printf(" Candidate pairs: %u\n", num_cands); | |
| 886 | printf(" Cosine pipeline: %.2f ms\n", cos_time); | |
| 887 | printf(" Full pipeline (extract + cosine): %.2f ms\n", total_time); | |
| 888 | printf(" Throughput: %.0f sentences/sec\n", | |
| 889 | bench_ns / (total_time / 1000.0)); | |
| 890 | ||
| 891 | if (num_cands > 0) { | |
| 892 | /* Read a few top cosines to verify */ | |
| 893 | uint32_t peek = (num_cands < 8) ? num_cands : 8; | |
| 894 | double h_cos[8]; | |
| 895 | clEnqueueReadBuffer(queue, cand_cosine, CL_TRUE, 0, | |
| 896 | sizeof(double) * peek, h_cos, 0, NULL, NULL); | |
| 897 | ||
| 898 | /* Find max cosine */ | |
| 899 | double max_cos = 0; | |
| 900 | for (uint32_t i = 0; i < peek; i++) | |
| 901 | if (h_cos[i] > max_cos) max_cos = h_cos[i]; | |
| 902 | printf(" Max cosine (first %u): %.4f\n", peek, max_cos); | |
| 903 | } | |
| 904 | ||
| 905 | int t6_pass = (h_num_sections > 0) && (num_cands > 0) && (total_time < 5000.0); | |
| 906 | printf(" %s\n\n", t6_pass ? "PASS" : "FAIL"); | |
| 907 | if (t6_pass) pass_count++; else fail_count++; | |
| 908 | ||
| 909 | /* ─── Summary ─── */ | |
| 910 | ||
| 911 | printf("=== Results: %d PASS, %d FAIL ===\n", pass_count, fail_count); | |
| 912 | ||
| 913 | /* Cleanup */ | |
| 914 | free(b_words); free(b_sent_offsets); free(b_sent_lengths); | |
| 915 | free(b_edge_p1); free(b_edge_p2); free(b_edge_offsets); free(b_edge_counts); | |
| 916 | free(src_ht); free(src_as); free(src_sc); free(src_cos); free(combined); | |
| 917 | ||
| 918 | clReleaseMemObject(d_flat_words); | |
| 919 | clReleaseMemObject(d_sent_offsets); | |
| 920 | clReleaseMemObject(d_sent_lengths); | |
| 921 | clReleaseMemObject(d_edge_p1); | |
| 922 | clReleaseMemObject(d_edge_p2); | |
| 923 | clReleaseMemObject(d_edge_offsets); | |
| 924 | clReleaseMemObject(d_edge_counts); | |
| 925 | clReleaseMemObject(sec_word); | |
| 926 | clReleaseMemObject(sec_disjunct_hash); | |
| 927 | clReleaseMemObject(sec_count); | |
| 928 | clReleaseMemObject(sec_next_free); | |
| 929 | clReleaseMemObject(sht_keys); | |
| 930 | clReleaseMemObject(sht_values); | |
| 931 | clReleaseMemObject(total_sections_created); | |
| 932 | clReleaseMemObject(djh_ht_keys); | |
| 933 | clReleaseMemObject(djh_ht_values); | |
| 934 | clReleaseMemObject(sec_chain_next); | |
| 935 | clReleaseMemObject(word_norm_sq); | |
| 936 | clReleaseMemObject(cand_ht_keys); | |
| 937 | clReleaseMemObject(cand_ht_values); | |
| 938 | clReleaseMemObject(cand_word_a); | |
| 939 | clReleaseMemObject(cand_word_b); | |
| 940 | clReleaseMemObject(cand_dot); | |
| 941 | clReleaseMemObject(cand_cosine); | |
| 942 | clReleaseMemObject(cand_next_free); | |
| 943 | clReleaseKernel(k_norms); | |
| 944 | clReleaseKernel(k_chains); | |
| 945 | clReleaseKernel(k_dots); | |
| 946 | clReleaseKernel(k_cosines); | |
| 947 | clReleaseKernel(k_filter); | |
| 948 | clReleaseKernel(k_extract); | |
| 949 | clReleaseProgram(program); | |
| 950 | clReleaseCommandQueue(queue); | |
| 951 | clReleaseContext(ctx); | |
| 952 | ||
| 953 | return fail_count > 0 ? 1 : 0; | |
| 954 | } |
| … | … | @@ -0,0 +1,720 @@ |
| 1 | /* | |
| 2 | * test-counting.c -- Test GPU sentence counting kernel | |
| 3 | * | |
| 4 | * Compile: gcc -O2 -o test-counting test-counting.c -lOpenCL -lm | |
| 5 | * Run: ./test-counting | |
| 6 | * | |
| 7 | * Tests: | |
| 8 | * 1. Simple sentence pair counting (window=2, exact verification) | |
| 9 | * 2. Multi-sentence batch (verify no cross-boundary pairs) | |
| 10 | * 3. Read pairs kernel (readback verification) | |
| 11 | * 4. Binary search variant (count_sentence_pairs_large) | |
| 12 | * 5. Benchmark: 1000 sentences with window=6 | |
| 13 | */ | |
| 14 | ||
| 15 | #include <stdio.h> | |
| 16 | #include <stdlib.h> | |
| 17 | #include <stdint.h> | |
| 18 | #include <string.h> | |
| 19 | #include <math.h> | |
| 20 | #include <time.h> | |
| 21 | #include <CL/cl.h> | |
| 22 | ||
| 23 | /* ─── Pool capacities ─── */ | |
| 24 | ||
| 25 | #define WORD_CAPACITY (128 * 1024) | |
| 26 | #define PAIR_CAPACITY (4 * 1024 * 1024) | |
| 27 | #define SECTION_CAPACITY (1024 * 1024) | |
| 28 | #define WORD_HT_CAPACITY (256 * 1024) | |
| 29 | #define PAIR_HT_CAPACITY (8 * 1024 * 1024) | |
| 30 | #define SECTION_HT_CAPACITY (2 * 1024 * 1024) | |
| 31 | ||
| 32 | #define HT_EMPTY_KEY 0xFFFFFFFFFFFFFFFFULL | |
| 33 | #define HT_EMPTY_VALUE 0xFFFFFFFFU | |
| 34 | ||
| 35 | /* ─── Error checking ─── */ | |
| 36 | ||
| 37 | #define CL_CHECK(err, msg) do { \ | |
| 38 | if ((err) != CL_SUCCESS) { \ | |
| 39 | fprintf(stderr, "OpenCL error %d at %s:%d: %s\n", \ | |
| 40 | (err), __FILE__, __LINE__, (msg)); \ | |
| 41 | exit(1); \ | |
| 42 | } \ | |
| 43 | } while(0) | |
| 44 | ||
| 45 | /* ─── Read file ─── */ | |
| 46 | ||
| 47 | char* read_file(const char* path, size_t* len) | |
| 48 | { | |
| 49 | FILE* f = fopen(path, "r"); | |
| 50 | if (!f) { fprintf(stderr, "Cannot open %s\n", path); exit(1); } | |
| 51 | fseek(f, 0, SEEK_END); | |
| 52 | *len = ftell(f); | |
| 53 | fseek(f, 0, SEEK_SET); | |
| 54 | char* buf = malloc(*len + 1); | |
| 55 | size_t n = fread(buf, 1, *len, f); | |
| 56 | buf[n] = '\0'; | |
| 57 | *len = n; | |
| 58 | fclose(f); | |
| 59 | return buf; | |
| 60 | } | |
| 61 | ||
| 62 | /* ─── Timing ─── */ | |
| 63 | ||
| 64 | double now_ms(void) | |
| 65 | { | |
| 66 | struct timespec ts; | |
| 67 | clock_gettime(CLOCK_MONOTONIC, &ts); | |
| 68 | return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0; | |
| 69 | } | |
| 70 | ||
| 71 | /* ─── Helper: reset pair pool and counters ─── */ | |
| 72 | ||
| 73 | void reset_pools(cl_command_queue queue, | |
| 74 | cl_mem pht_keys, cl_mem pht_values, | |
| 75 | cl_mem pair_count, cl_mem pair_mi, cl_mem pair_flags, | |
| 76 | cl_mem word_count, | |
| 77 | cl_mem pair_next_free, cl_mem total_pair_count) | |
| 78 | { | |
| 79 | uint8_t pat_ff = 0xFF; | |
| 80 | uint8_t pat_00 = 0x00; | |
| 81 | uint32_t zero = 0; | |
| 82 | ||
| 83 | clEnqueueFillBuffer(queue, pht_keys, &pat_ff, 1, 0, | |
| 84 | sizeof(uint64_t) * PAIR_HT_CAPACITY, 0, NULL, NULL); | |
| 85 | clEnqueueFillBuffer(queue, pht_values, &pat_ff, 1, 0, | |
| 86 | sizeof(uint32_t) * PAIR_HT_CAPACITY, 0, NULL, NULL); | |
| 87 | clEnqueueFillBuffer(queue, pair_count, &pat_00, 1, 0, | |
| 88 | sizeof(double) * PAIR_CAPACITY, 0, NULL, NULL); | |
| 89 | clEnqueueFillBuffer(queue, pair_mi, &pat_00, 1, 0, | |
| 90 | sizeof(double) * PAIR_CAPACITY, 0, NULL, NULL); | |
| 91 | clEnqueueFillBuffer(queue, pair_flags, &pat_00, 1, 0, | |
| 92 | sizeof(uint32_t) * PAIR_CAPACITY, 0, NULL, NULL); | |
| 93 | clEnqueueFillBuffer(queue, word_count, &pat_00, 1, 0, | |
| 94 | sizeof(double) * WORD_CAPACITY, 0, NULL, NULL); | |
| 95 | clEnqueueWriteBuffer(queue, pair_next_free, CL_FALSE, 0, | |
| 96 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 97 | clEnqueueWriteBuffer(queue, total_pair_count, CL_FALSE, 0, | |
| 98 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 99 | clFinish(queue); | |
| 100 | } | |
| 101 | ||
| 102 | /* ─── Main ─── */ | |
| 103 | ||
| 104 | int main(int argc, char** argv) | |
| 105 | { | |
| 106 | cl_int err; | |
| 107 | int pass_count = 0, fail_count = 0; | |
| 108 | ||
| 109 | printf("=== GPU Sentence Counting Test ===\n\n"); | |
| 110 | ||
| 111 | /* ─── OpenCL setup ─── */ | |
| 112 | ||
| 113 | cl_platform_id platform; | |
| 114 | err = clGetPlatformIDs(1, &platform, NULL); | |
| 115 | CL_CHECK(err, "platform"); | |
| 116 | ||
| 117 | cl_device_id device; | |
| 118 | err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL); | |
| 119 | CL_CHECK(err, "device"); | |
| 120 | ||
| 121 | char dev_name[256]; | |
| 122 | clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(dev_name), dev_name, NULL); | |
| 123 | printf("GPU: %s\n", dev_name); | |
| 124 | ||
| 125 | cl_context ctx = clCreateContext(NULL, 1, &device, NULL, NULL, &err); | |
| 126 | CL_CHECK(err, "context"); | |
| 127 | ||
| 128 | cl_command_queue queue = clCreateCommandQueue(ctx, device, 0, &err); | |
| 129 | CL_CHECK(err, "queue"); | |
| 130 | ||
| 131 | /* ─── Load and concatenate kernel sources ─── */ | |
| 132 | ||
| 133 | size_t ht_len, as_len, ct_len; | |
| 134 | char* ht_src = read_file("gpu-hashtable.cl", &ht_len); | |
| 135 | char* as_src = read_file("gpu-atomspace.cl", &as_len); | |
| 136 | char* ct_src = read_file("gpu-counting.cl", &ct_len); | |
| 137 | ||
| 138 | size_t total_len = ht_len + 1 + as_len + 1 + ct_len; | |
| 139 | char* combined = malloc(total_len + 1); | |
| 140 | memcpy(combined, ht_src, ht_len); | |
| 141 | combined[ht_len] = '\n'; | |
| 142 | memcpy(combined + ht_len + 1, as_src, as_len); | |
| 143 | combined[ht_len + 1 + as_len] = '\n'; | |
| 144 | memcpy(combined + ht_len + 1 + as_len + 1, ct_src, ct_len); | |
| 145 | combined[total_len] = '\0'; | |
| 146 | ||
| 147 | cl_program program = clCreateProgramWithSource(ctx, 1, | |
| 148 | (const char**)&combined, &total_len, &err); | |
| 149 | CL_CHECK(err, "create program"); | |
| 150 | ||
| 151 | char build_opts[512]; | |
| 152 | snprintf(build_opts, sizeof(build_opts), | |
| 153 | "-cl-std=CL1.2 " | |
| 154 | "-DWORD_CAPACITY=%d " | |
| 155 | "-DPAIR_CAPACITY=%d " | |
| 156 | "-DSECTION_CAPACITY=%d " | |
| 157 | "-DWORD_HT_CAPACITY=%d " | |
| 158 | "-DPAIR_HT_CAPACITY=%d " | |
| 159 | "-DSECTION_HT_CAPACITY=%d", | |
| 160 | WORD_CAPACITY, PAIR_CAPACITY, SECTION_CAPACITY, | |
| 161 | WORD_HT_CAPACITY, PAIR_HT_CAPACITY, SECTION_HT_CAPACITY); | |
| 162 | ||
| 163 | err = clBuildProgram(program, 1, &device, build_opts, NULL, NULL); | |
| 164 | if (err != CL_SUCCESS) { | |
| 165 | char log[16384]; | |
| 166 | clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, | |
| 167 | sizeof(log), log, NULL); | |
| 168 | fprintf(stderr, "Build error:\n%s\n", log); | |
| 169 | return 1; | |
| 170 | } | |
| 171 | printf("Kernels compiled successfully\n\n"); | |
| 172 | ||
| 173 | /* ─── Create kernels ─── */ | |
| 174 | ||
| 175 | cl_kernel k_count = clCreateKernel(program, "count_sentence_pairs", &err); | |
| 176 | CL_CHECK(err, "kernel count_sentence_pairs"); | |
| 177 | cl_kernel k_count_large = clCreateKernel(program, "count_sentence_pairs_large", &err); | |
| 178 | CL_CHECK(err, "kernel count_sentence_pairs_large"); | |
| 179 | cl_kernel k_read = clCreateKernel(program, "read_pairs", &err); | |
| 180 | CL_CHECK(err, "kernel read_pairs"); | |
| 181 | ||
| 182 | size_t local_size = 256; | |
| 183 | ||
| 184 | /* ─── Allocate GPU buffers ─── */ | |
| 185 | ||
| 186 | printf("Allocating GPU buffers...\n"); | |
| 187 | uint32_t zero = 0; | |
| 188 | ||
| 189 | /* Pair hash table */ | |
| 190 | cl_mem pht_keys = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 191 | sizeof(uint64_t) * PAIR_HT_CAPACITY, NULL, &err); | |
| 192 | CL_CHECK(err, "pht_keys"); | |
| 193 | cl_mem pht_values = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 194 | sizeof(uint32_t) * PAIR_HT_CAPACITY, NULL, &err); | |
| 195 | CL_CHECK(err, "pht_values"); | |
| 196 | ||
| 197 | /* Pair pool SoA */ | |
| 198 | cl_mem pair_word_a = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 199 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 200 | cl_mem pair_word_b = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 201 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 202 | cl_mem pair_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 203 | sizeof(double) * PAIR_CAPACITY, NULL, &err); | |
| 204 | cl_mem pair_mi = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 205 | sizeof(double) * PAIR_CAPACITY, NULL, &err); | |
| 206 | cl_mem pair_flags = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 207 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 208 | ||
| 209 | /* Pair bump allocator */ | |
| 210 | cl_mem pair_next_free = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 211 | sizeof(uint32_t), &zero, &err); | |
| 212 | ||
| 213 | /* Word count array (marginals) — used directly by counting kernel */ | |
| 214 | cl_mem word_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 215 | sizeof(double) * WORD_CAPACITY, NULL, &err); | |
| 216 | ||
| 217 | /* Total pair count (global atomic counter) */ | |
| 218 | cl_mem total_pair_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 219 | sizeof(uint32_t), &zero, &err); | |
| 220 | ||
| 221 | /* Initial reset */ | |
| 222 | reset_pools(queue, pht_keys, pht_values, pair_count, pair_mi, | |
| 223 | pair_flags, word_count, pair_next_free, total_pair_count); | |
| 224 | ||
| 225 | printf("GPU buffers ready\n\n"); | |
| 226 | ||
| 227 | /* ═══════════════════════════════════════════════════════════════ | |
| 228 | * TEST 1: Simple sentence pair counting | |
| 229 | * | |
| 230 | * Sentence: word indices [0, 1, 2, 3] — 4 words | |
| 231 | * Window = 2 | |
| 232 | * | |
| 233 | * Expected pairs (one thread per word position): | |
| 234 | * pos 0: (0,1), (0,2) | |
| 235 | * pos 1: (1,2), (1,3) | |
| 236 | * pos 2: (2,3) | |
| 237 | * pos 3: — | |
| 238 | * | |
| 239 | * = 5 unique pairs, 5 count events | |
| 240 | * Word marginals: word0=2, word1=3, word2=3, word3=2 | |
| 241 | * ═══════════════════════════════════════════════════════════════ */ | |
| 242 | ||
| 243 | printf("--- Test 1: Simple sentence (window=2) ---\n"); | |
| 244 | ||
| 245 | uint32_t sent1_words[] = {0, 1, 2, 3}; | |
| 246 | uint32_t sent1_offset = 0; | |
| 247 | uint32_t sent1_length = 4; | |
| 248 | cl_uint num_sentences = 1; | |
| 249 | cl_uint tw = 4; | |
| 250 | cl_uint window_size = 2; | |
| 251 | ||
| 252 | cl_mem d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 253 | sizeof(uint32_t) * tw, sent1_words, &err); | |
| 254 | cl_mem d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 255 | sizeof(uint32_t), &sent1_offset, &err); | |
| 256 | cl_mem d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 257 | sizeof(uint32_t), &sent1_length, &err); | |
| 258 | ||
| 259 | /* Set all 16 kernel args for count_sentence_pairs */ | |
| 260 | clSetKernelArg(k_count, 0, sizeof(cl_mem), &d_flat_words); | |
| 261 | clSetKernelArg(k_count, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 262 | clSetKernelArg(k_count, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 263 | clSetKernelArg(k_count, 3, sizeof(cl_uint), &num_sentences); | |
| 264 | clSetKernelArg(k_count, 4, sizeof(cl_uint), &tw); | |
| 265 | clSetKernelArg(k_count, 5, sizeof(cl_uint), &window_size); | |
| 266 | clSetKernelArg(k_count, 6, sizeof(cl_mem), &pht_keys); | |
| 267 | clSetKernelArg(k_count, 7, sizeof(cl_mem), &pht_values); | |
| 268 | clSetKernelArg(k_count, 8, sizeof(cl_mem), &pair_word_a); | |
| 269 | clSetKernelArg(k_count, 9, sizeof(cl_mem), &pair_word_b); | |
| 270 | clSetKernelArg(k_count, 10, sizeof(cl_mem), &pair_count); | |
| 271 | clSetKernelArg(k_count, 11, sizeof(cl_mem), &pair_mi); | |
| 272 | clSetKernelArg(k_count, 12, sizeof(cl_mem), &pair_flags); | |
| 273 | clSetKernelArg(k_count, 13, sizeof(cl_mem), &pair_next_free); | |
| 274 | clSetKernelArg(k_count, 14, sizeof(cl_mem), &word_count); | |
| 275 | clSetKernelArg(k_count, 15, sizeof(cl_mem), &total_pair_count); | |
| 276 | ||
| 277 | double t0 = now_ms(); | |
| 278 | size_t gs = ((tw + local_size - 1) / local_size) * local_size; | |
| 279 | err = clEnqueueNDRangeKernel(queue, k_count, 1, NULL, | |
| 280 | &gs, &local_size, 0, NULL, NULL); | |
| 281 | CL_CHECK(err, "enqueue count"); | |
| 282 | clFinish(queue); | |
| 283 | double t1 = now_ms(); | |
| 284 | ||
| 285 | /* Read back results */ | |
| 286 | uint32_t h_num_pairs; | |
| 287 | clEnqueueReadBuffer(queue, pair_next_free, CL_TRUE, 0, | |
| 288 | sizeof(uint32_t), &h_num_pairs, 0, NULL, NULL); | |
| 289 | ||
| 290 | uint32_t h_total; | |
| 291 | clEnqueueReadBuffer(queue, total_pair_count, CL_TRUE, 0, | |
| 292 | sizeof(uint32_t), &h_total, 0, NULL, NULL); | |
| 293 | ||
| 294 | /* Read word marginals */ | |
| 295 | double wc[4]; | |
| 296 | clEnqueueReadBuffer(queue, word_count, CL_TRUE, 0, | |
| 297 | sizeof(double) * 4, wc, 0, NULL, NULL); | |
| 298 | ||
| 299 | printf(" Pairs created: %u (expected 5)\n", h_num_pairs); | |
| 300 | printf(" Total count events: %u (expected 5)\n", h_total); | |
| 301 | printf(" Word marginals: [0]=%.0f [1]=%.0f [2]=%.0f [3]=%.0f\n", | |
| 302 | wc[0], wc[1], wc[2], wc[3]); | |
| 303 | printf(" Expected: [0]=2 [1]=3 [2]=3 [3]=2\n"); | |
| 304 | printf(" Time: %.2f ms\n", t1 - t0); | |
| 305 | ||
| 306 | int t1_pass = (h_num_pairs == 5) && (h_total == 5) && | |
| 307 | (fabs(wc[0] - 2.0) < 0.5) && (fabs(wc[1] - 3.0) < 0.5) && | |
| 308 | (fabs(wc[2] - 3.0) < 0.5) && (fabs(wc[3] - 2.0) < 0.5); | |
| 309 | printf(" %s\n\n", t1_pass ? "PASS" : "FAIL"); | |
| 310 | if (t1_pass) pass_count++; else fail_count++; | |
| 311 | ||
| 312 | /* ═══════════════════════════════════════════════════════════════ | |
| 313 | * TEST 2: Multi-sentence batch | |
| 314 | * | |
| 315 | * Sentence 1: [0, 1, 2, 3] — 4 words (same as test 1) | |
| 316 | * Sentence 2: [4, 5, 6] — 3 words (disjoint vocabulary) | |
| 317 | * | |
| 318 | * flat_words = [0, 1, 2, 3, 4, 5, 6] | |
| 319 | * sent_offsets = [0, 4] | |
| 320 | * sent_lengths = [4, 3] | |
| 321 | * | |
| 322 | * Window = 2: | |
| 323 | * Sentence 1: (0,1) (0,2) (1,2) (1,3) (2,3) = 5 pairs | |
| 324 | * Sentence 2: (4,5) (4,6) (5,6) = 3 pairs | |
| 325 | * Total: 8 unique pairs, 8 count events | |
| 326 | * | |
| 327 | * KEY: no cross-boundary pairs (e.g., no (3,4) pair) | |
| 328 | * ═══════════════════════════════════════════════════════════════ */ | |
| 329 | ||
| 330 | printf("--- Test 2: Multi-sentence batch (window=2) ---\n"); | |
| 331 | ||
| 332 | reset_pools(queue, pht_keys, pht_values, pair_count, pair_mi, | |
| 333 | pair_flags, word_count, pair_next_free, total_pair_count); | |
| 334 | ||
| 335 | uint32_t multi_words[] = {0, 1, 2, 3, 4, 5, 6}; | |
| 336 | uint32_t multi_offsets[] = {0, 4}; | |
| 337 | uint32_t multi_lengths[] = {4, 3}; | |
| 338 | cl_uint multi_ns = 2; | |
| 339 | cl_uint multi_tw = 7; | |
| 340 | ||
| 341 | clReleaseMemObject(d_flat_words); | |
| 342 | clReleaseMemObject(d_sent_offsets); | |
| 343 | clReleaseMemObject(d_sent_lengths); | |
| 344 | ||
| 345 | d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 346 | sizeof(uint32_t) * multi_tw, multi_words, &err); | |
| 347 | d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 348 | sizeof(uint32_t) * multi_ns, multi_offsets, &err); | |
| 349 | d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 350 | sizeof(uint32_t) * multi_ns, multi_lengths, &err); | |
| 351 | ||
| 352 | /* Update sentence-specific args (6-15 stay same) */ | |
| 353 | clSetKernelArg(k_count, 0, sizeof(cl_mem), &d_flat_words); | |
| 354 | clSetKernelArg(k_count, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 355 | clSetKernelArg(k_count, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 356 | clSetKernelArg(k_count, 3, sizeof(cl_uint), &multi_ns); | |
| 357 | clSetKernelArg(k_count, 4, sizeof(cl_uint), &multi_tw); | |
| 358 | clSetKernelArg(k_count, 5, sizeof(cl_uint), &window_size); | |
| 359 | ||
| 360 | t0 = now_ms(); | |
| 361 | gs = ((multi_tw + local_size - 1) / local_size) * local_size; | |
| 362 | err = clEnqueueNDRangeKernel(queue, k_count, 1, NULL, | |
| 363 | &gs, &local_size, 0, NULL, NULL); | |
| 364 | CL_CHECK(err, "enqueue count multi"); | |
| 365 | clFinish(queue); | |
| 366 | t1 = now_ms(); | |
| 367 | ||
| 368 | clEnqueueReadBuffer(queue, pair_next_free, CL_TRUE, 0, | |
| 369 | sizeof(uint32_t), &h_num_pairs, 0, NULL, NULL); | |
| 370 | clEnqueueReadBuffer(queue, total_pair_count, CL_TRUE, 0, | |
| 371 | sizeof(uint32_t), &h_total, 0, NULL, NULL); | |
| 372 | ||
| 373 | printf(" Pairs created: %u (expected 8)\n", h_num_pairs); | |
| 374 | printf(" Total count events: %u (expected 8)\n", h_total); | |
| 375 | printf(" Time: %.2f ms\n", t1 - t0); | |
| 376 | ||
| 377 | /* Verify no cross-boundary pairs exist. | |
| 378 | * Words 0-3 belong to sentence 1, words 4-6 to sentence 2. | |
| 379 | * No pair should have one word from each sentence. */ | |
| 380 | uint32_t* h_pa = malloc(sizeof(uint32_t) * h_num_pairs); | |
| 381 | uint32_t* h_pb = malloc(sizeof(uint32_t) * h_num_pairs); | |
| 382 | clEnqueueReadBuffer(queue, pair_word_a, CL_TRUE, 0, | |
| 383 | sizeof(uint32_t) * h_num_pairs, h_pa, 0, NULL, NULL); | |
| 384 | clEnqueueReadBuffer(queue, pair_word_b, CL_TRUE, 0, | |
| 385 | sizeof(uint32_t) * h_num_pairs, h_pb, 0, NULL, NULL); | |
| 386 | ||
| 387 | int cross_boundary = 0; | |
| 388 | for (uint32_t i = 0; i < h_num_pairs; i++) { | |
| 389 | int a_sent = (h_pa[i] <= 3) ? 1 : 2; | |
| 390 | int b_sent = (h_pb[i] <= 3) ? 1 : 2; | |
| 391 | if (a_sent != b_sent) { | |
| 392 | cross_boundary++; | |
| 393 | printf(" CROSS-BOUNDARY: pair(%u, %u)\n", h_pa[i], h_pb[i]); | |
| 394 | } | |
| 395 | } | |
| 396 | printf(" Cross-boundary pairs: %d (expected 0)\n", cross_boundary); | |
| 397 | ||
| 398 | int t2_pass = (h_num_pairs == 8) && (h_total == 8) && (cross_boundary == 0); | |
| 399 | printf(" %s\n\n", t2_pass ? "PASS" : "FAIL"); | |
| 400 | if (t2_pass) pass_count++; else fail_count++; | |
| 401 | ||
| 402 | free(h_pa); | |
| 403 | free(h_pb); | |
| 404 | ||
| 405 | /* ═══════════════════════════════════════════════════════════════ | |
| 406 | * TEST 3: Read pairs (readback kernel) | |
| 407 | * | |
| 408 | * Uses the read_pairs kernel to copy pair data from the pool | |
| 409 | * into output arrays. Verifies all pairs from Test 2 have | |
| 410 | * count=1 and dirty flag=1. | |
| 411 | * ═══════════════════════════════════════════════════════════════ */ | |
| 412 | ||
| 413 | printf("--- Test 3: Read pairs (readback verification) ---\n"); | |
| 414 | ||
| 415 | cl_mem d_out_wa = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 416 | sizeof(uint32_t) * h_num_pairs, NULL, &err); | |
| 417 | cl_mem d_out_wb = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 418 | sizeof(uint32_t) * h_num_pairs, NULL, &err); | |
| 419 | cl_mem d_out_cnt = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 420 | sizeof(double) * h_num_pairs, NULL, &err); | |
| 421 | cl_mem d_out_mi = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 422 | sizeof(double) * h_num_pairs, NULL, &err); | |
| 423 | cl_mem d_out_flags = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 424 | sizeof(uint32_t) * h_num_pairs, NULL, &err); | |
| 425 | ||
| 426 | cl_uint np = h_num_pairs; | |
| 427 | clSetKernelArg(k_read, 0, sizeof(cl_mem), &pair_word_a); | |
| 428 | clSetKernelArg(k_read, 1, sizeof(cl_mem), &pair_word_b); | |
| 429 | clSetKernelArg(k_read, 2, sizeof(cl_mem), &pair_count); | |
| 430 | clSetKernelArg(k_read, 3, sizeof(cl_mem), &pair_mi); | |
| 431 | clSetKernelArg(k_read, 4, sizeof(cl_mem), &pair_flags); | |
| 432 | clSetKernelArg(k_read, 5, sizeof(cl_mem), &d_out_wa); | |
| 433 | clSetKernelArg(k_read, 6, sizeof(cl_mem), &d_out_wb); | |
| 434 | clSetKernelArg(k_read, 7, sizeof(cl_mem), &d_out_cnt); | |
| 435 | clSetKernelArg(k_read, 8, sizeof(cl_mem), &d_out_mi); | |
| 436 | clSetKernelArg(k_read, 9, sizeof(cl_mem), &d_out_flags); | |
| 437 | clSetKernelArg(k_read, 10, sizeof(cl_uint), &np); | |
| 438 | ||
| 439 | gs = ((h_num_pairs + local_size - 1) / local_size) * local_size; | |
| 440 | err = clEnqueueNDRangeKernel(queue, k_read, 1, NULL, | |
| 441 | &gs, &local_size, 0, NULL, NULL); | |
| 442 | CL_CHECK(err, "enqueue read_pairs"); | |
| 443 | clFinish(queue); | |
| 444 | ||
| 445 | uint32_t* r_wa = malloc(sizeof(uint32_t) * h_num_pairs); | |
| 446 | uint32_t* r_wb = malloc(sizeof(uint32_t) * h_num_pairs); | |
| 447 | double* r_cnt = malloc(sizeof(double) * h_num_pairs); | |
| 448 | uint32_t* r_flags = malloc(sizeof(uint32_t) * h_num_pairs); | |
| 449 | ||
| 450 | clEnqueueReadBuffer(queue, d_out_wa, CL_TRUE, 0, | |
| 451 | sizeof(uint32_t) * h_num_pairs, r_wa, 0, NULL, NULL); | |
| 452 | clEnqueueReadBuffer(queue, d_out_wb, CL_TRUE, 0, | |
| 453 | sizeof(uint32_t) * h_num_pairs, r_wb, 0, NULL, NULL); | |
| 454 | clEnqueueReadBuffer(queue, d_out_cnt, CL_TRUE, 0, | |
| 455 | sizeof(double) * h_num_pairs, r_cnt, 0, NULL, NULL); | |
| 456 | clEnqueueReadBuffer(queue, d_out_flags, CL_TRUE, 0, | |
| 457 | sizeof(uint32_t) * h_num_pairs, r_flags, 0, NULL, NULL); | |
| 458 | ||
| 459 | int all_counted = 1; | |
| 460 | int all_dirty = 1; | |
| 461 | int all_canonical = 1; | |
| 462 | double sum_counts = 0; | |
| 463 | ||
| 464 | printf(" Pairs:\n"); | |
| 465 | for (uint32_t i = 0; i < h_num_pairs; i++) { | |
| 466 | printf(" [%u] (%u, %u) count=%.0f flags=%u\n", | |
| 467 | i, r_wa[i], r_wb[i], r_cnt[i], r_flags[i]); | |
| 468 | if (r_cnt[i] < 0.5) all_counted = 0; | |
| 469 | if (r_flags[i] != 1) all_dirty = 0; | |
| 470 | if (r_wa[i] > r_wb[i]) all_canonical = 0; | |
| 471 | sum_counts += r_cnt[i]; | |
| 472 | } | |
| 473 | ||
| 474 | printf(" Sum of counts: %.0f (expected %u)\n", sum_counts, h_total); | |
| 475 | printf(" All counts > 0: %s\n", all_counted ? "YES" : "NO"); | |
| 476 | printf(" All dirty flags: %s\n", all_dirty ? "YES" : "NO"); | |
| 477 | printf(" All canonical (a <= b): %s\n", all_canonical ? "YES" : "NO"); | |
| 478 | ||
| 479 | int t3_pass = all_counted && all_dirty && all_canonical && | |
| 480 | (fabs(sum_counts - h_total) < 0.5); | |
| 481 | printf(" %s\n\n", t3_pass ? "PASS" : "FAIL"); | |
| 482 | if (t3_pass) pass_count++; else fail_count++; | |
| 483 | ||
| 484 | free(r_wa); free(r_wb); free(r_cnt); free(r_flags); | |
| 485 | clReleaseMemObject(d_out_wa); | |
| 486 | clReleaseMemObject(d_out_wb); | |
| 487 | clReleaseMemObject(d_out_cnt); | |
| 488 | clReleaseMemObject(d_out_mi); | |
| 489 | clReleaseMemObject(d_out_flags); | |
| 490 | ||
| 491 | /* ═══════════════════════════════════════════════════════════════ | |
| 492 | * TEST 4: Binary search variant | |
| 493 | * | |
| 494 | * Same input as Test 2, using count_sentence_pairs_large. | |
| 495 | * Should produce identical results. | |
| 496 | * ═══════════════════════════════════════════════════════════════ */ | |
| 497 | ||
| 498 | printf("--- Test 4: Binary search variant ---\n"); | |
| 499 | ||
| 500 | reset_pools(queue, pht_keys, pht_values, pair_count, pair_mi, | |
| 501 | pair_flags, word_count, pair_next_free, total_pair_count); | |
| 502 | ||
| 503 | /* Set all 16 kernel args for count_sentence_pairs_large */ | |
| 504 | clSetKernelArg(k_count_large, 0, sizeof(cl_mem), &d_flat_words); | |
| 505 | clSetKernelArg(k_count_large, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 506 | clSetKernelArg(k_count_large, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 507 | clSetKernelArg(k_count_large, 3, sizeof(cl_uint), &multi_ns); | |
| 508 | clSetKernelArg(k_count_large, 4, sizeof(cl_uint), &multi_tw); | |
| 509 | clSetKernelArg(k_count_large, 5, sizeof(cl_uint), &window_size); | |
| 510 | clSetKernelArg(k_count_large, 6, sizeof(cl_mem), &pht_keys); | |
| 511 | clSetKernelArg(k_count_large, 7, sizeof(cl_mem), &pht_values); | |
| 512 | clSetKernelArg(k_count_large, 8, sizeof(cl_mem), &pair_word_a); | |
| 513 | clSetKernelArg(k_count_large, 9, sizeof(cl_mem), &pair_word_b); | |
| 514 | clSetKernelArg(k_count_large, 10, sizeof(cl_mem), &pair_count); | |
| 515 | clSetKernelArg(k_count_large, 11, sizeof(cl_mem), &pair_mi); | |
| 516 | clSetKernelArg(k_count_large, 12, sizeof(cl_mem), &pair_flags); | |
| 517 | clSetKernelArg(k_count_large, 13, sizeof(cl_mem), &pair_next_free); | |
| 518 | clSetKernelArg(k_count_large, 14, sizeof(cl_mem), &word_count); | |
| 519 | clSetKernelArg(k_count_large, 15, sizeof(cl_mem), &total_pair_count); | |
| 520 | ||
| 521 | t0 = now_ms(); | |
| 522 | gs = ((multi_tw + local_size - 1) / local_size) * local_size; | |
| 523 | err = clEnqueueNDRangeKernel(queue, k_count_large, 1, NULL, | |
| 524 | &gs, &local_size, 0, NULL, NULL); | |
| 525 | CL_CHECK(err, "enqueue count_large"); | |
| 526 | clFinish(queue); | |
| 527 | t1 = now_ms(); | |
| 528 | ||
| 529 | uint32_t h_pairs_large, h_total_large; | |
| 530 | clEnqueueReadBuffer(queue, pair_next_free, CL_TRUE, 0, | |
| 531 | sizeof(uint32_t), &h_pairs_large, 0, NULL, NULL); | |
| 532 | clEnqueueReadBuffer(queue, total_pair_count, CL_TRUE, 0, | |
| 533 | sizeof(uint32_t), &h_total_large, 0, NULL, NULL); | |
| 534 | ||
| 535 | printf(" Pairs created: %u (expected 8)\n", h_pairs_large); | |
| 536 | printf(" Total count events: %u (expected 8)\n", h_total_large); | |
| 537 | printf(" Time: %.2f ms\n", t1 - t0); | |
| 538 | ||
| 539 | /* Verify word marginals match Test 2's expected values */ | |
| 540 | double wc4[7]; | |
| 541 | clEnqueueReadBuffer(queue, word_count, CL_TRUE, 0, | |
| 542 | sizeof(double) * 7, wc4, 0, NULL, NULL); | |
| 543 | printf(" Word marginals: [0]=%.0f [1]=%.0f [2]=%.0f [3]=%.0f [4]=%.0f [5]=%.0f [6]=%.0f\n", | |
| 544 | wc4[0], wc4[1], wc4[2], wc4[3], wc4[4], wc4[5], wc4[6]); | |
| 545 | printf(" Expected: [0]=2 [1]=3 [2]=3 [3]=2 [4]=2 [5]=2 [6]=2\n"); | |
| 546 | ||
| 547 | int t4_pass = (h_pairs_large == 8) && (h_total_large == 8) && | |
| 548 | (fabs(wc4[0] - 2.0) < 0.5) && (fabs(wc4[1] - 3.0) < 0.5) && | |
| 549 | (fabs(wc4[2] - 3.0) < 0.5) && (fabs(wc4[3] - 2.0) < 0.5) && | |
| 550 | (fabs(wc4[4] - 2.0) < 0.5) && (fabs(wc4[5] - 2.0) < 0.5) && | |
| 551 | (fabs(wc4[6] - 2.0) < 0.5); | |
| 552 | printf(" %s\n\n", t4_pass ? "PASS" : "FAIL"); | |
| 553 | if (t4_pass) pass_count++; else fail_count++; | |
| 554 | ||
| 555 | /* ═══════════════════════════════════════════════════════════════ | |
| 556 | * TEST 5: Benchmark — 1000 sentences, window=6 | |
| 557 | * | |
| 558 | * 500-word vocabulary, Zipf-like distribution, 5-20 words per | |
| 559 | * sentence. Compares linear scan vs binary search variant. | |
| 560 | * ═══════════════════════════════════════════════════════════════ */ | |
| 561 | ||
| 562 | printf("--- Test 5: Benchmark (1000 sentences, window=6) ---\n"); | |
| 563 | ||
| 564 | reset_pools(queue, pht_keys, pht_values, pair_count, pair_mi, | |
| 565 | pair_flags, word_count, pair_next_free, total_pair_count); | |
| 566 | ||
| 567 | /* Generate 1000 sentences with Zipf-like word distribution */ | |
| 568 | int bench_ns = 1000; | |
| 569 | uint32_t* bench_offsets = malloc(sizeof(uint32_t) * bench_ns); | |
| 570 | uint32_t* bench_lengths = malloc(sizeof(uint32_t) * bench_ns); | |
| 571 | ||
| 572 | uint64_t rng = 0xCAFEBABEDEADBEEFULL; | |
| 573 | uint32_t bench_tw = 0; | |
| 574 | for (int s = 0; s < bench_ns; s++) { | |
| 575 | bench_offsets[s] = bench_tw; | |
| 576 | rng += 0x9E3779B97F4A7C15ULL; | |
| 577 | uint32_t slen = 5 + (uint32_t)((rng >> 32) % 16); | |
| 578 | bench_lengths[s] = slen; | |
| 579 | bench_tw += slen; | |
| 580 | } | |
| 581 | ||
| 582 | uint32_t* bench_words = malloc(sizeof(uint32_t) * bench_tw); | |
| 583 | rng = 0xFEEDFACE12345678ULL; | |
| 584 | for (uint32_t i = 0; i < bench_tw; i++) { | |
| 585 | rng += 0x9E3779B97F4A7C15ULL; | |
| 586 | double u = (double)(rng >> 32) / (double)0xFFFFFFFFU; | |
| 587 | bench_words[i] = (uint32_t)(u * u * 499.0); | |
| 588 | } | |
| 589 | ||
| 590 | printf(" Sentences: %d, Total words: %u, Avg len: %.1f\n", | |
| 591 | bench_ns, bench_tw, (float)bench_tw / bench_ns); | |
| 592 | ||
| 593 | clReleaseMemObject(d_flat_words); | |
| 594 | clReleaseMemObject(d_sent_offsets); | |
| 595 | clReleaseMemObject(d_sent_lengths); | |
| 596 | ||
| 597 | d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 598 | sizeof(uint32_t) * bench_tw, bench_words, &err); | |
| 599 | d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 600 | sizeof(uint32_t) * bench_ns, bench_offsets, &err); | |
| 601 | d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 602 | sizeof(uint32_t) * bench_ns, bench_lengths, &err); | |
| 603 | ||
| 604 | cl_uint bench_window = 6; | |
| 605 | cl_uint bns = bench_ns; | |
| 606 | ||
| 607 | /* Run linear scan variant */ | |
| 608 | clSetKernelArg(k_count, 0, sizeof(cl_mem), &d_flat_words); | |
| 609 | clSetKernelArg(k_count, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 610 | clSetKernelArg(k_count, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 611 | clSetKernelArg(k_count, 3, sizeof(cl_uint), &bns); | |
| 612 | clSetKernelArg(k_count, 4, sizeof(cl_uint), &bench_tw); | |
| 613 | clSetKernelArg(k_count, 5, sizeof(cl_uint), &bench_window); | |
| 614 | ||
| 615 | t0 = now_ms(); | |
| 616 | gs = ((bench_tw + local_size - 1) / local_size) * local_size; | |
| 617 | err = clEnqueueNDRangeKernel(queue, k_count, 1, NULL, | |
| 618 | &gs, &local_size, 0, NULL, NULL); | |
| 619 | CL_CHECK(err, "enqueue bench count"); | |
| 620 | clFinish(queue); | |
| 621 | t1 = now_ms(); | |
| 622 | ||
| 623 | uint32_t bench_pairs, bench_total; | |
| 624 | clEnqueueReadBuffer(queue, pair_next_free, CL_TRUE, 0, | |
| 625 | sizeof(uint32_t), &bench_pairs, 0, NULL, NULL); | |
| 626 | clEnqueueReadBuffer(queue, total_pair_count, CL_TRUE, 0, | |
| 627 | sizeof(uint32_t), &bench_total, 0, NULL, NULL); | |
| 628 | ||
| 629 | double linear_ms = t1 - t0; | |
| 630 | printf(" Linear scan: %.2f ms\n", linear_ms); | |
| 631 | printf(" Unique pairs: %u\n", bench_pairs); | |
| 632 | printf(" Total count events: %u\n", bench_total); | |
| 633 | printf(" Throughput: %.0f K sentences/sec, %.0f K pair-events/sec\n", | |
| 634 | bench_ns / (linear_ms / 1000.0) / 1000.0, | |
| 635 | bench_total / (linear_ms / 1000.0) / 1000.0); | |
| 636 | ||
| 637 | /* Run binary search variant with same data */ | |
| 638 | reset_pools(queue, pht_keys, pht_values, pair_count, pair_mi, | |
| 639 | pair_flags, word_count, pair_next_free, total_pair_count); | |
| 640 | ||
| 641 | clSetKernelArg(k_count_large, 0, sizeof(cl_mem), &d_flat_words); | |
| 642 | clSetKernelArg(k_count_large, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 643 | clSetKernelArg(k_count_large, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 644 | clSetKernelArg(k_count_large, 3, sizeof(cl_uint), &bns); | |
| 645 | clSetKernelArg(k_count_large, 4, sizeof(cl_uint), &bench_tw); | |
| 646 | clSetKernelArg(k_count_large, 5, sizeof(cl_uint), &bench_window); | |
| 647 | clSetKernelArg(k_count_large, 6, sizeof(cl_mem), &pht_keys); | |
| 648 | clSetKernelArg(k_count_large, 7, sizeof(cl_mem), &pht_values); | |
| 649 | clSetKernelArg(k_count_large, 8, sizeof(cl_mem), &pair_word_a); | |
| 650 | clSetKernelArg(k_count_large, 9, sizeof(cl_mem), &pair_word_b); | |
| 651 | clSetKernelArg(k_count_large, 10, sizeof(cl_mem), &pair_count); | |
| 652 | clSetKernelArg(k_count_large, 11, sizeof(cl_mem), &pair_mi); | |
| 653 | clSetKernelArg(k_count_large, 12, sizeof(cl_mem), &pair_flags); | |
| 654 | clSetKernelArg(k_count_large, 13, sizeof(cl_mem), &pair_next_free); | |
| 655 | clSetKernelArg(k_count_large, 14, sizeof(cl_mem), &word_count); | |
| 656 | clSetKernelArg(k_count_large, 15, sizeof(cl_mem), &total_pair_count); | |
| 657 | ||
| 658 | t0 = now_ms(); | |
| 659 | err = clEnqueueNDRangeKernel(queue, k_count_large, 1, NULL, | |
| 660 | &gs, &local_size, 0, NULL, NULL); | |
| 661 | CL_CHECK(err, "enqueue bench count_large"); | |
| 662 | clFinish(queue); | |
| 663 | t1 = now_ms(); | |
| 664 | ||
| 665 | uint32_t bench_pairs_l, bench_total_l; | |
| 666 | clEnqueueReadBuffer(queue, pair_next_free, CL_TRUE, 0, | |
| 667 | sizeof(uint32_t), &bench_pairs_l, 0, NULL, NULL); | |
| 668 | clEnqueueReadBuffer(queue, total_pair_count, CL_TRUE, 0, | |
| 669 | sizeof(uint32_t), &bench_total_l, 0, NULL, NULL); | |
| 670 | ||
| 671 | double binary_ms = t1 - t0; | |
| 672 | printf(" Binary search: %.2f ms\n", binary_ms); | |
| 673 | printf(" Unique pairs: %u (expected %u)\n", bench_pairs_l, bench_pairs); | |
| 674 | printf(" Total count events: %u (expected %u)\n", bench_total_l, bench_total); | |
| 675 | printf(" Throughput: %.0f K sentences/sec, %.0f K pair-events/sec\n", | |
| 676 | bench_ns / (binary_ms / 1000.0) / 1000.0, | |
| 677 | bench_total_l / (binary_ms / 1000.0) / 1000.0); | |
| 678 | ||
| 679 | int t5_pass = (bench_pairs_l == bench_pairs) && (bench_total_l == bench_total); | |
| 680 | printf(" Linear vs Binary match: %s\n", t5_pass ? "PASS" : "FAIL"); | |
| 681 | printf(" %s\n\n", t5_pass ? "PASS" : "FAIL"); | |
| 682 | if (t5_pass) pass_count++; else fail_count++; | |
| 683 | ||
| 684 | /* ═══ Summary ═══ */ | |
| 685 | ||
| 686 | printf("=== Results: %d PASS, %d FAIL ===\n", pass_count, fail_count); | |
| 687 | ||
| 688 | /* ─── Cleanup ─── */ | |
| 689 | ||
| 690 | clReleaseMemObject(pht_keys); | |
| 691 | clReleaseMemObject(pht_values); | |
| 692 | clReleaseMemObject(pair_word_a); | |
| 693 | clReleaseMemObject(pair_word_b); | |
| 694 | clReleaseMemObject(pair_count); | |
| 695 | clReleaseMemObject(pair_mi); | |
| 696 | clReleaseMemObject(pair_flags); | |
| 697 | clReleaseMemObject(pair_next_free); | |
| 698 | clReleaseMemObject(word_count); | |
| 699 | clReleaseMemObject(total_pair_count); | |
| 700 | clReleaseMemObject(d_flat_words); | |
| 701 | clReleaseMemObject(d_sent_offsets); | |
| 702 | clReleaseMemObject(d_sent_lengths); | |
| 703 | ||
| 704 | clReleaseKernel(k_count); | |
| 705 | clReleaseKernel(k_count_large); | |
| 706 | clReleaseKernel(k_read); | |
| 707 | clReleaseProgram(program); | |
| 708 | clReleaseCommandQueue(queue); | |
| 709 | clReleaseContext(ctx); | |
| 710 | ||
| 711 | free(bench_offsets); | |
| 712 | free(bench_lengths); | |
| 713 | free(bench_words); | |
| 714 | free(combined); | |
| 715 | free(ht_src); | |
| 716 | free(as_src); | |
| 717 | free(ct_src); | |
| 718 | ||
| 719 | return fail_count > 0 ? 1 : 0; | |
| 720 | } |
| … | … | @@ -0,0 +1,532 @@ |
| 1 | /* | |
| 2 | * test-hashtable.c -- Standalone test for GPU hash table | |
| 3 | * | |
| 4 | * Compile: gcc -O2 -o test-hashtable test-hashtable.c -lOpenCL | |
| 5 | * Run: ./test-hashtable | |
| 6 | * | |
| 7 | * Tests: | |
| 8 | * 1. Bulk insert 1M keys | |
| 9 | * 2. Lookup all inserted keys (verify 100% hit) | |
| 10 | * 3. Lookup non-existent keys (verify 100% miss) | |
| 11 | * 4. Delete some keys, verify they're gone | |
| 12 | * 5. Insert-or-increment (counting) | |
| 13 | * 6. Iterate and verify count | |
| 14 | * 7. Performance: inserts/sec and lookups/sec | |
| 15 | */ | |
| 16 | ||
| 17 | #include <stdio.h> | |
| 18 | #include <stdlib.h> | |
| 19 | #include <stdint.h> | |
| 20 | #include <string.h> | |
| 21 | #include <time.h> | |
| 22 | #include <CL/cl.h> | |
| 23 | ||
| 24 | /* ─── Configuration ─── */ | |
| 25 | ||
| 26 | /* Table capacity — must be power of 2. | |
| 27 | * At 50% load factor, 4M slots supports 2M entries. */ | |
| 28 | #define TABLE_CAPACITY (4 * 1024 * 1024) /* 4M slots */ | |
| 29 | #define NUM_TEST_ITEMS (1 * 1024 * 1024) /* 1M test entries */ | |
| 30 | ||
| 31 | #define HT_EMPTY_KEY 0xFFFFFFFFFFFFFFFFULL | |
| 32 | #define HT_EMPTY_VALUE 0xFFFFFFFFU | |
| 33 | ||
| 34 | /* ─── Error checking ─── */ | |
| 35 | ||
| 36 | #define CL_CHECK(err, msg) do { \ | |
| 37 | if ((err) != CL_SUCCESS) { \ | |
| 38 | fprintf(stderr, "OpenCL error %d at %s:%d: %s\n", \ | |
| 39 | (err), __FILE__, __LINE__, (msg)); \ | |
| 40 | exit(1); \ | |
| 41 | } \ | |
| 42 | } while(0) | |
| 43 | ||
| 44 | /* ─── Read kernel source ─── */ | |
| 45 | ||
| 46 | char* read_file(const char* path, size_t* len) | |
| 47 | { | |
| 48 | FILE* f = fopen(path, "r"); | |
| 49 | if (!f) { fprintf(stderr, "Cannot open %s\n", path); exit(1); } | |
| 50 | fseek(f, 0, SEEK_END); | |
| 51 | *len = ftell(f); | |
| 52 | fseek(f, 0, SEEK_SET); | |
| 53 | char* buf = malloc(*len + 1); | |
| 54 | fread(buf, 1, *len, f); | |
| 55 | buf[*len] = '\0'; | |
| 56 | fclose(f); | |
| 57 | return buf; | |
| 58 | } | |
| 59 | ||
| 60 | /* ─── Simple PRNG (splitmix64) ─── */ | |
| 61 | ||
| 62 | static uint64_t rng_state = 0x12345678DEADBEEFULL; | |
| 63 | ||
| 64 | uint64_t next_random(void) | |
| 65 | { | |
| 66 | rng_state += 0x9E3779B97F4A7C15ULL; | |
| 67 | uint64_t z = rng_state; | |
| 68 | z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; | |
| 69 | z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; | |
| 70 | return z ^ (z >> 31); | |
| 71 | } | |
| 72 | ||
| 73 | /* ─── Timing ─── */ | |
| 74 | ||
| 75 | double now_ms(void) | |
| 76 | { | |
| 77 | struct timespec ts; | |
| 78 | clock_gettime(CLOCK_MONOTONIC, &ts); | |
| 79 | return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0; | |
| 80 | } | |
| 81 | ||
| 82 | /* ─── Main ─── */ | |
| 83 | ||
| 84 | int main(int argc, char** argv) | |
| 85 | { | |
| 86 | cl_int err; | |
| 87 | ||
| 88 | printf("=== GPU Hash Table Test ===\n"); | |
| 89 | printf("Table capacity: %d slots (%lu MB)\n", | |
| 90 | TABLE_CAPACITY, | |
| 91 | (unsigned long)(TABLE_CAPACITY * (sizeof(uint64_t) + sizeof(uint32_t))) / (1024*1024)); | |
| 92 | printf("Test items: %d\n\n", NUM_TEST_ITEMS); | |
| 93 | ||
| 94 | /* ─── OpenCL setup ─── */ | |
| 95 | ||
| 96 | cl_platform_id platform; | |
| 97 | err = clGetPlatformIDs(1, &platform, NULL); | |
| 98 | CL_CHECK(err, "clGetPlatformIDs"); | |
| 99 | ||
| 100 | cl_device_id device; | |
| 101 | err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL); | |
| 102 | CL_CHECK(err, "clGetDeviceIDs"); | |
| 103 | ||
| 104 | /* Print device name */ | |
| 105 | char dev_name[256]; | |
| 106 | clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(dev_name), dev_name, NULL); | |
| 107 | printf("GPU: %s\n", dev_name); | |
| 108 | ||
| 109 | /* Check for int64 atomics */ | |
| 110 | char extensions[4096]; | |
| 111 | clGetDeviceInfo(device, CL_DEVICE_EXTENSIONS, sizeof(extensions), extensions, NULL); | |
| 112 | if (!strstr(extensions, "cl_khr_int64_base_atomics")) { | |
| 113 | fprintf(stderr, "ERROR: Device does not support cl_khr_int64_base_atomics\n"); | |
| 114 | return 1; | |
| 115 | } | |
| 116 | printf("cl_khr_int64_base_atomics: supported\n\n"); | |
| 117 | ||
| 118 | cl_context ctx = clCreateContext(NULL, 1, &device, NULL, NULL, &err); | |
| 119 | CL_CHECK(err, "clCreateContext"); | |
| 120 | ||
| 121 | cl_command_queue queue = clCreateCommandQueue(ctx, device, 0, &err); | |
| 122 | CL_CHECK(err, "clCreateCommandQueue"); | |
| 123 | ||
| 124 | /* ─── Build kernel ─── */ | |
| 125 | ||
| 126 | /* Find kernel source relative to this test file or in same dir */ | |
| 127 | const char* kernel_paths[] = { | |
| 128 | "gpu-hashtable.cl", | |
| 129 | "opencog/opencl/atomspace/gpu-hashtable.cl", | |
| 130 | NULL | |
| 131 | }; | |
| 132 | char* src = NULL; | |
| 133 | size_t src_len = 0; | |
| 134 | for (int i = 0; kernel_paths[i]; i++) { | |
| 135 | FILE* f = fopen(kernel_paths[i], "r"); | |
| 136 | if (f) { | |
| 137 | fclose(f); | |
| 138 | src = read_file(kernel_paths[i], &src_len); | |
| 139 | printf("Kernel source: %s\n", kernel_paths[i]); | |
| 140 | break; | |
| 141 | } | |
| 142 | } | |
| 143 | if (!src) { | |
| 144 | fprintf(stderr, "Cannot find gpu-hashtable.cl\n"); | |
| 145 | return 1; | |
| 146 | } | |
| 147 | ||
| 148 | cl_program program = clCreateProgramWithSource(ctx, 1, | |
| 149 | (const char**)&src, &src_len, &err); | |
| 150 | CL_CHECK(err, "clCreateProgramWithSource"); | |
| 151 | ||
| 152 | err = clBuildProgram(program, 1, &device, "-cl-std=CL1.2", NULL, NULL); | |
| 153 | if (err != CL_SUCCESS) { | |
| 154 | char log[16384]; | |
| 155 | clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, | |
| 156 | sizeof(log), log, NULL); | |
| 157 | fprintf(stderr, "Build error:\n%s\n", log); | |
| 158 | return 1; | |
| 159 | } | |
| 160 | printf("Kernel compiled successfully\n\n"); | |
| 161 | ||
| 162 | /* Create kernels */ | |
| 163 | cl_kernel k_insert = clCreateKernel(program, "ht_insert", &err); | |
| 164 | CL_CHECK(err, "create ht_insert"); | |
| 165 | cl_kernel k_lookup = clCreateKernel(program, "ht_lookup", &err); | |
| 166 | CL_CHECK(err, "create ht_lookup"); | |
| 167 | cl_kernel k_delete = clCreateKernel(program, "ht_delete", &err); | |
| 168 | CL_CHECK(err, "create ht_delete"); | |
| 169 | cl_kernel k_inc = clCreateKernel(program, "ht_insert_or_increment", &err); | |
| 170 | CL_CHECK(err, "create ht_insert_or_increment"); | |
| 171 | cl_kernel k_iter = clCreateKernel(program, "ht_iterate", &err); | |
| 172 | CL_CHECK(err, "create ht_iterate"); | |
| 173 | ||
| 174 | /* ─── Allocate table on GPU ─── */ | |
| 175 | ||
| 176 | cl_ulong capacity = TABLE_CAPACITY; | |
| 177 | ||
| 178 | cl_mem d_keys = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 179 | sizeof(uint64_t) * TABLE_CAPACITY, NULL, &err); | |
| 180 | CL_CHECK(err, "alloc table keys"); | |
| 181 | ||
| 182 | cl_mem d_values = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 183 | sizeof(uint32_t) * TABLE_CAPACITY, NULL, &err); | |
| 184 | CL_CHECK(err, "alloc table values"); | |
| 185 | ||
| 186 | /* Initialize table to empty (all 0xFF bytes) */ | |
| 187 | uint8_t pattern_ff = 0xFF; | |
| 188 | err = clEnqueueFillBuffer(queue, d_keys, &pattern_ff, 1, | |
| 189 | 0, sizeof(uint64_t) * TABLE_CAPACITY, 0, NULL, NULL); | |
| 190 | CL_CHECK(err, "fill table keys"); | |
| 191 | err = clEnqueueFillBuffer(queue, d_values, &pattern_ff, 1, | |
| 192 | 0, sizeof(uint32_t) * TABLE_CAPACITY, 0, NULL, NULL); | |
| 193 | CL_CHECK(err, "fill table values"); | |
| 194 | clFinish(queue); | |
| 195 | ||
| 196 | /* ─── Generate test data ─── */ | |
| 197 | ||
| 198 | uint64_t* h_keys = malloc(sizeof(uint64_t) * NUM_TEST_ITEMS); | |
| 199 | uint32_t* h_values = malloc(sizeof(uint32_t) * NUM_TEST_ITEMS); | |
| 200 | uint32_t* h_results = malloc(sizeof(uint32_t) * NUM_TEST_ITEMS); | |
| 201 | ||
| 202 | rng_state = 0x12345678DEADBEEFULL; | |
| 203 | for (int i = 0; i < NUM_TEST_ITEMS; i++) { | |
| 204 | uint64_t k = next_random(); | |
| 205 | /* Avoid sentinel key */ | |
| 206 | if (k == HT_EMPTY_KEY) k = 0; | |
| 207 | h_keys[i] = k; | |
| 208 | h_values[i] = (uint32_t)(i & 0xFFFFFFFF); | |
| 209 | } | |
| 210 | ||
| 211 | /* Upload test data to GPU */ | |
| 212 | cl_mem d_in_keys = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 213 | sizeof(uint64_t) * NUM_TEST_ITEMS, h_keys, &err); | |
| 214 | CL_CHECK(err, "alloc input keys"); | |
| 215 | cl_mem d_in_values = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 216 | sizeof(uint32_t) * NUM_TEST_ITEMS, h_values, &err); | |
| 217 | CL_CHECK(err, "alloc input values"); | |
| 218 | cl_mem d_out_values = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 219 | sizeof(uint32_t) * NUM_TEST_ITEMS, NULL, &err); | |
| 220 | CL_CHECK(err, "alloc output values"); | |
| 221 | ||
| 222 | size_t global_size = NUM_TEST_ITEMS; | |
| 223 | size_t local_size = 256; | |
| 224 | /* Round up global size to multiple of local size */ | |
| 225 | global_size = ((global_size + local_size - 1) / local_size) * local_size; | |
| 226 | ||
| 227 | cl_uint num_items = NUM_TEST_ITEMS; | |
| 228 | ||
| 229 | /* ═══ TEST 1: Bulk Insert ═══ */ | |
| 230 | ||
| 231 | printf("--- Test 1: Insert %d items ---\n", NUM_TEST_ITEMS); | |
| 232 | ||
| 233 | clSetKernelArg(k_insert, 0, sizeof(cl_mem), &d_keys); | |
| 234 | clSetKernelArg(k_insert, 1, sizeof(cl_mem), &d_values); | |
| 235 | clSetKernelArg(k_insert, 2, sizeof(cl_ulong), &capacity); | |
| 236 | clSetKernelArg(k_insert, 3, sizeof(cl_mem), &d_in_keys); | |
| 237 | clSetKernelArg(k_insert, 4, sizeof(cl_mem), &d_in_values); | |
| 238 | clSetKernelArg(k_insert, 5, sizeof(cl_uint), &num_items); | |
| 239 | ||
| 240 | double t0 = now_ms(); | |
| 241 | err = clEnqueueNDRangeKernel(queue, k_insert, 1, NULL, | |
| 242 | &global_size, &local_size, 0, NULL, NULL); | |
| 243 | CL_CHECK(err, "enqueue insert"); | |
| 244 | clFinish(queue); | |
| 245 | double t1 = now_ms(); | |
| 246 | ||
| 247 | printf(" Inserted %d items in %.1f ms (%.1f M keys/sec)\n", | |
| 248 | NUM_TEST_ITEMS, t1-t0, NUM_TEST_ITEMS / ((t1-t0) / 1000.0) / 1e6); | |
| 249 | ||
| 250 | /* ═══ TEST 2: Lookup all (should all hit) ═══ */ | |
| 251 | ||
| 252 | printf("--- Test 2: Lookup %d items (expect all found) ---\n", NUM_TEST_ITEMS); | |
| 253 | ||
| 254 | clSetKernelArg(k_lookup, 0, sizeof(cl_mem), &d_keys); | |
| 255 | clSetKernelArg(k_lookup, 1, sizeof(cl_mem), &d_values); | |
| 256 | clSetKernelArg(k_lookup, 2, sizeof(cl_ulong), &capacity); | |
| 257 | clSetKernelArg(k_lookup, 3, sizeof(cl_mem), &d_in_keys); | |
| 258 | clSetKernelArg(k_lookup, 4, sizeof(cl_mem), &d_out_values); | |
| 259 | clSetKernelArg(k_lookup, 5, sizeof(cl_uint), &num_items); | |
| 260 | ||
| 261 | t0 = now_ms(); | |
| 262 | err = clEnqueueNDRangeKernel(queue, k_lookup, 1, NULL, | |
| 263 | &global_size, &local_size, 0, NULL, NULL); | |
| 264 | CL_CHECK(err, "enqueue lookup"); | |
| 265 | clFinish(queue); | |
| 266 | t1 = now_ms(); | |
| 267 | ||
| 268 | /* Read back results */ | |
| 269 | err = clEnqueueReadBuffer(queue, d_out_values, CL_TRUE, 0, | |
| 270 | sizeof(uint32_t) * NUM_TEST_ITEMS, h_results, 0, NULL, NULL); | |
| 271 | CL_CHECK(err, "read lookup results"); | |
| 272 | ||
| 273 | int hits = 0, misses = 0, wrong = 0; | |
| 274 | for (int i = 0; i < NUM_TEST_ITEMS; i++) { | |
| 275 | if (h_results[i] == HT_EMPTY_VALUE) | |
| 276 | misses++; | |
| 277 | else if (h_results[i] == h_values[i]) | |
| 278 | hits++; | |
| 279 | else | |
| 280 | wrong++; | |
| 281 | } | |
| 282 | ||
| 283 | printf(" Lookup in %.1f ms (%.1f M keys/sec)\n", | |
| 284 | t1-t0, NUM_TEST_ITEMS / ((t1-t0) / 1000.0) / 1e6); | |
| 285 | printf(" Hits: %d Misses: %d Wrong: %d\n", hits, misses, wrong); | |
| 286 | printf(" %s\n\n", (hits == NUM_TEST_ITEMS && misses == 0 && wrong == 0) | |
| 287 | ? "PASS" : "FAIL"); | |
| 288 | ||
| 289 | /* ═══ TEST 3: Lookup non-existent keys (should all miss) ═══ */ | |
| 290 | ||
| 291 | printf("--- Test 3: Lookup %d non-existent keys ---\n", NUM_TEST_ITEMS); | |
| 292 | ||
| 293 | /* Generate different keys */ | |
| 294 | rng_state = 0xABCDABCDABCDABCDULL; | |
| 295 | uint64_t* h_miss_keys = malloc(sizeof(uint64_t) * NUM_TEST_ITEMS); | |
| 296 | for (int i = 0; i < NUM_TEST_ITEMS; i++) { | |
| 297 | uint64_t k = next_random(); | |
| 298 | if (k == HT_EMPTY_KEY) k = 0; | |
| 299 | h_miss_keys[i] = k; | |
| 300 | } | |
| 301 | ||
| 302 | cl_mem d_miss_keys = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 303 | sizeof(uint64_t) * NUM_TEST_ITEMS, h_miss_keys, &err); | |
| 304 | CL_CHECK(err, "alloc miss keys"); | |
| 305 | ||
| 306 | clSetKernelArg(k_lookup, 3, sizeof(cl_mem), &d_miss_keys); | |
| 307 | ||
| 308 | t0 = now_ms(); | |
| 309 | err = clEnqueueNDRangeKernel(queue, k_lookup, 1, NULL, | |
| 310 | &global_size, &local_size, 0, NULL, NULL); | |
| 311 | CL_CHECK(err, "enqueue miss lookup"); | |
| 312 | clFinish(queue); | |
| 313 | t1 = now_ms(); | |
| 314 | ||
| 315 | err = clEnqueueReadBuffer(queue, d_out_values, CL_TRUE, 0, | |
| 316 | sizeof(uint32_t) * NUM_TEST_ITEMS, h_results, 0, NULL, NULL); | |
| 317 | CL_CHECK(err, "read miss results"); | |
| 318 | ||
| 319 | misses = 0; | |
| 320 | for (int i = 0; i < NUM_TEST_ITEMS; i++) { | |
| 321 | if (h_results[i] == HT_EMPTY_VALUE) misses++; | |
| 322 | } | |
| 323 | ||
| 324 | printf(" Lookup in %.1f ms\n", t1-t0); | |
| 325 | printf(" Misses: %d / %d\n", misses, NUM_TEST_ITEMS); | |
| 326 | printf(" %s\n\n", (misses == NUM_TEST_ITEMS) ? "PASS" : "FAIL"); | |
| 327 | ||
| 328 | /* ═══ TEST 4: Delete first 1000 keys, verify ═══ */ | |
| 329 | ||
| 330 | int num_delete = 1000; | |
| 331 | printf("--- Test 4: Delete %d keys ---\n", num_delete); | |
| 332 | ||
| 333 | cl_mem d_del_keys = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 334 | sizeof(uint64_t) * num_delete, h_keys, &err); | |
| 335 | CL_CHECK(err, "alloc del keys"); | |
| 336 | ||
| 337 | cl_uint nd = num_delete; | |
| 338 | clSetKernelArg(k_delete, 0, sizeof(cl_mem), &d_keys); | |
| 339 | clSetKernelArg(k_delete, 1, sizeof(cl_mem), &d_values); | |
| 340 | clSetKernelArg(k_delete, 2, sizeof(cl_ulong), &capacity); | |
| 341 | clSetKernelArg(k_delete, 3, sizeof(cl_mem), &d_del_keys); | |
| 342 | clSetKernelArg(k_delete, 4, sizeof(cl_uint), &nd); | |
| 343 | ||
| 344 | size_t del_global = ((num_delete + local_size - 1) / local_size) * local_size; | |
| 345 | err = clEnqueueNDRangeKernel(queue, k_delete, 1, NULL, | |
| 346 | &del_global, &local_size, 0, NULL, NULL); | |
| 347 | CL_CHECK(err, "enqueue delete"); | |
| 348 | clFinish(queue); | |
| 349 | ||
| 350 | /* Lookup deleted keys — should miss */ | |
| 351 | cl_mem d_del_query = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 352 | sizeof(uint64_t) * num_delete, h_keys, &err); | |
| 353 | cl_mem d_del_out = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 354 | sizeof(uint32_t) * num_delete, NULL, &err); | |
| 355 | ||
| 356 | clSetKernelArg(k_lookup, 3, sizeof(cl_mem), &d_del_query); | |
| 357 | clSetKernelArg(k_lookup, 4, sizeof(cl_mem), &d_del_out); | |
| 358 | clSetKernelArg(k_lookup, 5, sizeof(cl_uint), &nd); | |
| 359 | ||
| 360 | err = clEnqueueNDRangeKernel(queue, k_lookup, 1, NULL, | |
| 361 | &del_global, &local_size, 0, NULL, NULL); | |
| 362 | CL_CHECK(err, "enqueue del verify"); | |
| 363 | clFinish(queue); | |
| 364 | ||
| 365 | uint32_t* h_del_results = malloc(sizeof(uint32_t) * num_delete); | |
| 366 | err = clEnqueueReadBuffer(queue, d_del_out, CL_TRUE, 0, | |
| 367 | sizeof(uint32_t) * num_delete, h_del_results, 0, NULL, NULL); | |
| 368 | ||
| 369 | int del_gone = 0; | |
| 370 | for (int i = 0; i < num_delete; i++) { | |
| 371 | if (h_del_results[i] == HT_EMPTY_VALUE) del_gone++; | |
| 372 | } | |
| 373 | printf(" Deleted keys returning empty: %d / %d\n", del_gone, num_delete); | |
| 374 | printf(" %s\n\n", (del_gone == num_delete) ? "PASS" : "FAIL"); | |
| 375 | ||
| 376 | /* ═══ TEST 5: Insert-or-increment ═══ */ | |
| 377 | ||
| 378 | printf("--- Test 5: Insert-or-increment (counting) ---\n"); | |
| 379 | ||
| 380 | /* Re-initialize table: keys to 0xFF (empty), values to 0 (for counting) */ | |
| 381 | err = clEnqueueFillBuffer(queue, d_keys, &pattern_ff, 1, | |
| 382 | 0, sizeof(uint64_t) * TABLE_CAPACITY, 0, NULL, NULL); | |
| 383 | CL_CHECK(err, "refill keys"); | |
| 384 | uint8_t pattern_00 = 0x00; | |
| 385 | err = clEnqueueFillBuffer(queue, d_values, &pattern_00, 1, | |
| 386 | 0, sizeof(uint32_t) * TABLE_CAPACITY, 0, NULL, NULL); | |
| 387 | CL_CHECK(err, "refill values to 0"); | |
| 388 | clFinish(queue); | |
| 389 | ||
| 390 | /* Insert same 100 keys 1000 times each = 100K operations */ | |
| 391 | int inc_unique = 100; | |
| 392 | int inc_repeats = 1000; | |
| 393 | int inc_total = inc_unique * inc_repeats; | |
| 394 | uint64_t* h_inc_keys = malloc(sizeof(uint64_t) * inc_total); | |
| 395 | ||
| 396 | rng_state = 0xFEEDFACECAFEBABEULL; | |
| 397 | uint64_t base_keys[100]; | |
| 398 | for (int i = 0; i < inc_unique; i++) { | |
| 399 | base_keys[i] = next_random(); | |
| 400 | if (base_keys[i] == HT_EMPTY_KEY) base_keys[i] = 0; | |
| 401 | } | |
| 402 | for (int r = 0; r < inc_repeats; r++) { | |
| 403 | for (int i = 0; i < inc_unique; i++) { | |
| 404 | h_inc_keys[r * inc_unique + i] = base_keys[i]; | |
| 405 | } | |
| 406 | } | |
| 407 | ||
| 408 | cl_mem d_inc_keys = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 409 | sizeof(uint64_t) * inc_total, h_inc_keys, &err); | |
| 410 | CL_CHECK(err, "alloc inc keys"); | |
| 411 | ||
| 412 | cl_uint inc_n = inc_total; | |
| 413 | clSetKernelArg(k_inc, 0, sizeof(cl_mem), &d_keys); | |
| 414 | clSetKernelArg(k_inc, 1, sizeof(cl_mem), &d_values); | |
| 415 | clSetKernelArg(k_inc, 2, sizeof(cl_ulong), &capacity); | |
| 416 | clSetKernelArg(k_inc, 3, sizeof(cl_mem), &d_inc_keys); | |
| 417 | clSetKernelArg(k_inc, 4, sizeof(cl_uint), &inc_n); | |
| 418 | ||
| 419 | size_t inc_global = ((inc_total + local_size - 1) / local_size) * local_size; | |
| 420 | t0 = now_ms(); | |
| 421 | err = clEnqueueNDRangeKernel(queue, k_inc, 1, NULL, | |
| 422 | &inc_global, &local_size, 0, NULL, NULL); | |
| 423 | CL_CHECK(err, "enqueue increment"); | |
| 424 | clFinish(queue); | |
| 425 | t1 = now_ms(); | |
| 426 | printf(" %d increments in %.1f ms\n", inc_total, t1-t0); | |
| 427 | ||
| 428 | /* Lookup the 100 keys, verify counts = 1000 (first insert = 1, then 999 increments) */ | |
| 429 | cl_mem d_inc_query = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 430 | sizeof(uint64_t) * inc_unique, base_keys, &err); | |
| 431 | cl_mem d_inc_out = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 432 | sizeof(uint32_t) * inc_unique, NULL, &err); | |
| 433 | cl_uint inc_u = inc_unique; | |
| 434 | ||
| 435 | clSetKernelArg(k_lookup, 3, sizeof(cl_mem), &d_inc_query); | |
| 436 | clSetKernelArg(k_lookup, 4, sizeof(cl_mem), &d_inc_out); | |
| 437 | clSetKernelArg(k_lookup, 5, sizeof(cl_uint), &inc_u); | |
| 438 | ||
| 439 | size_t inc_q_global = ((inc_unique + local_size - 1) / local_size) * local_size; | |
| 440 | err = clEnqueueNDRangeKernel(queue, k_lookup, 1, NULL, | |
| 441 | &inc_q_global, &local_size, 0, NULL, NULL); | |
| 442 | clFinish(queue); | |
| 443 | ||
| 444 | uint32_t* h_inc_results = malloc(sizeof(uint32_t) * inc_unique); | |
| 445 | clEnqueueReadBuffer(queue, d_inc_out, CL_TRUE, 0, | |
| 446 | sizeof(uint32_t) * inc_unique, h_inc_results, 0, NULL, NULL); | |
| 447 | ||
| 448 | int correct_counts = 0; | |
| 449 | for (int i = 0; i < inc_unique; i++) { | |
| 450 | if (h_inc_results[i] == (uint32_t)inc_repeats) correct_counts++; | |
| 451 | else if (i < 5) printf(" key %d: expected %d got %u\n", i, inc_repeats, h_inc_results[i]); | |
| 452 | } | |
| 453 | printf(" Correct counts (%d): %d / %d\n", inc_repeats, correct_counts, inc_unique); | |
| 454 | printf(" %s\n\n", (correct_counts == inc_unique) ? "PASS" : "FAIL"); | |
| 455 | ||
| 456 | /* ═══ TEST 6: Iterate ═══ */ | |
| 457 | ||
| 458 | printf("--- Test 6: Iterate (collect non-empty entries) ---\n"); | |
| 459 | ||
| 460 | cl_mem d_iter_keys = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 461 | sizeof(uint64_t) * inc_unique * 2, NULL, &err); | |
| 462 | cl_mem d_iter_values = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 463 | sizeof(uint32_t) * inc_unique * 2, NULL, &err); | |
| 464 | uint32_t zero = 0; | |
| 465 | cl_mem d_iter_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 466 | sizeof(uint32_t), &zero, &err); | |
| 467 | cl_uint max_out = inc_unique * 2; | |
| 468 | ||
| 469 | clSetKernelArg(k_iter, 0, sizeof(cl_mem), &d_keys); | |
| 470 | clSetKernelArg(k_iter, 1, sizeof(cl_mem), &d_values); | |
| 471 | clSetKernelArg(k_iter, 2, sizeof(cl_ulong), &capacity); | |
| 472 | clSetKernelArg(k_iter, 3, sizeof(cl_mem), &d_iter_keys); | |
| 473 | clSetKernelArg(k_iter, 4, sizeof(cl_mem), &d_iter_values); | |
| 474 | clSetKernelArg(k_iter, 5, sizeof(cl_mem), &d_iter_count); | |
| 475 | clSetKernelArg(k_iter, 6, sizeof(cl_uint), &max_out); | |
| 476 | ||
| 477 | size_t iter_global = ((TABLE_CAPACITY + local_size - 1) / local_size) * local_size; | |
| 478 | t0 = now_ms(); | |
| 479 | err = clEnqueueNDRangeKernel(queue, k_iter, 1, NULL, | |
| 480 | &iter_global, &local_size, 0, NULL, NULL); | |
| 481 | CL_CHECK(err, "enqueue iterate"); | |
| 482 | clFinish(queue); | |
| 483 | t1 = now_ms(); | |
| 484 | ||
| 485 | uint32_t iter_count; | |
| 486 | clEnqueueReadBuffer(queue, d_iter_count, CL_TRUE, 0, | |
| 487 | sizeof(uint32_t), &iter_count, 0, NULL, NULL); | |
| 488 | printf(" Iterated %u M slots in %.1f ms, found %u entries\n", | |
| 489 | TABLE_CAPACITY / (1024*1024), t1-t0, iter_count); | |
| 490 | printf(" Expected: %d entries\n", inc_unique); | |
| 491 | printf(" %s\n\n", (iter_count == (uint32_t)inc_unique) ? "PASS" : "FAIL"); | |
| 492 | ||
| 493 | /* ═══ Summary ═══ */ | |
| 494 | ||
| 495 | printf("=== All tests complete ===\n"); | |
| 496 | ||
| 497 | /* ─── Cleanup ─── */ | |
| 498 | ||
| 499 | clReleaseMemObject(d_keys); | |
| 500 | clReleaseMemObject(d_values); | |
| 501 | clReleaseMemObject(d_in_keys); | |
| 502 | clReleaseMemObject(d_in_values); | |
| 503 | clReleaseMemObject(d_out_values); | |
| 504 | clReleaseMemObject(d_miss_keys); | |
| 505 | clReleaseMemObject(d_del_keys); | |
| 506 | clReleaseMemObject(d_del_query); | |
| 507 | clReleaseMemObject(d_del_out); | |
| 508 | clReleaseMemObject(d_inc_keys); | |
| 509 | clReleaseMemObject(d_inc_query); | |
| 510 | clReleaseMemObject(d_inc_out); | |
| 511 | clReleaseMemObject(d_iter_keys); | |
| 512 | clReleaseMemObject(d_iter_values); | |
| 513 | clReleaseMemObject(d_iter_count); | |
| 514 | clReleaseKernel(k_insert); | |
| 515 | clReleaseKernel(k_lookup); | |
| 516 | clReleaseKernel(k_delete); | |
| 517 | clReleaseKernel(k_inc); | |
| 518 | clReleaseKernel(k_iter); | |
| 519 | clReleaseProgram(program); | |
| 520 | clReleaseCommandQueue(queue); | |
| 521 | clReleaseContext(ctx); | |
| 522 | free(h_keys); | |
| 523 | free(h_values); | |
| 524 | free(h_results); | |
| 525 | free(h_miss_keys); | |
| 526 | free(h_del_results); | |
| 527 | free(h_inc_keys); | |
| 528 | free(h_inc_results); | |
| 529 | free(src); | |
| 530 | ||
| 531 | return 0; | |
| 532 | } |
| … | … | @@ -0,0 +1,961 @@ |
| 1 | /* | |
| 2 | * test-mi.c -- Test GPU-resident MI computation | |
| 3 | * | |
| 4 | * Compile: gcc -O2 -o test-mi test-mi.c -lOpenCL -lm | |
| 5 | * Run: ./test-mi | |
| 6 | * | |
| 7 | * Tests the full pipeline: count sentences → compute MI → verify. | |
| 8 | * All data stays on GPU — no CPU↔GPU marshaling for MI. | |
| 9 | * | |
| 10 | * Tests: | |
| 11 | * 1. Manual MI verification (known counts → expected MI) | |
| 12 | * 2. Pipeline: count → MI (sentences → pairs → MI in one flow) | |
| 13 | * 3. Dirty-only MI (incremental recompute) | |
| 14 | * 4. MI statistics (count positive/above-threshold) | |
| 15 | * 5. MI filter (compact high-MI pairs) | |
| 16 | * 6. Benchmark: 1000 sentences → MI on all pairs | |
| 17 | */ | |
| 18 | ||
| 19 | #include <stdio.h> | |
| 20 | #include <stdlib.h> | |
| 21 | #include <stdint.h> | |
| 22 | #include <string.h> | |
| 23 | #include <math.h> | |
| 24 | #include <time.h> | |
| 25 | #include <CL/cl.h> | |
| 26 | ||
| 27 | /* ─── Pool capacities ─── */ | |
| 28 | ||
| 29 | #define WORD_CAPACITY (128 * 1024) | |
| 30 | #define PAIR_CAPACITY (4 * 1024 * 1024) | |
| 31 | #define SECTION_CAPACITY (1024 * 1024) | |
| 32 | #define WORD_HT_CAPACITY (256 * 1024) | |
| 33 | #define PAIR_HT_CAPACITY (8 * 1024 * 1024) | |
| 34 | #define SECTION_HT_CAPACITY (2 * 1024 * 1024) | |
| 35 | ||
| 36 | #define HT_EMPTY_KEY 0xFFFFFFFFFFFFFFFFULL | |
| 37 | #define HT_EMPTY_VALUE 0xFFFFFFFFU | |
| 38 | ||
| 39 | /* ─── Error checking ─── */ | |
| 40 | ||
| 41 | #define CL_CHECK(err, msg) do { \ | |
| 42 | if ((err) != CL_SUCCESS) { \ | |
| 43 | fprintf(stderr, "OpenCL error %d at %s:%d: %s\n", \ | |
| 44 | (err), __FILE__, __LINE__, (msg)); \ | |
| 45 | exit(1); \ | |
| 46 | } \ | |
| 47 | } while(0) | |
| 48 | ||
| 49 | /* ─── Read file ─── */ | |
| 50 | ||
| 51 | char* read_file(const char* path, size_t* len) | |
| 52 | { | |
| 53 | FILE* f = fopen(path, "r"); | |
| 54 | if (!f) { fprintf(stderr, "Cannot open %s\n", path); exit(1); } | |
| 55 | fseek(f, 0, SEEK_END); | |
| 56 | *len = ftell(f); | |
| 57 | fseek(f, 0, SEEK_SET); | |
| 58 | char* buf = malloc(*len + 1); | |
| 59 | size_t n = fread(buf, 1, *len, f); | |
| 60 | buf[n] = '\0'; | |
| 61 | *len = n; | |
| 62 | fclose(f); | |
| 63 | return buf; | |
| 64 | } | |
| 65 | ||
| 66 | /* ─── Timing ─── */ | |
| 67 | ||
| 68 | double now_ms(void) | |
| 69 | { | |
| 70 | struct timespec ts; | |
| 71 | clock_gettime(CLOCK_MONOTONIC, &ts); | |
| 72 | return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0; | |
| 73 | } | |
| 74 | ||
| 75 | /* ─── CPU MI for verification ─── */ | |
| 76 | ||
| 77 | double cpu_mi(double count, double left_marg, double right_marg, double n) | |
| 78 | { | |
| 79 | if (count < 1.0 || left_marg < 1e-10 || right_marg < 1e-10 || n < 1e-10) | |
| 80 | return 0.0; | |
| 81 | return log2(count * n / (left_marg * right_marg)); | |
| 82 | } | |
| 83 | ||
| 84 | /* ─── Reset pools ─── */ | |
| 85 | ||
| 86 | void reset_pools(cl_command_queue queue, | |
| 87 | cl_mem pht_keys, cl_mem pht_values, | |
| 88 | cl_mem pair_count, cl_mem pair_mi, cl_mem pair_flags, | |
| 89 | cl_mem word_count, | |
| 90 | cl_mem pair_next_free, cl_mem total_pair_count) | |
| 91 | { | |
| 92 | uint8_t pat_ff = 0xFF, pat_00 = 0x00; | |
| 93 | uint32_t zero = 0; | |
| 94 | ||
| 95 | clEnqueueFillBuffer(queue, pht_keys, &pat_ff, 1, 0, | |
| 96 | sizeof(uint64_t) * PAIR_HT_CAPACITY, 0, NULL, NULL); | |
| 97 | clEnqueueFillBuffer(queue, pht_values, &pat_ff, 1, 0, | |
| 98 | sizeof(uint32_t) * PAIR_HT_CAPACITY, 0, NULL, NULL); | |
| 99 | clEnqueueFillBuffer(queue, pair_count, &pat_00, 1, 0, | |
| 100 | sizeof(double) * PAIR_CAPACITY, 0, NULL, NULL); | |
| 101 | clEnqueueFillBuffer(queue, pair_mi, &pat_00, 1, 0, | |
| 102 | sizeof(double) * PAIR_CAPACITY, 0, NULL, NULL); | |
| 103 | clEnqueueFillBuffer(queue, pair_flags, &pat_00, 1, 0, | |
| 104 | sizeof(uint32_t) * PAIR_CAPACITY, 0, NULL, NULL); | |
| 105 | clEnqueueFillBuffer(queue, word_count, &pat_00, 1, 0, | |
| 106 | sizeof(double) * WORD_CAPACITY, 0, NULL, NULL); | |
| 107 | clEnqueueWriteBuffer(queue, pair_next_free, CL_FALSE, 0, | |
| 108 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 109 | clEnqueueWriteBuffer(queue, total_pair_count, CL_FALSE, 0, | |
| 110 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 111 | clFinish(queue); | |
| 112 | } | |
| 113 | ||
| 114 | /* ─── Main ─── */ | |
| 115 | ||
| 116 | int main(int argc, char** argv) | |
| 117 | { | |
| 118 | cl_int err; | |
| 119 | int pass_count = 0, fail_count = 0; | |
| 120 | ||
| 121 | printf("=== GPU MI Computation Test ===\n\n"); | |
| 122 | ||
| 123 | /* ─── OpenCL setup ─── */ | |
| 124 | ||
| 125 | cl_platform_id platform; | |
| 126 | err = clGetPlatformIDs(1, &platform, NULL); | |
| 127 | CL_CHECK(err, "platform"); | |
| 128 | ||
| 129 | cl_device_id device; | |
| 130 | err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL); | |
| 131 | CL_CHECK(err, "device"); | |
| 132 | ||
| 133 | char dev_name[256]; | |
| 134 | clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(dev_name), dev_name, NULL); | |
| 135 | printf("GPU: %s\n", dev_name); | |
| 136 | ||
| 137 | cl_context ctx = clCreateContext(NULL, 1, &device, NULL, NULL, &err); | |
| 138 | CL_CHECK(err, "context"); | |
| 139 | ||
| 140 | cl_command_queue queue = clCreateCommandQueue(ctx, device, 0, &err); | |
| 141 | CL_CHECK(err, "queue"); | |
| 142 | ||
| 143 | /* ─── Load and concatenate all 4 kernel sources ─── */ | |
| 144 | ||
| 145 | size_t ht_len, as_len, ct_len, mi_len; | |
| 146 | char* ht_src = read_file("gpu-hashtable.cl", &ht_len); | |
| 147 | char* as_src = read_file("gpu-atomspace.cl", &as_len); | |
| 148 | char* ct_src = read_file("gpu-counting.cl", &ct_len); | |
| 149 | char* mi_src = read_file("gpu-mi.cl", &mi_len); | |
| 150 | ||
| 151 | size_t total_len = ht_len + 1 + as_len + 1 + ct_len + 1 + mi_len; | |
| 152 | char* combined = malloc(total_len + 1); | |
| 153 | size_t off = 0; | |
| 154 | memcpy(combined + off, ht_src, ht_len); off += ht_len; | |
| 155 | combined[off++] = '\n'; | |
| 156 | memcpy(combined + off, as_src, as_len); off += as_len; | |
| 157 | combined[off++] = '\n'; | |
| 158 | memcpy(combined + off, ct_src, ct_len); off += ct_len; | |
| 159 | combined[off++] = '\n'; | |
| 160 | memcpy(combined + off, mi_src, mi_len); off += mi_len; | |
| 161 | combined[off] = '\0'; | |
| 162 | ||
| 163 | cl_program program = clCreateProgramWithSource(ctx, 1, | |
| 164 | (const char**)&combined, &total_len, &err); | |
| 165 | CL_CHECK(err, "create program"); | |
| 166 | ||
| 167 | char build_opts[512]; | |
| 168 | snprintf(build_opts, sizeof(build_opts), | |
| 169 | "-cl-std=CL1.2 " | |
| 170 | "-DWORD_CAPACITY=%d " | |
| 171 | "-DPAIR_CAPACITY=%d " | |
| 172 | "-DSECTION_CAPACITY=%d " | |
| 173 | "-DWORD_HT_CAPACITY=%d " | |
| 174 | "-DPAIR_HT_CAPACITY=%d " | |
| 175 | "-DSECTION_HT_CAPACITY=%d", | |
| 176 | WORD_CAPACITY, PAIR_CAPACITY, SECTION_CAPACITY, | |
| 177 | WORD_HT_CAPACITY, PAIR_HT_CAPACITY, SECTION_HT_CAPACITY); | |
| 178 | ||
| 179 | err = clBuildProgram(program, 1, &device, build_opts, NULL, NULL); | |
| 180 | if (err != CL_SUCCESS) { | |
| 181 | char log[16384]; | |
| 182 | clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, | |
| 183 | sizeof(log), log, NULL); | |
| 184 | fprintf(stderr, "Build error:\n%s\n", log); | |
| 185 | return 1; | |
| 186 | } | |
| 187 | printf("All kernels compiled successfully\n\n"); | |
| 188 | ||
| 189 | /* ─── Create kernels ─── */ | |
| 190 | ||
| 191 | cl_kernel k_count = clCreateKernel(program, "count_sentence_pairs", &err); | |
| 192 | CL_CHECK(err, "kernel count_sentence_pairs"); | |
| 193 | cl_kernel k_mi_all = clCreateKernel(program, "compute_mi_resident", &err); | |
| 194 | CL_CHECK(err, "kernel compute_mi_resident"); | |
| 195 | cl_kernel k_mi_dirty = clCreateKernel(program, "compute_mi_dirty", &err); | |
| 196 | CL_CHECK(err, "kernel compute_mi_dirty"); | |
| 197 | cl_kernel k_mi_stats = clCreateKernel(program, "mi_stats", &err); | |
| 198 | CL_CHECK(err, "kernel mi_stats"); | |
| 199 | cl_kernel k_mi_filter = clCreateKernel(program, "mi_filter", &err); | |
| 200 | CL_CHECK(err, "kernel mi_filter"); | |
| 201 | cl_kernel k_read_mi = clCreateKernel(program, "read_pairs_with_mi", &err); | |
| 202 | CL_CHECK(err, "kernel read_pairs_with_mi"); | |
| 203 | ||
| 204 | size_t local_size = 256; | |
| 205 | ||
| 206 | /* ─── Allocate GPU buffers ─── */ | |
| 207 | ||
| 208 | uint32_t zero = 0; | |
| 209 | ||
| 210 | cl_mem pht_keys = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 211 | sizeof(uint64_t) * PAIR_HT_CAPACITY, NULL, &err); | |
| 212 | cl_mem pht_values = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 213 | sizeof(uint32_t) * PAIR_HT_CAPACITY, NULL, &err); | |
| 214 | cl_mem pair_word_a = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 215 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 216 | cl_mem pair_word_b = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 217 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 218 | cl_mem pair_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 219 | sizeof(double) * PAIR_CAPACITY, NULL, &err); | |
| 220 | cl_mem pair_mi = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 221 | sizeof(double) * PAIR_CAPACITY, NULL, &err); | |
| 222 | cl_mem pair_flags = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 223 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 224 | cl_mem pair_next_free = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 225 | sizeof(uint32_t), &zero, &err); | |
| 226 | cl_mem word_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 227 | sizeof(double) * WORD_CAPACITY, NULL, &err); | |
| 228 | cl_mem total_pair_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 229 | sizeof(uint32_t), &zero, &err); | |
| 230 | ||
| 231 | reset_pools(queue, pht_keys, pht_values, pair_count, pair_mi, | |
| 232 | pair_flags, word_count, pair_next_free, total_pair_count); | |
| 233 | ||
| 234 | printf("GPU buffers ready\n\n"); | |
| 235 | ||
| 236 | /* ═══════════════════════════════════════════════════════════════ | |
| 237 | * TEST 1: Manual MI verification | |
| 238 | * | |
| 239 | * Manually set pair counts and word marginals, then verify | |
| 240 | * that compute_mi_resident produces correct MI values. | |
| 241 | * | |
| 242 | * Setup (3 pairs, 4 words): | |
| 243 | * pair 0: (word0, word1) count=10 → MI = log2(10*100/(30*40)) | |
| 244 | * pair 1: (word0, word2) count=5 → MI = log2(5*100/(30*20)) | |
| 245 | * pair 2: (word1, word3) count=20 → MI = log2(20*100/(40*50)) | |
| 246 | * | |
| 247 | * Word marginals: word0=30, word1=40, word2=20, word3=50 | |
| 248 | * N = 100 (total pair observations) | |
| 249 | * ═══════════════════════════════════════════════════════════════ */ | |
| 250 | ||
| 251 | printf("--- Test 1: Manual MI verification ---\n"); | |
| 252 | ||
| 253 | /* Write pair data directly to GPU */ | |
| 254 | uint32_t h_pa[] = {0, 0, 1}; | |
| 255 | uint32_t h_pb[] = {1, 2, 3}; | |
| 256 | double h_pc[] = {10.0, 5.0, 20.0}; | |
| 257 | double h_wc[] = {30.0, 40.0, 20.0, 50.0}; | |
| 258 | uint32_t h_np = 3; | |
| 259 | ||
| 260 | clEnqueueWriteBuffer(queue, pair_word_a, CL_FALSE, 0, | |
| 261 | sizeof(uint32_t) * 3, h_pa, 0, NULL, NULL); | |
| 262 | clEnqueueWriteBuffer(queue, pair_word_b, CL_FALSE, 0, | |
| 263 | sizeof(uint32_t) * 3, h_pb, 0, NULL, NULL); | |
| 264 | clEnqueueWriteBuffer(queue, pair_count, CL_FALSE, 0, | |
| 265 | sizeof(double) * 3, h_pc, 0, NULL, NULL); | |
| 266 | clEnqueueWriteBuffer(queue, word_count, CL_FALSE, 0, | |
| 267 | sizeof(double) * 4, h_wc, 0, NULL, NULL); | |
| 268 | clEnqueueWriteBuffer(queue, pair_next_free, CL_FALSE, 0, | |
| 269 | sizeof(uint32_t), &h_np, 0, NULL, NULL); | |
| 270 | clFinish(queue); | |
| 271 | ||
| 272 | /* Compute expected MI on CPU */ | |
| 273 | double n_total = 100.0; | |
| 274 | double expected_mi[3]; | |
| 275 | expected_mi[0] = cpu_mi(10.0, 30.0, 40.0, 100.0); /* log2(10*100/1200) */ | |
| 276 | expected_mi[1] = cpu_mi(5.0, 30.0, 20.0, 100.0); /* log2(5*100/600) */ | |
| 277 | expected_mi[2] = cpu_mi(20.0, 40.0, 50.0, 100.0); /* log2(20*100/2000) */ | |
| 278 | ||
| 279 | printf(" Expected MI: [%.4f, %.4f, %.4f]\n", | |
| 280 | expected_mi[0], expected_mi[1], expected_mi[2]); | |
| 281 | ||
| 282 | /* Run MI kernel */ | |
| 283 | cl_uint np = 3; | |
| 284 | clSetKernelArg(k_mi_all, 0, sizeof(cl_mem), &pair_count); | |
| 285 | clSetKernelArg(k_mi_all, 1, sizeof(cl_mem), &pair_word_a); | |
| 286 | clSetKernelArg(k_mi_all, 2, sizeof(cl_mem), &pair_word_b); | |
| 287 | clSetKernelArg(k_mi_all, 3, sizeof(cl_mem), &pair_mi); | |
| 288 | clSetKernelArg(k_mi_all, 4, sizeof(cl_mem), &word_count); | |
| 289 | clSetKernelArg(k_mi_all, 5, sizeof(cl_double), &n_total); | |
| 290 | clSetKernelArg(k_mi_all, 6, sizeof(cl_uint), &np); | |
| 291 | ||
| 292 | size_t gs = ((np + local_size - 1) / local_size) * local_size; | |
| 293 | double t0 = now_ms(); | |
| 294 | err = clEnqueueNDRangeKernel(queue, k_mi_all, 1, NULL, | |
| 295 | &gs, &local_size, 0, NULL, NULL); | |
| 296 | CL_CHECK(err, "enqueue mi_all"); | |
| 297 | clFinish(queue); | |
| 298 | double t1 = now_ms(); | |
| 299 | ||
| 300 | /* Read back MI values */ | |
| 301 | double gpu_mi[3]; | |
| 302 | clEnqueueReadBuffer(queue, pair_mi, CL_TRUE, 0, | |
| 303 | sizeof(double) * 3, gpu_mi, 0, NULL, NULL); | |
| 304 | ||
| 305 | printf(" GPU MI: [%.4f, %.4f, %.4f]\n", | |
| 306 | gpu_mi[0], gpu_mi[1], gpu_mi[2]); | |
| 307 | printf(" Time: %.2f ms\n", t1 - t0); | |
| 308 | ||
| 309 | int t1_pass = 1; | |
| 310 | for (int i = 0; i < 3; i++) { | |
| 311 | double diff = fabs(gpu_mi[i] - expected_mi[i]); | |
| 312 | if (diff > 0.001) { | |
| 313 | printf(" MISMATCH pair %d: gpu=%.6f expected=%.6f diff=%.6f\n", | |
| 314 | i, gpu_mi[i], expected_mi[i], diff); | |
| 315 | t1_pass = 0; | |
| 316 | } | |
| 317 | } | |
| 318 | printf(" %s\n\n", t1_pass ? "PASS" : "FAIL"); | |
| 319 | if (t1_pass) pass_count++; else fail_count++; | |
| 320 | ||
| 321 | /* ═══════════════════════════════════════════════════════════════ | |
| 322 | * TEST 2: Full pipeline — count sentences → compute MI | |
| 323 | * | |
| 324 | * Run count_sentence_pairs on test sentences, then immediately | |
| 325 | * run compute_mi_resident on the same GPU-resident data. | |
| 326 | * All data stays on GPU — zero transfers between stages. | |
| 327 | * | |
| 328 | * Sentence 1: [0, 1, 2, 3] (4 words, window=2) | |
| 329 | * Sentence 2: [4, 5, 6] (3 words) | |
| 330 | * | |
| 331 | * Expected: 8 pairs from counting, all get valid MI > 0. | |
| 332 | * ═══════════════════════════════════════════════════════════════ */ | |
| 333 | ||
| 334 | printf("--- Test 2: Full pipeline (count → MI) ---\n"); | |
| 335 | ||
| 336 | reset_pools(queue, pht_keys, pht_values, pair_count, pair_mi, | |
| 337 | pair_flags, word_count, pair_next_free, total_pair_count); | |
| 338 | ||
| 339 | uint32_t sent_words[] = {0, 1, 2, 3, 4, 5, 6}; | |
| 340 | uint32_t sent_offsets[] = {0, 4}; | |
| 341 | uint32_t sent_lengths[] = {4, 3}; | |
| 342 | cl_uint num_sentences = 2; | |
| 343 | cl_uint tw = 7; | |
| 344 | cl_uint window_size = 2; | |
| 345 | ||
| 346 | cl_mem d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 347 | sizeof(uint32_t) * tw, sent_words, &err); | |
| 348 | cl_mem d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 349 | sizeof(uint32_t) * num_sentences, sent_offsets, &err); | |
| 350 | cl_mem d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 351 | sizeof(uint32_t) * num_sentences, sent_lengths, &err); | |
| 352 | ||
| 353 | /* Stage 1: Count pairs */ | |
| 354 | clSetKernelArg(k_count, 0, sizeof(cl_mem), &d_flat_words); | |
| 355 | clSetKernelArg(k_count, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 356 | clSetKernelArg(k_count, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 357 | clSetKernelArg(k_count, 3, sizeof(cl_uint), &num_sentences); | |
| 358 | clSetKernelArg(k_count, 4, sizeof(cl_uint), &tw); | |
| 359 | clSetKernelArg(k_count, 5, sizeof(cl_uint), &window_size); | |
| 360 | clSetKernelArg(k_count, 6, sizeof(cl_mem), &pht_keys); | |
| 361 | clSetKernelArg(k_count, 7, sizeof(cl_mem), &pht_values); | |
| 362 | clSetKernelArg(k_count, 8, sizeof(cl_mem), &pair_word_a); | |
| 363 | clSetKernelArg(k_count, 9, sizeof(cl_mem), &pair_word_b); | |
| 364 | clSetKernelArg(k_count, 10, sizeof(cl_mem), &pair_count); | |
| 365 | clSetKernelArg(k_count, 11, sizeof(cl_mem), &pair_mi); | |
| 366 | clSetKernelArg(k_count, 12, sizeof(cl_mem), &pair_flags); | |
| 367 | clSetKernelArg(k_count, 13, sizeof(cl_mem), &pair_next_free); | |
| 368 | clSetKernelArg(k_count, 14, sizeof(cl_mem), &word_count); | |
| 369 | clSetKernelArg(k_count, 15, sizeof(cl_mem), &total_pair_count); | |
| 370 | ||
| 371 | t0 = now_ms(); | |
| 372 | gs = ((tw + local_size - 1) / local_size) * local_size; | |
| 373 | err = clEnqueueNDRangeKernel(queue, k_count, 1, NULL, | |
| 374 | &gs, &local_size, 0, NULL, NULL); | |
| 375 | CL_CHECK(err, "enqueue count"); | |
| 376 | clFinish(queue); | |
| 377 | double t_count = now_ms() - t0; | |
| 378 | ||
| 379 | /* Read num_pairs and total for MI computation */ | |
| 380 | uint32_t h_num_pairs, h_total; | |
| 381 | clEnqueueReadBuffer(queue, pair_next_free, CL_TRUE, 0, | |
| 382 | sizeof(uint32_t), &h_num_pairs, 0, NULL, NULL); | |
| 383 | clEnqueueReadBuffer(queue, total_pair_count, CL_TRUE, 0, | |
| 384 | sizeof(uint32_t), &h_total, 0, NULL, NULL); | |
| 385 | ||
| 386 | printf(" Stage 1 (count): %u pairs, %u events in %.2f ms\n", | |
| 387 | h_num_pairs, h_total, t_count); | |
| 388 | ||
| 389 | /* Stage 2: Compute MI — data stays on GPU! */ | |
| 390 | double n = (double)h_total; | |
| 391 | clSetKernelArg(k_mi_all, 0, sizeof(cl_mem), &pair_count); | |
| 392 | clSetKernelArg(k_mi_all, 1, sizeof(cl_mem), &pair_word_a); | |
| 393 | clSetKernelArg(k_mi_all, 2, sizeof(cl_mem), &pair_word_b); | |
| 394 | clSetKernelArg(k_mi_all, 3, sizeof(cl_mem), &pair_mi); | |
| 395 | clSetKernelArg(k_mi_all, 4, sizeof(cl_mem), &word_count); | |
| 396 | clSetKernelArg(k_mi_all, 5, sizeof(cl_double), &n); | |
| 397 | clSetKernelArg(k_mi_all, 6, sizeof(cl_uint), &h_num_pairs); | |
| 398 | ||
| 399 | t0 = now_ms(); | |
| 400 | gs = ((h_num_pairs + local_size - 1) / local_size) * local_size; | |
| 401 | err = clEnqueueNDRangeKernel(queue, k_mi_all, 1, NULL, | |
| 402 | &gs, &local_size, 0, NULL, NULL); | |
| 403 | CL_CHECK(err, "enqueue mi_all pipeline"); | |
| 404 | clFinish(queue); | |
| 405 | double t_mi = now_ms() - t0; | |
| 406 | ||
| 407 | printf(" Stage 2 (MI): %u pairs in %.2f ms\n", h_num_pairs, t_mi); | |
| 408 | printf(" Total pipeline: %.2f ms (zero transfers between stages)\n", | |
| 409 | t_count + t_mi); | |
| 410 | ||
| 411 | /* Read back and verify with read_pairs_with_mi */ | |
| 412 | cl_mem d_out_wa = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 413 | sizeof(uint32_t) * h_num_pairs, NULL, &err); | |
| 414 | cl_mem d_out_wb = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 415 | sizeof(uint32_t) * h_num_pairs, NULL, &err); | |
| 416 | cl_mem d_out_cnt = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 417 | sizeof(double) * h_num_pairs, NULL, &err); | |
| 418 | cl_mem d_out_mi = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 419 | sizeof(double) * h_num_pairs, NULL, &err); | |
| 420 | cl_mem d_out_lm = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 421 | sizeof(double) * h_num_pairs, NULL, &err); | |
| 422 | cl_mem d_out_rm = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 423 | sizeof(double) * h_num_pairs, NULL, &err); | |
| 424 | ||
| 425 | clSetKernelArg(k_read_mi, 0, sizeof(cl_mem), &pair_word_a); | |
| 426 | clSetKernelArg(k_read_mi, 1, sizeof(cl_mem), &pair_word_b); | |
| 427 | clSetKernelArg(k_read_mi, 2, sizeof(cl_mem), &pair_count); | |
| 428 | clSetKernelArg(k_read_mi, 3, sizeof(cl_mem), &pair_mi); | |
| 429 | clSetKernelArg(k_read_mi, 4, sizeof(cl_mem), &pair_flags); | |
| 430 | clSetKernelArg(k_read_mi, 5, sizeof(cl_mem), &word_count); | |
| 431 | clSetKernelArg(k_read_mi, 6, sizeof(cl_mem), &d_out_wa); | |
| 432 | clSetKernelArg(k_read_mi, 7, sizeof(cl_mem), &d_out_wb); | |
| 433 | clSetKernelArg(k_read_mi, 8, sizeof(cl_mem), &d_out_cnt); | |
| 434 | clSetKernelArg(k_read_mi, 9, sizeof(cl_mem), &d_out_mi); | |
| 435 | clSetKernelArg(k_read_mi, 10, sizeof(cl_mem), &d_out_lm); | |
| 436 | clSetKernelArg(k_read_mi, 11, sizeof(cl_mem), &d_out_rm); | |
| 437 | clSetKernelArg(k_read_mi, 12, sizeof(cl_uint), &h_num_pairs); | |
| 438 | ||
| 439 | gs = ((h_num_pairs + local_size - 1) / local_size) * local_size; | |
| 440 | clEnqueueNDRangeKernel(queue, k_read_mi, 1, NULL, | |
| 441 | &gs, &local_size, 0, NULL, NULL); | |
| 442 | clFinish(queue); | |
| 443 | ||
| 444 | uint32_t* r_wa = malloc(sizeof(uint32_t) * h_num_pairs); | |
| 445 | uint32_t* r_wb = malloc(sizeof(uint32_t) * h_num_pairs); | |
| 446 | double* r_cnt = malloc(sizeof(double) * h_num_pairs); | |
| 447 | double* r_mi = malloc(sizeof(double) * h_num_pairs); | |
| 448 | double* r_lm = malloc(sizeof(double) * h_num_pairs); | |
| 449 | double* r_rm = malloc(sizeof(double) * h_num_pairs); | |
| 450 | ||
| 451 | clEnqueueReadBuffer(queue, d_out_wa, CL_TRUE, 0, sizeof(uint32_t)*h_num_pairs, r_wa, 0, NULL, NULL); | |
| 452 | clEnqueueReadBuffer(queue, d_out_wb, CL_TRUE, 0, sizeof(uint32_t)*h_num_pairs, r_wb, 0, NULL, NULL); | |
| 453 | clEnqueueReadBuffer(queue, d_out_cnt, CL_TRUE, 0, sizeof(double)*h_num_pairs, r_cnt, 0, NULL, NULL); | |
| 454 | clEnqueueReadBuffer(queue, d_out_mi, CL_TRUE, 0, sizeof(double)*h_num_pairs, r_mi, 0, NULL, NULL); | |
| 455 | clEnqueueReadBuffer(queue, d_out_lm, CL_TRUE, 0, sizeof(double)*h_num_pairs, r_lm, 0, NULL, NULL); | |
| 456 | clEnqueueReadBuffer(queue, d_out_rm, CL_TRUE, 0, sizeof(double)*h_num_pairs, r_rm, 0, NULL, NULL); | |
| 457 | ||
| 458 | int t2_pass = 1; | |
| 459 | int all_mi_valid = 1; | |
| 460 | printf(" Pair details:\n"); | |
| 461 | for (uint32_t i = 0; i < h_num_pairs; i++) { | |
| 462 | double exp = cpu_mi(r_cnt[i], r_lm[i], r_rm[i], n); | |
| 463 | double diff = fabs(r_mi[i] - exp); | |
| 464 | printf(" [%u] (%u,%u) cnt=%.0f lm=%.0f rm=%.0f MI=%.4f (exp=%.4f) %s\n", | |
| 465 | i, r_wa[i], r_wb[i], r_cnt[i], r_lm[i], r_rm[i], | |
| 466 | r_mi[i], exp, (diff < 0.001) ? "OK" : "MISMATCH"); | |
| 467 | if (diff > 0.001) { t2_pass = 0; all_mi_valid = 0; } | |
| 468 | } | |
| 469 | printf(" All MI values match CPU: %s\n", all_mi_valid ? "YES" : "NO"); | |
| 470 | t2_pass = t2_pass && (h_num_pairs == 8); | |
| 471 | printf(" %s\n\n", t2_pass ? "PASS" : "FAIL"); | |
| 472 | if (t2_pass) pass_count++; else fail_count++; | |
| 473 | ||
| 474 | free(r_wa); free(r_wb); free(r_cnt); free(r_mi); free(r_lm); free(r_rm); | |
| 475 | clReleaseMemObject(d_out_wa); clReleaseMemObject(d_out_wb); | |
| 476 | clReleaseMemObject(d_out_cnt); clReleaseMemObject(d_out_mi); | |
| 477 | clReleaseMemObject(d_out_lm); clReleaseMemObject(d_out_rm); | |
| 478 | ||
| 479 | /* ═══════════════════════════════════════════════════════════════ | |
| 480 | * TEST 3: Dirty-only MI recompute | |
| 481 | * | |
| 482 | * After counting, all pairs have flags=1 (dirty). | |
| 483 | * Run compute_mi_dirty — should compute MI and clear flags. | |
| 484 | * Then add more sentences, creating new dirty pairs. | |
| 485 | * Run compute_mi_dirty again — should only recompute dirty ones. | |
| 486 | * ═══════════════════════════════════════════════════════════════ */ | |
| 487 | ||
| 488 | printf("--- Test 3: Dirty-only MI recompute ---\n"); | |
| 489 | ||
| 490 | reset_pools(queue, pht_keys, pht_values, pair_count, pair_mi, | |
| 491 | pair_flags, word_count, pair_next_free, total_pair_count); | |
| 492 | ||
| 493 | /* Count first batch: [0, 1, 2] window=2 → pairs (0,1),(0,2),(1,2) */ | |
| 494 | uint32_t batch1_words[] = {0, 1, 2}; | |
| 495 | uint32_t batch1_offset = 0; | |
| 496 | uint32_t batch1_length = 3; | |
| 497 | cl_uint b1_ns = 1, b1_tw = 3, b1_ws = 2; | |
| 498 | ||
| 499 | clReleaseMemObject(d_flat_words); | |
| 500 | clReleaseMemObject(d_sent_offsets); | |
| 501 | clReleaseMemObject(d_sent_lengths); | |
| 502 | ||
| 503 | d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 504 | sizeof(uint32_t) * 3, batch1_words, &err); | |
| 505 | d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 506 | sizeof(uint32_t), &batch1_offset, &err); | |
| 507 | d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 508 | sizeof(uint32_t), &batch1_length, &err); | |
| 509 | ||
| 510 | clSetKernelArg(k_count, 0, sizeof(cl_mem), &d_flat_words); | |
| 511 | clSetKernelArg(k_count, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 512 | clSetKernelArg(k_count, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 513 | clSetKernelArg(k_count, 3, sizeof(cl_uint), &b1_ns); | |
| 514 | clSetKernelArg(k_count, 4, sizeof(cl_uint), &b1_tw); | |
| 515 | clSetKernelArg(k_count, 5, sizeof(cl_uint), &b1_ws); | |
| 516 | ||
| 517 | clEnqueueNDRangeKernel(queue, k_count, 1, NULL, | |
| 518 | &local_size, &local_size, 0, NULL, NULL); | |
| 519 | clFinish(queue); | |
| 520 | ||
| 521 | clEnqueueReadBuffer(queue, pair_next_free, CL_TRUE, 0, | |
| 522 | sizeof(uint32_t), &h_num_pairs, 0, NULL, NULL); | |
| 523 | clEnqueueReadBuffer(queue, total_pair_count, CL_TRUE, 0, | |
| 524 | sizeof(uint32_t), &h_total, 0, NULL, NULL); | |
| 525 | ||
| 526 | printf(" Batch 1: %u pairs, %u events\n", h_num_pairs, h_total); | |
| 527 | ||
| 528 | /* Run dirty MI — should process all 3 pairs and clear flags */ | |
| 529 | n = (double)h_total; | |
| 530 | clSetKernelArg(k_mi_dirty, 0, sizeof(cl_mem), &pair_count); | |
| 531 | clSetKernelArg(k_mi_dirty, 1, sizeof(cl_mem), &pair_word_a); | |
| 532 | clSetKernelArg(k_mi_dirty, 2, sizeof(cl_mem), &pair_word_b); | |
| 533 | clSetKernelArg(k_mi_dirty, 3, sizeof(cl_mem), &pair_mi); | |
| 534 | clSetKernelArg(k_mi_dirty, 4, sizeof(cl_mem), &pair_flags); | |
| 535 | clSetKernelArg(k_mi_dirty, 5, sizeof(cl_mem), &word_count); | |
| 536 | clSetKernelArg(k_mi_dirty, 6, sizeof(cl_double), &n); | |
| 537 | clSetKernelArg(k_mi_dirty, 7, sizeof(cl_uint), &h_num_pairs); | |
| 538 | ||
| 539 | gs = ((h_num_pairs + local_size - 1) / local_size) * local_size; | |
| 540 | clEnqueueNDRangeKernel(queue, k_mi_dirty, 1, NULL, | |
| 541 | &gs, &local_size, 0, NULL, NULL); | |
| 542 | clFinish(queue); | |
| 543 | ||
| 544 | /* Verify flags are cleared */ | |
| 545 | uint32_t flags_after[3]; | |
| 546 | clEnqueueReadBuffer(queue, pair_flags, CL_TRUE, 0, | |
| 547 | sizeof(uint32_t) * 3, flags_after, 0, NULL, NULL); | |
| 548 | ||
| 549 | int all_clear = (flags_after[0] == 0 && flags_after[1] == 0 && flags_after[2] == 0); | |
| 550 | printf(" After dirty MI: flags=[%u,%u,%u] (all 0?) %s\n", | |
| 551 | flags_after[0], flags_after[1], flags_after[2], | |
| 552 | all_clear ? "YES" : "NO"); | |
| 553 | ||
| 554 | /* Read MI values after first batch */ | |
| 555 | double mi_batch1[3]; | |
| 556 | clEnqueueReadBuffer(queue, pair_mi, CL_TRUE, 0, | |
| 557 | sizeof(double) * 3, mi_batch1, 0, NULL, NULL); | |
| 558 | printf(" MI after batch 1: [%.4f, %.4f, %.4f]\n", | |
| 559 | mi_batch1[0], mi_batch1[1], mi_batch1[2]); | |
| 560 | ||
| 561 | /* Count second batch: [1, 2, 3] window=2 → new pair (1,3),(2,3) + existing (1,2) */ | |
| 562 | uint32_t batch2_words[] = {1, 2, 3}; | |
| 563 | uint32_t batch2_offset = 0; | |
| 564 | uint32_t batch2_length = 3; | |
| 565 | ||
| 566 | clReleaseMemObject(d_flat_words); | |
| 567 | d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 568 | sizeof(uint32_t) * 3, batch2_words, &err); | |
| 569 | ||
| 570 | clSetKernelArg(k_count, 0, sizeof(cl_mem), &d_flat_words); | |
| 571 | clSetKernelArg(k_count, 4, sizeof(cl_uint), &b1_tw); | |
| 572 | ||
| 573 | clEnqueueNDRangeKernel(queue, k_count, 1, NULL, | |
| 574 | &local_size, &local_size, 0, NULL, NULL); | |
| 575 | clFinish(queue); | |
| 576 | ||
| 577 | uint32_t h_num_pairs2, h_total2; | |
| 578 | clEnqueueReadBuffer(queue, pair_next_free, CL_TRUE, 0, | |
| 579 | sizeof(uint32_t), &h_num_pairs2, 0, NULL, NULL); | |
| 580 | clEnqueueReadBuffer(queue, total_pair_count, CL_TRUE, 0, | |
| 581 | sizeof(uint32_t), &h_total2, 0, NULL, NULL); | |
| 582 | ||
| 583 | printf(" Batch 2: %u total pairs, %u total events\n", h_num_pairs2, h_total2); | |
| 584 | ||
| 585 | /* Run dirty MI again — should only recompute dirty pairs */ | |
| 586 | n = (double)h_total2; | |
| 587 | clSetKernelArg(k_mi_dirty, 6, sizeof(cl_double), &n); | |
| 588 | clSetKernelArg(k_mi_dirty, 7, sizeof(cl_uint), &h_num_pairs2); | |
| 589 | ||
| 590 | gs = ((h_num_pairs2 + local_size - 1) / local_size) * local_size; | |
| 591 | clEnqueueNDRangeKernel(queue, k_mi_dirty, 1, NULL, | |
| 592 | &gs, &local_size, 0, NULL, NULL); | |
| 593 | clFinish(queue); | |
| 594 | ||
| 595 | /* Read all MI values */ | |
| 596 | double mi_batch2[5]; | |
| 597 | uint32_t flags_after2[5]; | |
| 598 | clEnqueueReadBuffer(queue, pair_mi, CL_TRUE, 0, | |
| 599 | sizeof(double) * h_num_pairs2, mi_batch2, 0, NULL, NULL); | |
| 600 | clEnqueueReadBuffer(queue, pair_flags, CL_TRUE, 0, | |
| 601 | sizeof(uint32_t) * h_num_pairs2, flags_after2, 0, NULL, NULL); | |
| 602 | ||
| 603 | printf(" MI after batch 2:\n"); | |
| 604 | int t3_pass = all_clear; | |
| 605 | for (uint32_t i = 0; i < h_num_pairs2; i++) { | |
| 606 | printf(" pair[%u] MI=%.4f flags=%u\n", i, mi_batch2[i], flags_after2[i]); | |
| 607 | if (flags_after2[i] != 0) t3_pass = 0; | |
| 608 | /* MI should be non-zero for all counted pairs */ | |
| 609 | if (fabs(mi_batch2[i]) < 0.001) t3_pass = 0; | |
| 610 | } | |
| 611 | printf(" All flags cleared: %s\n", | |
| 612 | (t3_pass) ? "YES" : "NO"); | |
| 613 | printf(" %s\n\n", t3_pass ? "PASS" : "FAIL"); | |
| 614 | if (t3_pass) pass_count++; else fail_count++; | |
| 615 | ||
| 616 | /* ═══════════════════════════════════════════════════════════════ | |
| 617 | * TEST 4: MI statistics | |
| 618 | * ═══════════════════════════════════════════════════════════════ */ | |
| 619 | ||
| 620 | printf("--- Test 4: MI statistics ---\n"); | |
| 621 | ||
| 622 | /* Use the data from Test 3 (5 pairs, all with MI > 0) */ | |
| 623 | cl_mem d_cnt_nz = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 624 | sizeof(uint32_t), &zero, &err); | |
| 625 | cl_mem d_cnt_pos = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 626 | sizeof(uint32_t), &zero, &err); | |
| 627 | cl_mem d_cnt_at = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 628 | sizeof(uint32_t), &zero, &err); | |
| 629 | ||
| 630 | cl_double threshold = 1.0; | |
| 631 | ||
| 632 | clSetKernelArg(k_mi_stats, 0, sizeof(cl_mem), &pair_mi); | |
| 633 | clSetKernelArg(k_mi_stats, 1, sizeof(cl_mem), &pair_count); | |
| 634 | clSetKernelArg(k_mi_stats, 2, sizeof(cl_uint), &h_num_pairs2); | |
| 635 | clSetKernelArg(k_mi_stats, 3, sizeof(cl_double), &threshold); | |
| 636 | clSetKernelArg(k_mi_stats, 4, sizeof(cl_mem), &d_cnt_nz); | |
| 637 | clSetKernelArg(k_mi_stats, 5, sizeof(cl_mem), &d_cnt_pos); | |
| 638 | clSetKernelArg(k_mi_stats, 6, sizeof(cl_mem), &d_cnt_at); | |
| 639 | ||
| 640 | gs = ((h_num_pairs2 + local_size - 1) / local_size) * local_size; | |
| 641 | clEnqueueNDRangeKernel(queue, k_mi_stats, 1, NULL, | |
| 642 | &gs, &local_size, 0, NULL, NULL); | |
| 643 | clFinish(queue); | |
| 644 | ||
| 645 | uint32_t cnt_nz, cnt_pos, cnt_at; | |
| 646 | clEnqueueReadBuffer(queue, d_cnt_nz, CL_TRUE, 0, sizeof(uint32_t), &cnt_nz, 0, NULL, NULL); | |
| 647 | clEnqueueReadBuffer(queue, d_cnt_pos, CL_TRUE, 0, sizeof(uint32_t), &cnt_pos, 0, NULL, NULL); | |
| 648 | clEnqueueReadBuffer(queue, d_cnt_at, CL_TRUE, 0, sizeof(uint32_t), &cnt_at, 0, NULL, NULL); | |
| 649 | ||
| 650 | printf(" Pairs with count > 0: %u (expected %u)\n", cnt_nz, h_num_pairs2); | |
| 651 | printf(" Pairs with MI > 0: %u\n", cnt_pos); | |
| 652 | printf(" Pairs with MI > %.1f: %u\n", threshold, cnt_at); | |
| 653 | ||
| 654 | /* Count expected on CPU */ | |
| 655 | int exp_pos = 0, exp_at = 0; | |
| 656 | for (uint32_t i = 0; i < h_num_pairs2; i++) { | |
| 657 | if (mi_batch2[i] > 0.0) exp_pos++; | |
| 658 | if (mi_batch2[i] > threshold) exp_at++; | |
| 659 | } | |
| 660 | ||
| 661 | int t4_pass = (cnt_nz == h_num_pairs2) && (cnt_pos == (uint32_t)exp_pos) | |
| 662 | && (cnt_at == (uint32_t)exp_at); | |
| 663 | printf(" Expected: pos=%d above=%.1f=%d\n", exp_pos, threshold, exp_at); | |
| 664 | printf(" %s\n\n", t4_pass ? "PASS" : "FAIL"); | |
| 665 | if (t4_pass) pass_count++; else fail_count++; | |
| 666 | ||
| 667 | clReleaseMemObject(d_cnt_nz); | |
| 668 | clReleaseMemObject(d_cnt_pos); | |
| 669 | clReleaseMemObject(d_cnt_at); | |
| 670 | ||
| 671 | /* ═══════════════════════════════════════════════════════════════ | |
| 672 | * TEST 5: MI filter (compact high-MI pairs) | |
| 673 | * ═══════════════════════════════════════════════════════════════ */ | |
| 674 | ||
| 675 | printf("--- Test 5: MI filter ---\n"); | |
| 676 | ||
| 677 | uint32_t max_output = 100; | |
| 678 | cl_mem d_filt_idx = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 679 | sizeof(uint32_t) * max_output, NULL, &err); | |
| 680 | cl_mem d_filt_mi = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 681 | sizeof(double) * max_output, NULL, &err); | |
| 682 | cl_mem d_filt_cnt = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 683 | sizeof(uint32_t), &zero, &err); | |
| 684 | ||
| 685 | cl_double mi_thresh = 1.0; | |
| 686 | ||
| 687 | clSetKernelArg(k_mi_filter, 0, sizeof(cl_mem), &pair_mi); | |
| 688 | clSetKernelArg(k_mi_filter, 1, sizeof(cl_mem), &pair_count); | |
| 689 | clSetKernelArg(k_mi_filter, 2, sizeof(cl_uint), &h_num_pairs2); | |
| 690 | clSetKernelArg(k_mi_filter, 3, sizeof(cl_double), &mi_thresh); | |
| 691 | clSetKernelArg(k_mi_filter, 4, sizeof(cl_mem), &d_filt_idx); | |
| 692 | clSetKernelArg(k_mi_filter, 5, sizeof(cl_mem), &d_filt_mi); | |
| 693 | clSetKernelArg(k_mi_filter, 6, sizeof(cl_mem), &d_filt_cnt); | |
| 694 | clSetKernelArg(k_mi_filter, 7, sizeof(cl_uint), &max_output); | |
| 695 | ||
| 696 | gs = ((h_num_pairs2 + local_size - 1) / local_size) * local_size; | |
| 697 | clEnqueueNDRangeKernel(queue, k_mi_filter, 1, NULL, | |
| 698 | &gs, &local_size, 0, NULL, NULL); | |
| 699 | clFinish(queue); | |
| 700 | ||
| 701 | uint32_t filt_count; | |
| 702 | clEnqueueReadBuffer(queue, d_filt_cnt, CL_TRUE, 0, | |
| 703 | sizeof(uint32_t), &filt_count, 0, NULL, NULL); | |
| 704 | ||
| 705 | printf(" Pairs with MI > %.1f: %u (expected %u)\n", | |
| 706 | mi_thresh, filt_count, cnt_at); | |
| 707 | ||
| 708 | if (filt_count > 0 && filt_count <= max_output) { | |
| 709 | uint32_t* f_idx = malloc(sizeof(uint32_t) * filt_count); | |
| 710 | double* f_mi = malloc(sizeof(double) * filt_count); | |
| 711 | clEnqueueReadBuffer(queue, d_filt_idx, CL_TRUE, 0, | |
| 712 | sizeof(uint32_t) * filt_count, f_idx, 0, NULL, NULL); | |
| 713 | clEnqueueReadBuffer(queue, d_filt_mi, CL_TRUE, 0, | |
| 714 | sizeof(double) * filt_count, f_mi, 0, NULL, NULL); | |
| 715 | ||
| 716 | printf(" Filtered pairs:\n"); | |
| 717 | for (uint32_t i = 0; i < filt_count; i++) { | |
| 718 | printf(" pair[%u] MI=%.4f\n", f_idx[i], f_mi[i]); | |
| 719 | } | |
| 720 | free(f_idx); free(f_mi); | |
| 721 | } | |
| 722 | ||
| 723 | int t5_pass = (filt_count == cnt_at); | |
| 724 | printf(" %s\n\n", t5_pass ? "PASS" : "FAIL"); | |
| 725 | if (t5_pass) pass_count++; else fail_count++; | |
| 726 | ||
| 727 | clReleaseMemObject(d_filt_idx); | |
| 728 | clReleaseMemObject(d_filt_mi); | |
| 729 | clReleaseMemObject(d_filt_cnt); | |
| 730 | ||
| 731 | /* ═══════════════════════════════════════════════════════════════ | |
| 732 | * TEST 6: Benchmark — 1000 sentences → MI | |
| 733 | * | |
| 734 | * Full pipeline: count 1000 sentences then compute MI. | |
| 735 | * Measures the end-to-end time with zero CPU↔GPU transfers | |
| 736 | * between stages. | |
| 737 | * ═══════════════════════════════════════════════════════════════ */ | |
| 738 | ||
| 739 | printf("--- Test 6: Benchmark (1000 sentences → MI) ---\n"); | |
| 740 | ||
| 741 | reset_pools(queue, pht_keys, pht_values, pair_count, pair_mi, | |
| 742 | pair_flags, word_count, pair_next_free, total_pair_count); | |
| 743 | ||
| 744 | /* Generate 1000 sentences */ | |
| 745 | int bench_ns = 1000; | |
| 746 | uint32_t* bench_offsets = malloc(sizeof(uint32_t) * bench_ns); | |
| 747 | uint32_t* bench_lengths = malloc(sizeof(uint32_t) * bench_ns); | |
| 748 | ||
| 749 | uint64_t rng = 0xCAFEBABEDEADBEEFULL; | |
| 750 | uint32_t bench_tw = 0; | |
| 751 | for (int s = 0; s < bench_ns; s++) { | |
| 752 | bench_offsets[s] = bench_tw; | |
| 753 | rng += 0x9E3779B97F4A7C15ULL; | |
| 754 | bench_lengths[s] = 5 + (uint32_t)((rng >> 32) % 16); | |
| 755 | bench_tw += bench_lengths[s]; | |
| 756 | } | |
| 757 | ||
| 758 | uint32_t* bench_words = malloc(sizeof(uint32_t) * bench_tw); | |
| 759 | rng = 0xFEEDFACE12345678ULL; | |
| 760 | for (uint32_t i = 0; i < bench_tw; i++) { | |
| 761 | rng += 0x9E3779B97F4A7C15ULL; | |
| 762 | double u = (double)(rng >> 32) / (double)0xFFFFFFFFU; | |
| 763 | bench_words[i] = (uint32_t)(u * u * 499.0); | |
| 764 | } | |
| 765 | ||
| 766 | printf(" Sentences: %d, Total words: %u\n", bench_ns, bench_tw); | |
| 767 | ||
| 768 | clReleaseMemObject(d_flat_words); | |
| 769 | clReleaseMemObject(d_sent_offsets); | |
| 770 | clReleaseMemObject(d_sent_lengths); | |
| 771 | ||
| 772 | d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 773 | sizeof(uint32_t) * bench_tw, bench_words, &err); | |
| 774 | d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 775 | sizeof(uint32_t) * bench_ns, bench_offsets, &err); | |
| 776 | d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 777 | sizeof(uint32_t) * bench_ns, bench_lengths, &err); | |
| 778 | ||
| 779 | cl_uint bns = bench_ns; | |
| 780 | cl_uint bench_window = 6; | |
| 781 | ||
| 782 | clSetKernelArg(k_count, 0, sizeof(cl_mem), &d_flat_words); | |
| 783 | clSetKernelArg(k_count, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 784 | clSetKernelArg(k_count, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 785 | clSetKernelArg(k_count, 3, sizeof(cl_uint), &bns); | |
| 786 | clSetKernelArg(k_count, 4, sizeof(cl_uint), &bench_tw); | |
| 787 | clSetKernelArg(k_count, 5, sizeof(cl_uint), &bench_window); | |
| 788 | clSetKernelArg(k_count, 6, sizeof(cl_mem), &pht_keys); | |
| 789 | clSetKernelArg(k_count, 7, sizeof(cl_mem), &pht_values); | |
| 790 | clSetKernelArg(k_count, 8, sizeof(cl_mem), &pair_word_a); | |
| 791 | clSetKernelArg(k_count, 9, sizeof(cl_mem), &pair_word_b); | |
| 792 | clSetKernelArg(k_count, 10, sizeof(cl_mem), &pair_count); | |
| 793 | clSetKernelArg(k_count, 11, sizeof(cl_mem), &pair_mi); | |
| 794 | clSetKernelArg(k_count, 12, sizeof(cl_mem), &pair_flags); | |
| 795 | clSetKernelArg(k_count, 13, sizeof(cl_mem), &pair_next_free); | |
| 796 | clSetKernelArg(k_count, 14, sizeof(cl_mem), &word_count); | |
| 797 | clSetKernelArg(k_count, 15, sizeof(cl_mem), &total_pair_count); | |
| 798 | ||
| 799 | /* Stage 1: Count */ | |
| 800 | t0 = now_ms(); | |
| 801 | gs = ((bench_tw + local_size - 1) / local_size) * local_size; | |
| 802 | clEnqueueNDRangeKernel(queue, k_count, 1, NULL, | |
| 803 | &gs, &local_size, 0, NULL, NULL); | |
| 804 | clFinish(queue); | |
| 805 | t1 = now_ms(); | |
| 806 | double count_ms = t1 - t0; | |
| 807 | ||
| 808 | clEnqueueReadBuffer(queue, pair_next_free, CL_TRUE, 0, | |
| 809 | sizeof(uint32_t), &h_num_pairs, 0, NULL, NULL); | |
| 810 | clEnqueueReadBuffer(queue, total_pair_count, CL_TRUE, 0, | |
| 811 | sizeof(uint32_t), &h_total, 0, NULL, NULL); | |
| 812 | ||
| 813 | printf(" Count: %u pairs, %u events in %.2f ms\n", | |
| 814 | h_num_pairs, h_total, count_ms); | |
| 815 | ||
| 816 | /* Stage 2: MI on all pairs */ | |
| 817 | n = (double)h_total; | |
| 818 | clSetKernelArg(k_mi_all, 5, sizeof(cl_double), &n); | |
| 819 | clSetKernelArg(k_mi_all, 6, sizeof(cl_uint), &h_num_pairs); | |
| 820 | ||
| 821 | t0 = now_ms(); | |
| 822 | gs = ((h_num_pairs + local_size - 1) / local_size) * local_size; | |
| 823 | clEnqueueNDRangeKernel(queue, k_mi_all, 1, NULL, | |
| 824 | &gs, &local_size, 0, NULL, NULL); | |
| 825 | clFinish(queue); | |
| 826 | t1 = now_ms(); | |
| 827 | double mi_ms = t1 - t0; | |
| 828 | ||
| 829 | printf(" MI: %u pairs in %.2f ms (%.1f M pairs/sec)\n", | |
| 830 | h_num_pairs, mi_ms, | |
| 831 | h_num_pairs / (mi_ms / 1000.0) / 1e6); | |
| 832 | ||
| 833 | /* Stage 3: MI stats */ | |
| 834 | cl_mem d_s_nz = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 835 | sizeof(uint32_t), &zero, &err); | |
| 836 | cl_mem d_s_pos = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 837 | sizeof(uint32_t), &zero, &err); | |
| 838 | cl_mem d_s_at = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 839 | sizeof(uint32_t), &zero, &err); | |
| 840 | ||
| 841 | threshold = 1.0; | |
| 842 | clSetKernelArg(k_mi_stats, 0, sizeof(cl_mem), &pair_mi); | |
| 843 | clSetKernelArg(k_mi_stats, 1, sizeof(cl_mem), &pair_count); | |
| 844 | clSetKernelArg(k_mi_stats, 2, sizeof(cl_uint), &h_num_pairs); | |
| 845 | clSetKernelArg(k_mi_stats, 3, sizeof(cl_double), &threshold); | |
| 846 | clSetKernelArg(k_mi_stats, 4, sizeof(cl_mem), &d_s_nz); | |
| 847 | clSetKernelArg(k_mi_stats, 5, sizeof(cl_mem), &d_s_pos); | |
| 848 | clSetKernelArg(k_mi_stats, 6, sizeof(cl_mem), &d_s_at); | |
| 849 | ||
| 850 | t0 = now_ms(); | |
| 851 | clEnqueueNDRangeKernel(queue, k_mi_stats, 1, NULL, | |
| 852 | &gs, &local_size, 0, NULL, NULL); | |
| 853 | clFinish(queue); | |
| 854 | double stats_ms = now_ms() - t0; | |
| 855 | ||
| 856 | clEnqueueReadBuffer(queue, d_s_nz, CL_TRUE, 0, sizeof(uint32_t), &cnt_nz, 0, NULL, NULL); | |
| 857 | clEnqueueReadBuffer(queue, d_s_pos, CL_TRUE, 0, sizeof(uint32_t), &cnt_pos, 0, NULL, NULL); | |
| 858 | clEnqueueReadBuffer(queue, d_s_at, CL_TRUE, 0, sizeof(uint32_t), &cnt_at, 0, NULL, NULL); | |
| 859 | ||
| 860 | printf(" Stats: %u nonzero, %u positive MI, %u MI>%.1f in %.2f ms\n", | |
| 861 | cnt_nz, cnt_pos, cnt_at, threshold, stats_ms); | |
| 862 | ||
| 863 | /* Stage 4: Dirty MI (incremental — count another batch, recompute dirty only) */ | |
| 864 | ||
| 865 | /* Reset only the counting state, keep existing MI values */ | |
| 866 | /* First record current pair count */ | |
| 867 | uint32_t pairs_before = h_num_pairs; | |
| 868 | ||
| 869 | /* Count same sentences again (adds to counts, marks dirty) */ | |
| 870 | clSetKernelArg(k_count, 3, sizeof(cl_uint), &bns); | |
| 871 | clSetKernelArg(k_count, 4, sizeof(cl_uint), &bench_tw); | |
| 872 | ||
| 873 | t0 = now_ms(); | |
| 874 | gs = ((bench_tw + local_size - 1) / local_size) * local_size; | |
| 875 | clEnqueueNDRangeKernel(queue, k_count, 1, NULL, | |
| 876 | &gs, &local_size, 0, NULL, NULL); | |
| 877 | clFinish(queue); | |
| 878 | double count2_ms = now_ms() - t0; | |
| 879 | ||
| 880 | clEnqueueReadBuffer(queue, pair_next_free, CL_TRUE, 0, | |
| 881 | sizeof(uint32_t), &h_num_pairs, 0, NULL, NULL); | |
| 882 | clEnqueueReadBuffer(queue, total_pair_count, CL_TRUE, 0, | |
| 883 | sizeof(uint32_t), &h_total, 0, NULL, NULL); | |
| 884 | ||
| 885 | printf(" Count (batch 2): %u pairs, %u events in %.2f ms\n", | |
| 886 | h_num_pairs, h_total, count2_ms); | |
| 887 | printf(" New pairs: %u (existing reused: %u)\n", | |
| 888 | h_num_pairs - pairs_before, pairs_before); | |
| 889 | ||
| 890 | /* Dirty MI recompute */ | |
| 891 | n = (double)h_total; | |
| 892 | clSetKernelArg(k_mi_dirty, 6, sizeof(cl_double), &n); | |
| 893 | clSetKernelArg(k_mi_dirty, 7, sizeof(cl_uint), &h_num_pairs); | |
| 894 | ||
| 895 | t0 = now_ms(); | |
| 896 | gs = ((h_num_pairs + local_size - 1) / local_size) * local_size; | |
| 897 | clEnqueueNDRangeKernel(queue, k_mi_dirty, 1, NULL, | |
| 898 | &gs, &local_size, 0, NULL, NULL); | |
| 899 | clFinish(queue); | |
| 900 | double dirty_ms = now_ms() - t0; | |
| 901 | ||
| 902 | printf(" Dirty MI: %u pairs scanned in %.2f ms\n", h_num_pairs, dirty_ms); | |
| 903 | printf("\n === Pipeline Summary ===\n"); | |
| 904 | printf(" Count (1000 sent): %.2f ms\n", count_ms); | |
| 905 | printf(" MI (all %u pairs): %.2f ms\n", pairs_before, mi_ms); | |
| 906 | printf(" Stats: %.2f ms\n", stats_ms); | |
| 907 | printf(" Count (batch 2): %.2f ms\n", count2_ms); | |
| 908 | printf(" MI (dirty only): %.2f ms\n", dirty_ms); | |
| 909 | printf(" Total pipeline: %.2f ms\n", | |
| 910 | count_ms + mi_ms + stats_ms + count2_ms + dirty_ms); | |
| 911 | printf(" CPU↔GPU transfers: 0 (all data GPU-resident)\n"); | |
| 912 | ||
| 913 | int t6_pass = (h_num_pairs > 0) && (h_total > 0) && (cnt_nz > 0); | |
| 914 | printf(" %s\n\n", t6_pass ? "PASS" : "FAIL"); | |
| 915 | if (t6_pass) pass_count++; else fail_count++; | |
| 916 | ||
| 917 | clReleaseMemObject(d_s_nz); | |
| 918 | clReleaseMemObject(d_s_pos); | |
| 919 | clReleaseMemObject(d_s_at); | |
| 920 | ||
| 921 | /* ═══ Summary ═══ */ | |
| 922 | ||
| 923 | printf("=== Results: %d PASS, %d FAIL ===\n", pass_count, fail_count); | |
| 924 | ||
| 925 | /* ─── Cleanup ─── */ | |
| 926 | ||
| 927 | clReleaseMemObject(pht_keys); | |
| 928 | clReleaseMemObject(pht_values); | |
| 929 | clReleaseMemObject(pair_word_a); | |
| 930 | clReleaseMemObject(pair_word_b); | |
| 931 | clReleaseMemObject(pair_count); | |
| 932 | clReleaseMemObject(pair_mi); | |
| 933 | clReleaseMemObject(pair_flags); | |
| 934 | clReleaseMemObject(pair_next_free); | |
| 935 | clReleaseMemObject(word_count); | |
| 936 | clReleaseMemObject(total_pair_count); | |
| 937 | clReleaseMemObject(d_flat_words); | |
| 938 | clReleaseMemObject(d_sent_offsets); | |
| 939 | clReleaseMemObject(d_sent_lengths); | |
| 940 | ||
| 941 | clReleaseKernel(k_count); | |
| 942 | clReleaseKernel(k_mi_all); | |
| 943 | clReleaseKernel(k_mi_dirty); | |
| 944 | clReleaseKernel(k_mi_stats); | |
| 945 | clReleaseKernel(k_mi_filter); | |
| 946 | clReleaseKernel(k_read_mi); | |
| 947 | clReleaseProgram(program); | |
| 948 | clReleaseCommandQueue(queue); | |
| 949 | clReleaseContext(ctx); | |
| 950 | ||
| 951 | free(bench_offsets); | |
| 952 | free(bench_lengths); | |
| 953 | free(bench_words); | |
| 954 | free(combined); | |
| 955 | free(ht_src); | |
| 956 | free(as_src); | |
| 957 | free(ct_src); | |
| 958 | free(mi_src); | |
| 959 | ||
| 960 | return fail_count > 0 ? 1 : 0; | |
| 961 | } |
| … | … | @@ -0,0 +1,883 @@ |
| 1 | /* | |
| 2 | * test-sections.c -- Test GPU section extraction kernel | |
| 3 | * | |
| 4 | * Compile: gcc -O2 -o test-sections test-sections.c -lOpenCL -lm | |
| 5 | * Run: ./test-sections | |
| 6 | * | |
| 7 | * Tests: | |
| 8 | * 1. Simple MST: 3-word sentence with 2 edges → 3 sections | |
| 9 | * 2. Star parse: all edges from one root → verify disjuncts | |
| 10 | * 3. Multi-sentence batch (no cross-boundary sections) | |
| 11 | * 4. Duplicate disjuncts: same parse seen twice → counts accumulate | |
| 12 | * 5. Readback kernel verification | |
| 13 | * 6. Benchmark: 1000 sentences with random MST edges | |
| 14 | */ | |
| 15 | ||
| 16 | #include <stdio.h> | |
| 17 | #include <stdlib.h> | |
| 18 | #include <stdint.h> | |
| 19 | #include <string.h> | |
| 20 | #include <math.h> | |
| 21 | #include <time.h> | |
| 22 | #include <CL/cl.h> | |
| 23 | ||
| 24 | /* ─── Pool capacities ─── */ | |
| 25 | ||
| 26 | #define WORD_CAPACITY (128 * 1024) | |
| 27 | #define PAIR_CAPACITY (4 * 1024 * 1024) | |
| 28 | #define SECTION_CAPACITY (1024 * 1024) | |
| 29 | #define WORD_HT_CAPACITY (256 * 1024) | |
| 30 | #define PAIR_HT_CAPACITY (8 * 1024 * 1024) | |
| 31 | #define SECTION_HT_CAPACITY (2 * 1024 * 1024) | |
| 32 | ||
| 33 | #define HT_EMPTY_KEY 0xFFFFFFFFFFFFFFFFULL | |
| 34 | #define HT_EMPTY_VALUE 0xFFFFFFFFU | |
| 35 | ||
| 36 | /* ─── Error checking ─── */ | |
| 37 | ||
| 38 | #define CL_CHECK(err, msg) do { \ | |
| 39 | if ((err) != CL_SUCCESS) { \ | |
| 40 | fprintf(stderr, "OpenCL error %d at %s:%d: %s\n", \ | |
| 41 | (err), __FILE__, __LINE__, (msg)); \ | |
| 42 | exit(1); \ | |
| 43 | } \ | |
| 44 | } while(0) | |
| 45 | ||
| 46 | /* ─── Read file ─── */ | |
| 47 | ||
| 48 | char* read_file(const char* path, size_t* len) | |
| 49 | { | |
| 50 | FILE* f = fopen(path, "r"); | |
| 51 | if (!f) { fprintf(stderr, "Cannot open %s\n", path); exit(1); } | |
| 52 | fseek(f, 0, SEEK_END); | |
| 53 | *len = ftell(f); | |
| 54 | fseek(f, 0, SEEK_SET); | |
| 55 | char* buf = malloc(*len + 1); | |
| 56 | size_t n = fread(buf, 1, *len, f); | |
| 57 | buf[n] = '\0'; | |
| 58 | *len = n; | |
| 59 | fclose(f); | |
| 60 | return buf; | |
| 61 | } | |
| 62 | ||
| 63 | /* ─── Timing ─── */ | |
| 64 | ||
| 65 | double now_ms(void) | |
| 66 | { | |
| 67 | struct timespec ts; | |
| 68 | clock_gettime(CLOCK_MONOTONIC, &ts); | |
| 69 | return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0; | |
| 70 | } | |
| 71 | ||
| 72 | /* ─── CPU-side disjunct hash (must match GPU) ─── */ | |
| 73 | ||
| 74 | static uint64_t cpu_fnv1a_init(void) { return 0xcbf29ce484222325ULL; } | |
| 75 | ||
| 76 | static uint64_t cpu_fnv1a_mix(uint64_t hash, uint64_t val) | |
| 77 | { | |
| 78 | hash ^= val; | |
| 79 | hash *= 0x100000001b3ULL; | |
| 80 | return hash; | |
| 81 | } | |
| 82 | ||
| 83 | static uint64_t cpu_hash_disjunct(uint32_t* words, uint32_t* dirs, uint32_t count) | |
| 84 | { | |
| 85 | uint64_t h = cpu_fnv1a_init(); | |
| 86 | for (uint32_t i = 0; i < count; i++) { | |
| 87 | uint64_t encoded = ((uint64_t)words[i] << 1) | (uint64_t)dirs[i]; | |
| 88 | h = cpu_fnv1a_mix(h, encoded); | |
| 89 | } | |
| 90 | if (h == HT_EMPTY_KEY) h = 0; | |
| 91 | return h; | |
| 92 | } | |
| 93 | ||
| 94 | /* CPU-side section_key (must match GPU) */ | |
| 95 | static uint64_t cpu_section_key(uint32_t word_idx, uint64_t disjunct_hash) | |
| 96 | { | |
| 97 | uint64_t key = disjunct_hash ^ ((uint64_t)word_idx * 0x9E3779B97F4A7C15ULL); | |
| 98 | if (key == HT_EMPTY_KEY) key = 0; | |
| 99 | return key; | |
| 100 | } | |
| 101 | ||
| 102 | /* ─── Helper: reset section pool and hash table ─── */ | |
| 103 | ||
| 104 | void reset_section_pool(cl_command_queue queue, | |
| 105 | cl_mem sht_keys, cl_mem sht_values, | |
| 106 | cl_mem sec_count, | |
| 107 | cl_mem sec_next_free, cl_mem total_sections) | |
| 108 | { | |
| 109 | uint8_t pat_ff = 0xFF; | |
| 110 | uint8_t pat_00 = 0x00; | |
| 111 | uint32_t zero = 0; | |
| 112 | ||
| 113 | clEnqueueFillBuffer(queue, sht_keys, &pat_ff, 1, 0, | |
| 114 | sizeof(uint64_t) * SECTION_HT_CAPACITY, 0, NULL, NULL); | |
| 115 | clEnqueueFillBuffer(queue, sht_values, &pat_ff, 1, 0, | |
| 116 | sizeof(uint32_t) * SECTION_HT_CAPACITY, 0, NULL, NULL); | |
| 117 | clEnqueueFillBuffer(queue, sec_count, &pat_00, 1, 0, | |
| 118 | sizeof(double) * SECTION_CAPACITY, 0, NULL, NULL); | |
| 119 | clEnqueueWriteBuffer(queue, sec_next_free, CL_FALSE, 0, | |
| 120 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 121 | clEnqueueWriteBuffer(queue, total_sections, CL_FALSE, 0, | |
| 122 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 123 | clFinish(queue); | |
| 124 | } | |
| 125 | ||
| 126 | /* ─── Main ─── */ | |
| 127 | ||
| 128 | int main(int argc, char** argv) | |
| 129 | { | |
| 130 | cl_int err; | |
| 131 | int pass_count = 0, fail_count = 0; | |
| 132 | ||
| 133 | printf("=== GPU Section Extraction Test ===\n\n"); | |
| 134 | ||
| 135 | /* ─── OpenCL setup ─── */ | |
| 136 | ||
| 137 | cl_platform_id platform; | |
| 138 | err = clGetPlatformIDs(1, &platform, NULL); | |
| 139 | CL_CHECK(err, "platform"); | |
| 140 | ||
| 141 | cl_device_id device; | |
| 142 | err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL); | |
| 143 | CL_CHECK(err, "device"); | |
| 144 | ||
| 145 | char dev_name[256]; | |
| 146 | clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(dev_name), dev_name, NULL); | |
| 147 | printf("GPU: %s\n", dev_name); | |
| 148 | ||
| 149 | cl_context ctx = clCreateContext(NULL, 1, &device, NULL, NULL, &err); | |
| 150 | CL_CHECK(err, "context"); | |
| 151 | ||
| 152 | cl_command_queue queue = clCreateCommandQueue(ctx, device, 0, &err); | |
| 153 | CL_CHECK(err, "queue"); | |
| 154 | ||
| 155 | /* ─── Load and concatenate kernel sources ─── */ | |
| 156 | ||
| 157 | size_t ht_len, as_len, sc_len; | |
| 158 | char* ht_src = read_file("gpu-hashtable.cl", &ht_len); | |
| 159 | char* as_src = read_file("gpu-atomspace.cl", &as_len); | |
| 160 | char* sc_src = read_file("gpu-sections.cl", &sc_len); | |
| 161 | ||
| 162 | size_t total_len = ht_len + 1 + as_len + 1 + sc_len; | |
| 163 | char* combined = malloc(total_len + 1); | |
| 164 | memcpy(combined, ht_src, ht_len); | |
| 165 | combined[ht_len] = '\n'; | |
| 166 | memcpy(combined + ht_len + 1, as_src, as_len); | |
| 167 | combined[ht_len + 1 + as_len] = '\n'; | |
| 168 | memcpy(combined + ht_len + 1 + as_len + 1, sc_src, sc_len); | |
| 169 | combined[total_len] = '\0'; | |
| 170 | ||
| 171 | cl_program program = clCreateProgramWithSource(ctx, 1, | |
| 172 | (const char**)&combined, &total_len, &err); | |
| 173 | CL_CHECK(err, "create program"); | |
| 174 | ||
| 175 | char build_opts[512]; | |
| 176 | snprintf(build_opts, sizeof(build_opts), | |
| 177 | "-cl-std=CL1.2 " | |
| 178 | "-DWORD_CAPACITY=%d " | |
| 179 | "-DPAIR_CAPACITY=%d " | |
| 180 | "-DSECTION_CAPACITY=%d " | |
| 181 | "-DWORD_HT_CAPACITY=%d " | |
| 182 | "-DPAIR_HT_CAPACITY=%d " | |
| 183 | "-DSECTION_HT_CAPACITY=%d", | |
| 184 | WORD_CAPACITY, PAIR_CAPACITY, SECTION_CAPACITY, | |
| 185 | WORD_HT_CAPACITY, PAIR_HT_CAPACITY, SECTION_HT_CAPACITY); | |
| 186 | ||
| 187 | err = clBuildProgram(program, 1, &device, build_opts, NULL, NULL); | |
| 188 | if (err != CL_SUCCESS) { | |
| 189 | char log[16384]; | |
| 190 | clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, | |
| 191 | sizeof(log), log, NULL); | |
| 192 | fprintf(stderr, "Build error:\n%s\n", log); | |
| 193 | return 1; | |
| 194 | } | |
| 195 | printf("Kernels compiled successfully\n\n"); | |
| 196 | ||
| 197 | /* ─── Create kernels ─── */ | |
| 198 | ||
| 199 | cl_kernel k_extract = clCreateKernel(program, "extract_sections", &err); | |
| 200 | CL_CHECK(err, "kernel extract_sections"); | |
| 201 | cl_kernel k_read = clCreateKernel(program, "read_sections", &err); | |
| 202 | CL_CHECK(err, "kernel read_sections"); | |
| 203 | ||
| 204 | size_t local_size = 256; | |
| 205 | ||
| 206 | /* ─── Allocate GPU buffers ─── */ | |
| 207 | ||
| 208 | printf("Allocating GPU buffers...\n"); | |
| 209 | uint32_t zero = 0; | |
| 210 | ||
| 211 | /* Section hash table */ | |
| 212 | cl_mem sht_keys = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 213 | sizeof(uint64_t) * SECTION_HT_CAPACITY, NULL, &err); | |
| 214 | CL_CHECK(err, "sht_keys"); | |
| 215 | cl_mem sht_values = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 216 | sizeof(uint32_t) * SECTION_HT_CAPACITY, NULL, &err); | |
| 217 | CL_CHECK(err, "sht_values"); | |
| 218 | ||
| 219 | /* Section pool SoA */ | |
| 220 | cl_mem sec_word = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 221 | sizeof(uint32_t) * SECTION_CAPACITY, NULL, &err); | |
| 222 | cl_mem sec_disjunct_hash = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 223 | sizeof(uint64_t) * SECTION_CAPACITY, NULL, &err); | |
| 224 | cl_mem sec_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 225 | sizeof(double) * SECTION_CAPACITY, NULL, &err); | |
| 226 | ||
| 227 | /* Section bump allocator */ | |
| 228 | cl_mem sec_next_free = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 229 | sizeof(uint32_t), &zero, &err); | |
| 230 | ||
| 231 | /* Total sections created (stats counter) */ | |
| 232 | cl_mem total_sections = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 233 | sizeof(uint32_t), &zero, &err); | |
| 234 | ||
| 235 | /* Initial reset */ | |
| 236 | reset_section_pool(queue, sht_keys, sht_values, sec_count, | |
| 237 | sec_next_free, total_sections); | |
| 238 | ||
| 239 | printf("GPU buffers ready\n\n"); | |
| 240 | ||
| 241 | /* ═══════════════════════════════════════════════════════════════ | |
| 242 | * TEST 1: Simple MST — 3-word sentence with chain parse | |
| 243 | * | |
| 244 | * Sentence: words [10, 20, 30] (word pool indices) | |
| 245 | * MST edges: (0,1), (1,2) — chain: 10—20—30 | |
| 246 | * | |
| 247 | * Expected sections: | |
| 248 | * Word 10 (pos 0): connectors = [(20, RIGHT)] | |
| 249 | * disjunct = "20+" | |
| 250 | * Word 20 (pos 1): connectors = [(10, LEFT), (30, RIGHT)] | |
| 251 | * disjunct = "10- 30+" | |
| 252 | * Word 30 (pos 2): connectors = [(20, LEFT)] | |
| 253 | * disjunct = "20-" | |
| 254 | * | |
| 255 | * = 3 unique sections | |
| 256 | * ═══════════════════════════════════════════════════════════════ */ | |
| 257 | ||
| 258 | printf("--- Test 1: Simple chain parse (3 words, 2 edges) ---\n"); | |
| 259 | ||
| 260 | uint32_t t1_words[] = {10, 20, 30}; | |
| 261 | uint32_t t1_sent_offsets[] = {0}; | |
| 262 | uint32_t t1_sent_lengths[] = {3}; | |
| 263 | uint32_t t1_edge_p1[] = {0, 1}; | |
| 264 | uint32_t t1_edge_p2[] = {1, 2}; | |
| 265 | uint32_t t1_edge_offsets[] = {0}; | |
| 266 | uint32_t t1_edge_counts[] = {2}; | |
| 267 | cl_uint t1_ns = 1, t1_tw = 3; | |
| 268 | ||
| 269 | cl_mem d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 270 | sizeof(uint32_t) * t1_tw, t1_words, &err); | |
| 271 | cl_mem d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 272 | sizeof(uint32_t) * t1_ns, t1_sent_offsets, &err); | |
| 273 | cl_mem d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 274 | sizeof(uint32_t) * t1_ns, t1_sent_lengths, &err); | |
| 275 | cl_mem d_edge_p1 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 276 | sizeof(uint32_t) * 2, t1_edge_p1, &err); | |
| 277 | cl_mem d_edge_p2 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 278 | sizeof(uint32_t) * 2, t1_edge_p2, &err); | |
| 279 | cl_mem d_edge_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 280 | sizeof(uint32_t) * t1_ns, t1_edge_offsets, &err); | |
| 281 | cl_mem d_edge_counts = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 282 | sizeof(uint32_t) * t1_ns, t1_edge_counts, &err); | |
| 283 | ||
| 284 | /* Set kernel args */ | |
| 285 | clSetKernelArg(k_extract, 0, sizeof(cl_mem), &d_flat_words); | |
| 286 | clSetKernelArg(k_extract, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 287 | clSetKernelArg(k_extract, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 288 | clSetKernelArg(k_extract, 3, sizeof(cl_uint), &t1_ns); | |
| 289 | clSetKernelArg(k_extract, 4, sizeof(cl_uint), &t1_tw); | |
| 290 | clSetKernelArg(k_extract, 5, sizeof(cl_mem), &d_edge_p1); | |
| 291 | clSetKernelArg(k_extract, 6, sizeof(cl_mem), &d_edge_p2); | |
| 292 | clSetKernelArg(k_extract, 7, sizeof(cl_mem), &d_edge_offsets); | |
| 293 | clSetKernelArg(k_extract, 8, sizeof(cl_mem), &d_edge_counts); | |
| 294 | clSetKernelArg(k_extract, 9, sizeof(cl_mem), &sht_keys); | |
| 295 | clSetKernelArg(k_extract, 10, sizeof(cl_mem), &sht_values); | |
| 296 | clSetKernelArg(k_extract, 11, sizeof(cl_mem), &sec_word); | |
| 297 | clSetKernelArg(k_extract, 12, sizeof(cl_mem), &sec_disjunct_hash); | |
| 298 | clSetKernelArg(k_extract, 13, sizeof(cl_mem), &sec_count); | |
| 299 | clSetKernelArg(k_extract, 14, sizeof(cl_mem), &sec_next_free); | |
| 300 | clSetKernelArg(k_extract, 15, sizeof(cl_mem), &total_sections); | |
| 301 | ||
| 302 | double t0 = now_ms(); | |
| 303 | size_t gs = ((t1_tw + local_size - 1) / local_size) * local_size; | |
| 304 | err = clEnqueueNDRangeKernel(queue, k_extract, 1, NULL, | |
| 305 | &gs, &local_size, 0, NULL, NULL); | |
| 306 | CL_CHECK(err, "enqueue extract"); | |
| 307 | clFinish(queue); | |
| 308 | double t1 = now_ms(); | |
| 309 | ||
| 310 | /* Read back results */ | |
| 311 | uint32_t h_num_sections, h_total_created; | |
| 312 | clEnqueueReadBuffer(queue, sec_next_free, CL_TRUE, 0, | |
| 313 | sizeof(uint32_t), &h_num_sections, 0, NULL, NULL); | |
| 314 | clEnqueueReadBuffer(queue, total_sections, CL_TRUE, 0, | |
| 315 | sizeof(uint32_t), &h_total_created, 0, NULL, NULL); | |
| 316 | ||
| 317 | /* Read section data */ | |
| 318 | uint32_t h_sec_words[8]; | |
| 319 | uint64_t h_sec_djh[8]; | |
| 320 | double h_sec_counts[8]; | |
| 321 | clEnqueueReadBuffer(queue, sec_word, CL_TRUE, 0, | |
| 322 | sizeof(uint32_t) * h_num_sections, h_sec_words, 0, NULL, NULL); | |
| 323 | clEnqueueReadBuffer(queue, sec_disjunct_hash, CL_TRUE, 0, | |
| 324 | sizeof(uint64_t) * h_num_sections, h_sec_djh, 0, NULL, NULL); | |
| 325 | clEnqueueReadBuffer(queue, sec_count, CL_TRUE, 0, | |
| 326 | sizeof(double) * h_num_sections, h_sec_counts, 0, NULL, NULL); | |
| 327 | ||
| 328 | /* Compute expected disjunct hashes on CPU */ | |
| 329 | /* Word 10 (pos 0): [(20, RIGHT=1)] */ | |
| 330 | uint32_t cw0[] = {20}; uint32_t cd0[] = {1}; | |
| 331 | uint64_t exp_djh_0 = cpu_hash_disjunct(cw0, cd0, 1); | |
| 332 | /* Word 20 (pos 1): [(10, LEFT=0), (30, RIGHT=1)] — already sorted */ | |
| 333 | uint32_t cw1[] = {10, 30}; uint32_t cd1[] = {0, 1}; | |
| 334 | uint64_t exp_djh_1 = cpu_hash_disjunct(cw1, cd1, 2); | |
| 335 | /* Word 30 (pos 2): [(20, LEFT=0)] */ | |
| 336 | uint32_t cw2[] = {20}; uint32_t cd2[] = {0}; | |
| 337 | uint64_t exp_djh_2 = cpu_hash_disjunct(cw2, cd2, 1); | |
| 338 | ||
| 339 | printf(" Sections created: %u (expected 3)\n", h_num_sections); | |
| 340 | printf(" Stats counter: %u (expected 3)\n", h_total_created); | |
| 341 | ||
| 342 | /* Verify each section exists with correct data */ | |
| 343 | int found_10 = 0, found_20 = 0, found_30 = 0; | |
| 344 | for (uint32_t i = 0; i < h_num_sections; i++) { | |
| 345 | if (h_sec_words[i] == 10 && h_sec_djh[i] == exp_djh_0 && | |
| 346 | fabs(h_sec_counts[i] - 1.0) < 0.01) found_10 = 1; | |
| 347 | if (h_sec_words[i] == 20 && h_sec_djh[i] == exp_djh_1 && | |
| 348 | fabs(h_sec_counts[i] - 1.0) < 0.01) found_20 = 1; | |
| 349 | if (h_sec_words[i] == 30 && h_sec_djh[i] == exp_djh_2 && | |
| 350 | fabs(h_sec_counts[i] - 1.0) < 0.01) found_30 = 1; | |
| 351 | } | |
| 352 | ||
| 353 | printf(" Section (word=10, djh=0x%016llx): %s\n", | |
| 354 | (unsigned long long)exp_djh_0, found_10 ? "found" : "MISSING"); | |
| 355 | printf(" Section (word=20, djh=0x%016llx): %s\n", | |
| 356 | (unsigned long long)exp_djh_1, found_20 ? "found" : "MISSING"); | |
| 357 | printf(" Section (word=30, djh=0x%016llx): %s\n", | |
| 358 | (unsigned long long)exp_djh_2, found_30 ? "found" : "MISSING"); | |
| 359 | printf(" Time: %.2f ms\n", t1 - t0); | |
| 360 | ||
| 361 | int t1_pass = (h_num_sections == 3) && (h_total_created == 3) && | |
| 362 | found_10 && found_20 && found_30; | |
| 363 | printf(" %s\n\n", t1_pass ? "PASS" : "FAIL"); | |
| 364 | if (t1_pass) pass_count++; else fail_count++; | |
| 365 | ||
| 366 | /* ═══════════════════════════════════════════════════════════════ | |
| 367 | * TEST 2: Star parse — root word connected to all others | |
| 368 | * | |
| 369 | * Sentence: words [100, 101, 102, 103, 104] (5 words) | |
| 370 | * MST edges: (2,0), (2,1), (2,3), (2,4) — word 102 is root | |
| 371 | * | |
| 372 | * Expected sections: | |
| 373 | * Word 100 (pos 0): [(102, RIGHT)] — 1 connector | |
| 374 | * Word 101 (pos 1): [(102, RIGHT)] — 1 connector | |
| 375 | * Word 102 (pos 2): [(100, LEFT), (101, LEFT), (103, RIGHT), (104, RIGHT)] | |
| 376 | * Word 103 (pos 3): [(102, LEFT)] — 1 connector | |
| 377 | * Word 104 (pos 4): [(102, LEFT)] — 1 connector | |
| 378 | * | |
| 379 | * = 5 sections (4 unique disjuncts: leaf-left, leaf-right, root-4conn) | |
| 380 | * But words 100 and 101 have same disjunct hash ONLY if their | |
| 381 | * partner word pool index is the same (both connect to 102) AND | |
| 382 | * direction is the same (both RIGHT). So disjunct hash matches! | |
| 383 | * → 100 and 101 have same disjunct but different words → 2 sections | |
| 384 | * Similarly 103 and 104 connect LEFT to 102 → same disjunct → 2 sections | |
| 385 | * | |
| 386 | * Total unique sections (word, disjunct) pairs: 5 | |
| 387 | * ═══════════════════════════════════════════════════════════════ */ | |
| 388 | ||
| 389 | printf("--- Test 2: Star parse (5 words, root at center) ---\n"); | |
| 390 | ||
| 391 | reset_section_pool(queue, sht_keys, sht_values, sec_count, | |
| 392 | sec_next_free, total_sections); | |
| 393 | ||
| 394 | uint32_t t2_words[] = {100, 101, 102, 103, 104}; | |
| 395 | uint32_t t2_sent_offsets[] = {0}; | |
| 396 | uint32_t t2_sent_lengths[] = {5}; | |
| 397 | uint32_t t2_edge_p1[] = {2, 2, 2, 2}; | |
| 398 | uint32_t t2_edge_p2[] = {0, 1, 3, 4}; | |
| 399 | uint32_t t2_edge_offsets[] = {0}; | |
| 400 | uint32_t t2_edge_counts[] = {4}; | |
| 401 | cl_uint t2_ns = 1, t2_tw = 5; | |
| 402 | ||
| 403 | clReleaseMemObject(d_flat_words); | |
| 404 | clReleaseMemObject(d_sent_offsets); | |
| 405 | clReleaseMemObject(d_sent_lengths); | |
| 406 | clReleaseMemObject(d_edge_p1); | |
| 407 | clReleaseMemObject(d_edge_p2); | |
| 408 | clReleaseMemObject(d_edge_offsets); | |
| 409 | clReleaseMemObject(d_edge_counts); | |
| 410 | ||
| 411 | d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 412 | sizeof(uint32_t) * t2_tw, t2_words, &err); | |
| 413 | d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 414 | sizeof(uint32_t) * t2_ns, t2_sent_offsets, &err); | |
| 415 | d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 416 | sizeof(uint32_t) * t2_ns, t2_sent_lengths, &err); | |
| 417 | d_edge_p1 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 418 | sizeof(uint32_t) * 4, t2_edge_p1, &err); | |
| 419 | d_edge_p2 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 420 | sizeof(uint32_t) * 4, t2_edge_p2, &err); | |
| 421 | d_edge_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 422 | sizeof(uint32_t) * t2_ns, t2_edge_offsets, &err); | |
| 423 | d_edge_counts = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 424 | sizeof(uint32_t) * t2_ns, t2_edge_counts, &err); | |
| 425 | ||
| 426 | /* Set kernel args */ | |
| 427 | clSetKernelArg(k_extract, 0, sizeof(cl_mem), &d_flat_words); | |
| 428 | clSetKernelArg(k_extract, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 429 | clSetKernelArg(k_extract, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 430 | clSetKernelArg(k_extract, 3, sizeof(cl_uint), &t2_ns); | |
| 431 | clSetKernelArg(k_extract, 4, sizeof(cl_uint), &t2_tw); | |
| 432 | clSetKernelArg(k_extract, 5, sizeof(cl_mem), &d_edge_p1); | |
| 433 | clSetKernelArg(k_extract, 6, sizeof(cl_mem), &d_edge_p2); | |
| 434 | clSetKernelArg(k_extract, 7, sizeof(cl_mem), &d_edge_offsets); | |
| 435 | clSetKernelArg(k_extract, 8, sizeof(cl_mem), &d_edge_counts); | |
| 436 | ||
| 437 | t0 = now_ms(); | |
| 438 | gs = ((t2_tw + local_size - 1) / local_size) * local_size; | |
| 439 | err = clEnqueueNDRangeKernel(queue, k_extract, 1, NULL, | |
| 440 | &gs, &local_size, 0, NULL, NULL); | |
| 441 | CL_CHECK(err, "enqueue extract t2"); | |
| 442 | clFinish(queue); | |
| 443 | t1 = now_ms(); | |
| 444 | ||
| 445 | clEnqueueReadBuffer(queue, sec_next_free, CL_TRUE, 0, | |
| 446 | sizeof(uint32_t), &h_num_sections, 0, NULL, NULL); | |
| 447 | clEnqueueReadBuffer(queue, total_sections, CL_TRUE, 0, | |
| 448 | sizeof(uint32_t), &h_total_created, 0, NULL, NULL); | |
| 449 | ||
| 450 | /* Compute expected disjunct for root word 102 (pos 2): | |
| 451 | * Connectors: (100, LEFT=0), (101, LEFT=0), (103, RIGHT=1), (104, RIGHT=1) | |
| 452 | * Sorted: dir 0 first sorted by word → (100,0), (101,0), (103,1), (104,1) */ | |
| 453 | uint32_t root_cw[] = {100, 101, 103, 104}; | |
| 454 | uint32_t root_cd[] = {0, 0, 1, 1}; | |
| 455 | uint64_t exp_root_djh = cpu_hash_disjunct(root_cw, root_cd, 4); | |
| 456 | ||
| 457 | /* Read back all sections */ | |
| 458 | uint32_t h2_sec_words[8]; | |
| 459 | uint64_t h2_sec_djh[8]; | |
| 460 | double h2_sec_counts[8]; | |
| 461 | uint32_t n_read = (h_num_sections < 8) ? h_num_sections : 8; | |
| 462 | clEnqueueReadBuffer(queue, sec_word, CL_TRUE, 0, | |
| 463 | sizeof(uint32_t) * n_read, h2_sec_words, 0, NULL, NULL); | |
| 464 | clEnqueueReadBuffer(queue, sec_disjunct_hash, CL_TRUE, 0, | |
| 465 | sizeof(uint64_t) * n_read, h2_sec_djh, 0, NULL, NULL); | |
| 466 | clEnqueueReadBuffer(queue, sec_count, CL_TRUE, 0, | |
| 467 | sizeof(double) * n_read, h2_sec_counts, 0, NULL, NULL); | |
| 468 | ||
| 469 | printf(" Sections created: %u (expected 5)\n", h_num_sections); | |
| 470 | ||
| 471 | /* Find root section */ | |
| 472 | int found_root = 0; | |
| 473 | for (uint32_t i = 0; i < n_read; i++) { | |
| 474 | if (h2_sec_words[i] == 102 && h2_sec_djh[i] == exp_root_djh && | |
| 475 | fabs(h2_sec_counts[i] - 1.0) < 0.01) { | |
| 476 | found_root = 1; | |
| 477 | } | |
| 478 | } | |
| 479 | printf(" Root section (word=102, 4 connectors): %s\n", | |
| 480 | found_root ? "found" : "MISSING"); | |
| 481 | printf(" Time: %.2f ms\n", t1 - t0); | |
| 482 | ||
| 483 | int t2_pass = (h_num_sections == 5) && found_root; | |
| 484 | printf(" %s\n\n", t2_pass ? "PASS" : "FAIL"); | |
| 485 | if (t2_pass) pass_count++; else fail_count++; | |
| 486 | ||
| 487 | /* ═══════════════════════════════════════════════════════════════ | |
| 488 | * TEST 3: Multi-sentence batch (no cross-boundary sections) | |
| 489 | * | |
| 490 | * Sentence 1: [10, 20, 30] edges: (0,1), (1,2) — chain | |
| 491 | * Sentence 2: [40, 50, 60] edges: (0,1), (0,2) — star from 40 | |
| 492 | * | |
| 493 | * flat_words = [10, 20, 30, 40, 50, 60] | |
| 494 | * flat_edges = [(0,1), (1,2), (0,1), (0,2)] | |
| 495 | * edge_offsets = [0, 2] | |
| 496 | * edge_counts = [2, 2] | |
| 497 | * | |
| 498 | * Expected: 6 sections (3 per sentence), none spanning boundary | |
| 499 | * ═══════════════════════════════════════════════════════════════ */ | |
| 500 | ||
| 501 | printf("--- Test 3: Multi-sentence batch ---\n"); | |
| 502 | ||
| 503 | reset_section_pool(queue, sht_keys, sht_values, sec_count, | |
| 504 | sec_next_free, total_sections); | |
| 505 | ||
| 506 | uint32_t t3_words[] = {10, 20, 30, 40, 50, 60}; | |
| 507 | uint32_t t3_sent_offsets[] = {0, 3}; | |
| 508 | uint32_t t3_sent_lengths[] = {3, 3}; | |
| 509 | uint32_t t3_edge_p1[] = {0, 1, 0, 0}; | |
| 510 | uint32_t t3_edge_p2[] = {1, 2, 1, 2}; | |
| 511 | uint32_t t3_edge_offsets[] = {0, 2}; | |
| 512 | uint32_t t3_edge_counts[] = {2, 2}; | |
| 513 | cl_uint t3_ns = 2, t3_tw = 6; | |
| 514 | ||
| 515 | clReleaseMemObject(d_flat_words); | |
| 516 | clReleaseMemObject(d_sent_offsets); | |
| 517 | clReleaseMemObject(d_sent_lengths); | |
| 518 | clReleaseMemObject(d_edge_p1); | |
| 519 | clReleaseMemObject(d_edge_p2); | |
| 520 | clReleaseMemObject(d_edge_offsets); | |
| 521 | clReleaseMemObject(d_edge_counts); | |
| 522 | ||
| 523 | d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 524 | sizeof(uint32_t) * t3_tw, t3_words, &err); | |
| 525 | d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 526 | sizeof(uint32_t) * t3_ns, t3_sent_offsets, &err); | |
| 527 | d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 528 | sizeof(uint32_t) * t3_ns, t3_sent_lengths, &err); | |
| 529 | d_edge_p1 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 530 | sizeof(uint32_t) * 4, t3_edge_p1, &err); | |
| 531 | d_edge_p2 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 532 | sizeof(uint32_t) * 4, t3_edge_p2, &err); | |
| 533 | d_edge_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 534 | sizeof(uint32_t) * t3_ns, t3_edge_offsets, &err); | |
| 535 | d_edge_counts = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 536 | sizeof(uint32_t) * t3_ns, t3_edge_counts, &err); | |
| 537 | ||
| 538 | clSetKernelArg(k_extract, 0, sizeof(cl_mem), &d_flat_words); | |
| 539 | clSetKernelArg(k_extract, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 540 | clSetKernelArg(k_extract, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 541 | clSetKernelArg(k_extract, 3, sizeof(cl_uint), &t3_ns); | |
| 542 | clSetKernelArg(k_extract, 4, sizeof(cl_uint), &t3_tw); | |
| 543 | clSetKernelArg(k_extract, 5, sizeof(cl_mem), &d_edge_p1); | |
| 544 | clSetKernelArg(k_extract, 6, sizeof(cl_mem), &d_edge_p2); | |
| 545 | clSetKernelArg(k_extract, 7, sizeof(cl_mem), &d_edge_offsets); | |
| 546 | clSetKernelArg(k_extract, 8, sizeof(cl_mem), &d_edge_counts); | |
| 547 | ||
| 548 | t0 = now_ms(); | |
| 549 | gs = ((t3_tw + local_size - 1) / local_size) * local_size; | |
| 550 | err = clEnqueueNDRangeKernel(queue, k_extract, 1, NULL, | |
| 551 | &gs, &local_size, 0, NULL, NULL); | |
| 552 | CL_CHECK(err, "enqueue extract t3"); | |
| 553 | clFinish(queue); | |
| 554 | t1 = now_ms(); | |
| 555 | ||
| 556 | clEnqueueReadBuffer(queue, sec_next_free, CL_TRUE, 0, | |
| 557 | sizeof(uint32_t), &h_num_sections, 0, NULL, NULL); | |
| 558 | ||
| 559 | /* Verify: sentence 1 chain gives 3 sections, sentence 2 star gives 3. | |
| 560 | * But sentence 1's word 10 has disjunct "20+" and sentence 2's word 50 | |
| 561 | * has disjunct "40-" — different disjuncts. All 6 words produce sections. | |
| 562 | * Are any (word, disjunct) pairs the same? No — different words, different | |
| 563 | * disjuncts. So 6 unique sections. */ | |
| 564 | printf(" Sections created: %u (expected 6)\n", h_num_sections); | |
| 565 | printf(" Time: %.2f ms\n", t1 - t0); | |
| 566 | ||
| 567 | int t3_pass = (h_num_sections == 6); | |
| 568 | printf(" %s\n\n", t3_pass ? "PASS" : "FAIL"); | |
| 569 | if (t3_pass) pass_count++; else fail_count++; | |
| 570 | ||
| 571 | /* ═══════════════════════════════════════════════════════════════ | |
| 572 | * TEST 4: Duplicate sections — same parse seen twice | |
| 573 | * | |
| 574 | * Process the SAME sentence twice → section counts should be 2.0 | |
| 575 | * | |
| 576 | * Sentence: [10, 20, 30] edges: (0,1), (1,2) | |
| 577 | * Run extract_sections TWICE without resetting. | |
| 578 | * | |
| 579 | * Expected: 3 sections, each with count = 2.0 | |
| 580 | * ═══════════════════════════════════════════════════════════════ */ | |
| 581 | ||
| 582 | printf("--- Test 4: Duplicate sections (same parse twice) ---\n"); | |
| 583 | ||
| 584 | reset_section_pool(queue, sht_keys, sht_values, sec_count, | |
| 585 | sec_next_free, total_sections); | |
| 586 | ||
| 587 | uint32_t t4_words[] = {10, 20, 30}; | |
| 588 | uint32_t t4_sent_offsets[] = {0}; | |
| 589 | uint32_t t4_sent_lengths[] = {3}; | |
| 590 | uint32_t t4_edge_p1[] = {0, 1}; | |
| 591 | uint32_t t4_edge_p2[] = {1, 2}; | |
| 592 | uint32_t t4_edge_offsets[] = {0}; | |
| 593 | uint32_t t4_edge_counts[] = {2}; | |
| 594 | cl_uint t4_ns = 1, t4_tw = 3; | |
| 595 | ||
| 596 | clReleaseMemObject(d_flat_words); | |
| 597 | clReleaseMemObject(d_sent_offsets); | |
| 598 | clReleaseMemObject(d_sent_lengths); | |
| 599 | clReleaseMemObject(d_edge_p1); | |
| 600 | clReleaseMemObject(d_edge_p2); | |
| 601 | clReleaseMemObject(d_edge_offsets); | |
| 602 | clReleaseMemObject(d_edge_counts); | |
| 603 | ||
| 604 | d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 605 | sizeof(uint32_t) * t4_tw, t4_words, &err); | |
| 606 | d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 607 | sizeof(uint32_t) * t4_ns, t4_sent_offsets, &err); | |
| 608 | d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 609 | sizeof(uint32_t) * t4_ns, t4_sent_lengths, &err); | |
| 610 | d_edge_p1 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 611 | sizeof(uint32_t) * 2, t4_edge_p1, &err); | |
| 612 | d_edge_p2 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 613 | sizeof(uint32_t) * 2, t4_edge_p2, &err); | |
| 614 | d_edge_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 615 | sizeof(uint32_t) * t4_ns, t4_edge_offsets, &err); | |
| 616 | d_edge_counts = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 617 | sizeof(uint32_t) * t4_ns, t4_edge_counts, &err); | |
| 618 | ||
| 619 | clSetKernelArg(k_extract, 0, sizeof(cl_mem), &d_flat_words); | |
| 620 | clSetKernelArg(k_extract, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 621 | clSetKernelArg(k_extract, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 622 | clSetKernelArg(k_extract, 3, sizeof(cl_uint), &t4_ns); | |
| 623 | clSetKernelArg(k_extract, 4, sizeof(cl_uint), &t4_tw); | |
| 624 | clSetKernelArg(k_extract, 5, sizeof(cl_mem), &d_edge_p1); | |
| 625 | clSetKernelArg(k_extract, 6, sizeof(cl_mem), &d_edge_p2); | |
| 626 | clSetKernelArg(k_extract, 7, sizeof(cl_mem), &d_edge_offsets); | |
| 627 | clSetKernelArg(k_extract, 8, sizeof(cl_mem), &d_edge_counts); | |
| 628 | ||
| 629 | /* Run TWICE */ | |
| 630 | gs = ((t4_tw + local_size - 1) / local_size) * local_size; | |
| 631 | err = clEnqueueNDRangeKernel(queue, k_extract, 1, NULL, | |
| 632 | &gs, &local_size, 0, NULL, NULL); | |
| 633 | CL_CHECK(err, "enqueue extract t4a"); | |
| 634 | clFinish(queue); | |
| 635 | ||
| 636 | err = clEnqueueNDRangeKernel(queue, k_extract, 1, NULL, | |
| 637 | &gs, &local_size, 0, NULL, NULL); | |
| 638 | CL_CHECK(err, "enqueue extract t4b"); | |
| 639 | clFinish(queue); | |
| 640 | ||
| 641 | clEnqueueReadBuffer(queue, sec_next_free, CL_TRUE, 0, | |
| 642 | sizeof(uint32_t), &h_num_sections, 0, NULL, NULL); | |
| 643 | ||
| 644 | /* Read counts */ | |
| 645 | double h4_counts[4]; | |
| 646 | clEnqueueReadBuffer(queue, sec_count, CL_TRUE, 0, | |
| 647 | sizeof(double) * h_num_sections, h4_counts, 0, NULL, NULL); | |
| 648 | ||
| 649 | printf(" Sections created: %u (expected 3 — dedup works)\n", h_num_sections); | |
| 650 | ||
| 651 | int all_count_2 = 1; | |
| 652 | for (uint32_t i = 0; i < h_num_sections; i++) { | |
| 653 | printf(" Section %u count: %.1f (expected 2.0)\n", i, h4_counts[i]); | |
| 654 | if (fabs(h4_counts[i] - 2.0) > 0.01) all_count_2 = 0; | |
| 655 | } | |
| 656 | ||
| 657 | int t4_pass = (h_num_sections == 3) && all_count_2; | |
| 658 | printf(" %s\n\n", t4_pass ? "PASS" : "FAIL"); | |
| 659 | if (t4_pass) pass_count++; else fail_count++; | |
| 660 | ||
| 661 | /* ═══════════════════════════════════════════════════════════════ | |
| 662 | * TEST 5: Readback kernel | |
| 663 | * | |
| 664 | * Use read_sections to verify section pool data matches | |
| 665 | * what extract_sections stored. (Reuse state from test 4.) | |
| 666 | * ═══════════════════════════════════════════════════════════════ */ | |
| 667 | ||
| 668 | printf("--- Test 5: Readback kernel ---\n"); | |
| 669 | ||
| 670 | uint32_t n_secs = h_num_sections; | |
| 671 | cl_mem d_out_word = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 672 | sizeof(uint32_t) * n_secs, NULL, &err); | |
| 673 | cl_mem d_out_djh = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 674 | sizeof(uint64_t) * n_secs, NULL, &err); | |
| 675 | cl_mem d_out_count = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, | |
| 676 | sizeof(double) * n_secs, NULL, &err); | |
| 677 | ||
| 678 | clSetKernelArg(k_read, 0, sizeof(cl_mem), &sec_word); | |
| 679 | clSetKernelArg(k_read, 1, sizeof(cl_mem), &sec_disjunct_hash); | |
| 680 | clSetKernelArg(k_read, 2, sizeof(cl_mem), &sec_count); | |
| 681 | clSetKernelArg(k_read, 3, sizeof(cl_mem), &d_out_word); | |
| 682 | clSetKernelArg(k_read, 4, sizeof(cl_mem), &d_out_djh); | |
| 683 | clSetKernelArg(k_read, 5, sizeof(cl_mem), &d_out_count); | |
| 684 | clSetKernelArg(k_read, 6, sizeof(cl_uint), &n_secs); | |
| 685 | ||
| 686 | gs = ((n_secs + local_size - 1) / local_size) * local_size; | |
| 687 | err = clEnqueueNDRangeKernel(queue, k_read, 1, NULL, | |
| 688 | &gs, &local_size, 0, NULL, NULL); | |
| 689 | CL_CHECK(err, "enqueue read_sections"); | |
| 690 | clFinish(queue); | |
| 691 | ||
| 692 | uint32_t rb_words[4]; | |
| 693 | uint64_t rb_djh[4]; | |
| 694 | double rb_counts[4]; | |
| 695 | clEnqueueReadBuffer(queue, d_out_word, CL_TRUE, 0, | |
| 696 | sizeof(uint32_t) * n_secs, rb_words, 0, NULL, NULL); | |
| 697 | clEnqueueReadBuffer(queue, d_out_djh, CL_TRUE, 0, | |
| 698 | sizeof(uint64_t) * n_secs, rb_djh, 0, NULL, NULL); | |
| 699 | clEnqueueReadBuffer(queue, d_out_count, CL_TRUE, 0, | |
| 700 | sizeof(double) * n_secs, rb_counts, 0, NULL, NULL); | |
| 701 | ||
| 702 | /* Should match test 4's data */ | |
| 703 | uint32_t h5_words[4]; | |
| 704 | uint64_t h5_djh[4]; | |
| 705 | double h5_counts[4]; | |
| 706 | clEnqueueReadBuffer(queue, sec_word, CL_TRUE, 0, | |
| 707 | sizeof(uint32_t) * n_secs, h5_words, 0, NULL, NULL); | |
| 708 | clEnqueueReadBuffer(queue, sec_disjunct_hash, CL_TRUE, 0, | |
| 709 | sizeof(uint64_t) * n_secs, h5_djh, 0, NULL, NULL); | |
| 710 | clEnqueueReadBuffer(queue, sec_count, CL_TRUE, 0, | |
| 711 | sizeof(double) * n_secs, h5_counts, 0, NULL, NULL); | |
| 712 | ||
| 713 | int readback_match = 1; | |
| 714 | for (uint32_t i = 0; i < n_secs; i++) { | |
| 715 | if (rb_words[i] != h5_words[i] || rb_djh[i] != h5_djh[i] || | |
| 716 | fabs(rb_counts[i] - h5_counts[i]) > 0.01) | |
| 717 | readback_match = 0; | |
| 718 | } | |
| 719 | printf(" Readback matches direct read: %s\n", readback_match ? "yes" : "NO"); | |
| 720 | ||
| 721 | int t5_pass = readback_match; | |
| 722 | printf(" %s\n\n", t5_pass ? "PASS" : "FAIL"); | |
| 723 | if (t5_pass) pass_count++; else fail_count++; | |
| 724 | ||
| 725 | clReleaseMemObject(d_out_word); | |
| 726 | clReleaseMemObject(d_out_djh); | |
| 727 | clReleaseMemObject(d_out_count); | |
| 728 | ||
| 729 | /* ═══════════════════════════════════════════════════════════════ | |
| 730 | * TEST 6: Benchmark — 1000 sentences with random MST edges | |
| 731 | * | |
| 732 | * Each sentence: 10-20 words, 9-19 MST edges (chain parse) | |
| 733 | * Total: ~15000 words, ~14000 edges | |
| 734 | * | |
| 735 | * Measures extract_sections throughput. | |
| 736 | * ═══════════════════════════════════════════════════════════════ */ | |
| 737 | ||
| 738 | printf("--- Test 6: Benchmark (1000 sentences) ---\n"); | |
| 739 | ||
| 740 | reset_section_pool(queue, sht_keys, sht_values, sec_count, | |
| 741 | sec_next_free, total_sections); | |
| 742 | ||
| 743 | srand(42); | |
| 744 | uint32_t bench_ns = 1000; | |
| 745 | uint32_t vocab_size = 500; /* word pool indices 0..499 */ | |
| 746 | ||
| 747 | /* Generate sentences */ | |
| 748 | uint32_t max_words = bench_ns * 25; | |
| 749 | uint32_t max_edges = bench_ns * 25; | |
| 750 | uint32_t* b_words = malloc(sizeof(uint32_t) * max_words); | |
| 751 | uint32_t* b_sent_offsets = malloc(sizeof(uint32_t) * bench_ns); | |
| 752 | uint32_t* b_sent_lengths = malloc(sizeof(uint32_t) * bench_ns); | |
| 753 | uint32_t* b_edge_p1 = malloc(sizeof(uint32_t) * max_edges); | |
| 754 | uint32_t* b_edge_p2 = malloc(sizeof(uint32_t) * max_edges); | |
| 755 | uint32_t* b_edge_offsets = malloc(sizeof(uint32_t) * bench_ns); | |
| 756 | uint32_t* b_edge_counts = malloc(sizeof(uint32_t) * bench_ns); | |
| 757 | ||
| 758 | uint32_t word_pos = 0, edge_pos = 0; | |
| 759 | for (uint32_t s = 0; s < bench_ns; s++) { | |
| 760 | uint32_t slen = 10 + (rand() % 11); /* 10-20 words */ | |
| 761 | b_sent_offsets[s] = word_pos; | |
| 762 | b_sent_lengths[s] = slen; | |
| 763 | b_edge_offsets[s] = edge_pos; | |
| 764 | b_edge_counts[s] = slen - 1; /* chain parse */ | |
| 765 | ||
| 766 | for (uint32_t w = 0; w < slen; w++) { | |
| 767 | b_words[word_pos++] = rand() % vocab_size; | |
| 768 | } | |
| 769 | /* Chain parse: pos 0-1, 1-2, ..., (slen-2)-(slen-1) */ | |
| 770 | for (uint32_t e = 0; e < slen - 1; e++) { | |
| 771 | b_edge_p1[edge_pos] = e; | |
| 772 | b_edge_p2[edge_pos] = e + 1; | |
| 773 | edge_pos++; | |
| 774 | } | |
| 775 | } | |
| 776 | ||
| 777 | printf(" Total words: %u\n", word_pos); | |
| 778 | printf(" Total edges: %u\n", edge_pos); | |
| 779 | ||
| 780 | clReleaseMemObject(d_flat_words); | |
| 781 | clReleaseMemObject(d_sent_offsets); | |
| 782 | clReleaseMemObject(d_sent_lengths); | |
| 783 | clReleaseMemObject(d_edge_p1); | |
| 784 | clReleaseMemObject(d_edge_p2); | |
| 785 | clReleaseMemObject(d_edge_offsets); | |
| 786 | clReleaseMemObject(d_edge_counts); | |
| 787 | ||
| 788 | d_flat_words = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 789 | sizeof(uint32_t) * word_pos, b_words, &err); | |
| 790 | d_sent_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 791 | sizeof(uint32_t) * bench_ns, b_sent_offsets, &err); | |
| 792 | d_sent_lengths = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 793 | sizeof(uint32_t) * bench_ns, b_sent_lengths, &err); | |
| 794 | d_edge_p1 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 795 | sizeof(uint32_t) * edge_pos, b_edge_p1, &err); | |
| 796 | d_edge_p2 = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 797 | sizeof(uint32_t) * edge_pos, b_edge_p2, &err); | |
| 798 | d_edge_offsets = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 799 | sizeof(uint32_t) * bench_ns, b_edge_offsets, &err); | |
| 800 | d_edge_counts = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 801 | sizeof(uint32_t) * bench_ns, b_edge_counts, &err); | |
| 802 | ||
| 803 | cl_uint tw_bench = word_pos; | |
| 804 | clSetKernelArg(k_extract, 0, sizeof(cl_mem), &d_flat_words); | |
| 805 | clSetKernelArg(k_extract, 1, sizeof(cl_mem), &d_sent_offsets); | |
| 806 | clSetKernelArg(k_extract, 2, sizeof(cl_mem), &d_sent_lengths); | |
| 807 | clSetKernelArg(k_extract, 3, sizeof(cl_uint), &bench_ns); | |
| 808 | clSetKernelArg(k_extract, 4, sizeof(cl_uint), &tw_bench); | |
| 809 | clSetKernelArg(k_extract, 5, sizeof(cl_mem), &d_edge_p1); | |
| 810 | clSetKernelArg(k_extract, 6, sizeof(cl_mem), &d_edge_p2); | |
| 811 | clSetKernelArg(k_extract, 7, sizeof(cl_mem), &d_edge_offsets); | |
| 812 | clSetKernelArg(k_extract, 8, sizeof(cl_mem), &d_edge_counts); | |
| 813 | ||
| 814 | /* Warm up */ | |
| 815 | gs = ((tw_bench + local_size - 1) / local_size) * local_size; | |
| 816 | clEnqueueNDRangeKernel(queue, k_extract, 1, NULL, | |
| 817 | &gs, &local_size, 0, NULL, NULL); | |
| 818 | clFinish(queue); | |
| 819 | ||
| 820 | /* Reset for actual benchmark */ | |
| 821 | reset_section_pool(queue, sht_keys, sht_values, sec_count, | |
| 822 | sec_next_free, total_sections); | |
| 823 | ||
| 824 | t0 = now_ms(); | |
| 825 | err = clEnqueueNDRangeKernel(queue, k_extract, 1, NULL, | |
| 826 | &gs, &local_size, 0, NULL, NULL); | |
| 827 | CL_CHECK(err, "enqueue benchmark"); | |
| 828 | clFinish(queue); | |
| 829 | t1 = now_ms(); | |
| 830 | ||
| 831 | clEnqueueReadBuffer(queue, sec_next_free, CL_TRUE, 0, | |
| 832 | sizeof(uint32_t), &h_num_sections, 0, NULL, NULL); | |
| 833 | ||
| 834 | double elapsed = t1 - t0; | |
| 835 | double secs_per_sec = bench_ns / (elapsed / 1000.0); | |
| 836 | double words_per_sec = word_pos / (elapsed / 1000.0); | |
| 837 | ||
| 838 | printf(" Sections created: %u\n", h_num_sections); | |
| 839 | printf(" Time: %.2f ms\n", elapsed); | |
| 840 | printf(" Throughput: %.0f sentences/sec, %.0f words/sec\n", | |
| 841 | secs_per_sec, words_per_sec); | |
| 842 | printf(" Throughput: %.1fM sections/sec\n", | |
| 843 | h_num_sections / (elapsed / 1000.0) / 1e6); | |
| 844 | ||
| 845 | /* Sanity: every word should produce a section in a chain parse, | |
| 846 | * but some (word, disjunct) pairs may collide. So sections < words | |
| 847 | * but > 0. */ | |
| 848 | int t6_pass = (h_num_sections > 0) && (h_num_sections <= word_pos) && | |
| 849 | (elapsed < 1000.0); | |
| 850 | printf(" %s\n\n", t6_pass ? "PASS" : "FAIL"); | |
| 851 | if (t6_pass) pass_count++; else fail_count++; | |
| 852 | ||
| 853 | /* ─── Summary ─── */ | |
| 854 | ||
| 855 | printf("=== Results: %d PASS, %d FAIL ===\n", pass_count, fail_count); | |
| 856 | ||
| 857 | /* Cleanup */ | |
| 858 | free(b_words); free(b_sent_offsets); free(b_sent_lengths); | |
| 859 | free(b_edge_p1); free(b_edge_p2); free(b_edge_offsets); free(b_edge_counts); | |
| 860 | free(ht_src); free(as_src); free(sc_src); free(combined); | |
| 861 | ||
| 862 | clReleaseMemObject(d_flat_words); | |
| 863 | clReleaseMemObject(d_sent_offsets); | |
| 864 | clReleaseMemObject(d_sent_lengths); | |
| 865 | clReleaseMemObject(d_edge_p1); | |
| 866 | clReleaseMemObject(d_edge_p2); | |
| 867 | clReleaseMemObject(d_edge_offsets); | |
| 868 | clReleaseMemObject(d_edge_counts); | |
| 869 | clReleaseMemObject(sht_keys); | |
| 870 | clReleaseMemObject(sht_values); | |
| 871 | clReleaseMemObject(sec_word); | |
| 872 | clReleaseMemObject(sec_disjunct_hash); | |
| 873 | clReleaseMemObject(sec_count); | |
| 874 | clReleaseMemObject(sec_next_free); | |
| 875 | clReleaseMemObject(total_sections); | |
| 876 | clReleaseKernel(k_extract); | |
| 877 | clReleaseKernel(k_read); | |
| 878 | clReleaseProgram(program); | |
| 879 | clReleaseCommandQueue(queue); | |
| 880 | clReleaseContext(ctx); | |
| 881 | ||
| 882 | return fail_count > 0 ? 1 : 0; | |
| 883 | } |
| … | … | @@ -0,0 +1,767 @@ |
| 1 | /* | |
| 2 | * test-substitute.c -- Test GPU class substitution kernel | |
| 3 | * | |
| 4 | * Compile: gcc -O2 -o test-substitute test-substitute.c -lOpenCL -lm | |
| 5 | * Run: ./test-substitute | |
| 6 | * | |
| 7 | * Tests: | |
| 8 | * 1. Class assignment — batch set word_class_id | |
| 9 | * 2. Pair substitution — replace word indices (no dedup) | |
| 10 | * 3. Pair merge — duplicate pairs after substitution get merged | |
| 11 | * 4. Self-pair elimination — both words → same class → dropped | |
| 12 | * 5. Section word substitution | |
| 13 | * 6. Benchmark: 100K pairs, 1000 class assignments, substitute + rebuild | |
| 14 | */ | |
| 15 | ||
| 16 | #include <stdio.h> | |
| 17 | #include <stdlib.h> | |
| 18 | #include <stdint.h> | |
| 19 | #include <string.h> | |
| 20 | #include <math.h> | |
| 21 | #include <time.h> | |
| 22 | #include <CL/cl.h> | |
| 23 | ||
| 24 | /* ─── Pool capacities ─── */ | |
| 25 | ||
| 26 | #define WORD_CAPACITY (128 * 1024) | |
| 27 | #define PAIR_CAPACITY (4 * 1024 * 1024) | |
| 28 | #define SECTION_CAPACITY (1024 * 1024) | |
| 29 | #define WORD_HT_CAPACITY (256 * 1024) | |
| 30 | #define PAIR_HT_CAPACITY (8 * 1024 * 1024) | |
| 31 | #define SECTION_HT_CAPACITY (2 * 1024 * 1024) | |
| 32 | ||
| 33 | #define HT_EMPTY_KEY 0xFFFFFFFFFFFFFFFFULL | |
| 34 | #define HT_EMPTY_VALUE 0xFFFFFFFFU | |
| 35 | ||
| 36 | /* ─── Error checking ─── */ | |
| 37 | ||
| 38 | #define CL_CHECK(err, msg) do { \ | |
| 39 | if ((err) != CL_SUCCESS) { \ | |
| 40 | fprintf(stderr, "OpenCL error %d at %s:%d: %s\n", \ | |
| 41 | (err), __FILE__, __LINE__, (msg)); \ | |
| 42 | exit(1); \ | |
| 43 | } \ | |
| 44 | } while(0) | |
| 45 | ||
| 46 | /* ─── Read file ─── */ | |
| 47 | ||
| 48 | char* read_file(const char* path, size_t* len) | |
| 49 | { | |
| 50 | FILE* f = fopen(path, "r"); | |
| 51 | if (!f) { fprintf(stderr, "Cannot open %s\n", path); exit(1); } | |
| 52 | fseek(f, 0, SEEK_END); | |
| 53 | *len = ftell(f); | |
| 54 | fseek(f, 0, SEEK_SET); | |
| 55 | char* buf = malloc(*len + 1); | |
| 56 | size_t n = fread(buf, 1, *len, f); | |
| 57 | buf[n] = '\0'; | |
| 58 | *len = n; | |
| 59 | fclose(f); | |
| 60 | return buf; | |
| 61 | } | |
| 62 | ||
| 63 | /* ─── Timing ─── */ | |
| 64 | ||
| 65 | double now_ms(void) | |
| 66 | { | |
| 67 | struct timespec ts; | |
| 68 | clock_gettime(CLOCK_MONOTONIC, &ts); | |
| 69 | return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0; | |
| 70 | } | |
| 71 | ||
| 72 | /* ─── Main ─── */ | |
| 73 | ||
| 74 | int main(int argc, char** argv) | |
| 75 | { | |
| 76 | cl_int err; | |
| 77 | int pass_count = 0, fail_count = 0; | |
| 78 | ||
| 79 | printf("=== GPU Class Substitution Test ===\n\n"); | |
| 80 | ||
| 81 | /* ─── OpenCL setup ─── */ | |
| 82 | ||
| 83 | cl_platform_id platform; | |
| 84 | err = clGetPlatformIDs(1, &platform, NULL); | |
| 85 | CL_CHECK(err, "platform"); | |
| 86 | ||
| 87 | cl_device_id device; | |
| 88 | err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL); | |
| 89 | CL_CHECK(err, "device"); | |
| 90 | ||
| 91 | char dev_name[256]; | |
| 92 | clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(dev_name), dev_name, NULL); | |
| 93 | printf("GPU: %s\n", dev_name); | |
| 94 | ||
| 95 | cl_context ctx = clCreateContext(NULL, 1, &device, NULL, NULL, &err); | |
| 96 | CL_CHECK(err, "context"); | |
| 97 | ||
| 98 | cl_command_queue queue = clCreateCommandQueue(ctx, device, 0, &err); | |
| 99 | CL_CHECK(err, "queue"); | |
| 100 | ||
| 101 | /* ─── Load and concatenate kernel sources ─── */ | |
| 102 | ||
| 103 | size_t len_ht, len_as, len_sub; | |
| 104 | char* src_ht = read_file("gpu-hashtable.cl", &len_ht); | |
| 105 | char* src_as = read_file("gpu-atomspace.cl", &len_as); | |
| 106 | char* src_sub = read_file("gpu-substitute.cl", &len_sub); | |
| 107 | ||
| 108 | size_t total_len = len_ht + 1 + len_as + 1 + len_sub; | |
| 109 | char* combined = malloc(total_len + 1); | |
| 110 | memcpy(combined, src_ht, len_ht); | |
| 111 | combined[len_ht] = '\n'; | |
| 112 | memcpy(combined + len_ht + 1, src_as, len_as); | |
| 113 | combined[len_ht + 1 + len_as] = '\n'; | |
| 114 | memcpy(combined + len_ht + 1 + len_as + 1, src_sub, len_sub); | |
| 115 | combined[total_len] = '\0'; | |
| 116 | ||
| 117 | cl_program program = clCreateProgramWithSource(ctx, 1, | |
| 118 | (const char**)&combined, &total_len, &err); | |
| 119 | CL_CHECK(err, "create program"); | |
| 120 | ||
| 121 | char build_opts[512]; | |
| 122 | snprintf(build_opts, sizeof(build_opts), | |
| 123 | "-cl-std=CL1.2 " | |
| 124 | "-DWORD_CAPACITY=%d " | |
| 125 | "-DPAIR_CAPACITY=%d " | |
| 126 | "-DSECTION_CAPACITY=%d " | |
| 127 | "-DWORD_HT_CAPACITY=%d " | |
| 128 | "-DPAIR_HT_CAPACITY=%d " | |
| 129 | "-DSECTION_HT_CAPACITY=%d", | |
| 130 | WORD_CAPACITY, PAIR_CAPACITY, SECTION_CAPACITY, | |
| 131 | WORD_HT_CAPACITY, PAIR_HT_CAPACITY, SECTION_HT_CAPACITY); | |
| 132 | ||
| 133 | err = clBuildProgram(program, 1, &device, build_opts, NULL, NULL); | |
| 134 | if (err != CL_SUCCESS) { | |
| 135 | char log[16384]; | |
| 136 | clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, | |
| 137 | sizeof(log), log, NULL); | |
| 138 | fprintf(stderr, "Build error:\n%s\n", log); | |
| 139 | return 1; | |
| 140 | } | |
| 141 | printf("Kernels compiled successfully\n\n"); | |
| 142 | ||
| 143 | /* ─── Create kernels ─── */ | |
| 144 | ||
| 145 | cl_kernel k_assign = clCreateKernel(program, "assign_classes", &err); | |
| 146 | CL_CHECK(err, "kernel assign_classes"); | |
| 147 | cl_kernel k_sub_pair = clCreateKernel(program, "substitute_pairs", &err); | |
| 148 | CL_CHECK(err, "kernel substitute_pairs"); | |
| 149 | cl_kernel k_rebuild = clCreateKernel(program, "rebuild_pair_index", &err); | |
| 150 | CL_CHECK(err, "kernel rebuild_pair_index"); | |
| 151 | cl_kernel k_sub_sec = clCreateKernel(program, "substitute_section_words", &err); | |
| 152 | CL_CHECK(err, "kernel substitute_section_words"); | |
| 153 | ||
| 154 | size_t local_size = 256; | |
| 155 | ||
| 156 | /* ─── Allocate GPU buffers ─── */ | |
| 157 | ||
| 158 | printf("Allocating GPU buffers...\n"); | |
| 159 | uint32_t zero = 0; | |
| 160 | uint8_t pat_ff = 0xFF; | |
| 161 | uint8_t pat_00 = 0x00; | |
| 162 | ||
| 163 | /* Word class IDs */ | |
| 164 | cl_mem word_class_id = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 165 | sizeof(uint32_t) * WORD_CAPACITY, NULL, &err); | |
| 166 | CL_CHECK(err, "word_class_id"); | |
| 167 | clEnqueueFillBuffer(queue, word_class_id, &pat_00, 1, 0, | |
| 168 | sizeof(uint32_t) * WORD_CAPACITY, 0, NULL, NULL); | |
| 169 | ||
| 170 | /* Pair pool */ | |
| 171 | cl_mem pair_word_a = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 172 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 173 | cl_mem pair_word_b = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 174 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 175 | cl_mem pair_count = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 176 | sizeof(double) * PAIR_CAPACITY, NULL, &err); | |
| 177 | cl_mem pair_mi = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 178 | sizeof(double) * PAIR_CAPACITY, NULL, &err); | |
| 179 | cl_mem pair_flags = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 180 | sizeof(uint32_t) * PAIR_CAPACITY, NULL, &err); | |
| 181 | ||
| 182 | /* Pair hash table */ | |
| 183 | cl_mem pht_keys = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 184 | sizeof(uint64_t) * PAIR_HT_CAPACITY, NULL, &err); | |
| 185 | cl_mem pht_values = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 186 | sizeof(uint32_t) * PAIR_HT_CAPACITY, NULL, &err); | |
| 187 | ||
| 188 | /* Section pool (for test 5) */ | |
| 189 | cl_mem sec_word = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 190 | sizeof(uint32_t) * SECTION_CAPACITY, NULL, &err); | |
| 191 | cl_mem sec_count_buf = clCreateBuffer(ctx, CL_MEM_READ_WRITE, | |
| 192 | sizeof(double) * SECTION_CAPACITY, NULL, &err); | |
| 193 | ||
| 194 | /* Counters */ | |
| 195 | cl_mem num_changed = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 196 | sizeof(uint32_t), &zero, &err); | |
| 197 | cl_mem num_eliminated = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 198 | sizeof(uint32_t), &zero, &err); | |
| 199 | cl_mem num_merged = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, | |
| 200 | sizeof(uint32_t), &zero, &err); | |
| 201 | ||
| 202 | clFinish(queue); | |
| 203 | printf("GPU buffers ready\n\n"); | |
| 204 | ||
| 205 | /* ═══════════════════════════════════════════════════════════════ | |
| 206 | * TEST 1: Class assignment | |
| 207 | * | |
| 208 | * Assign: word 10 → class 100, word 20 → class 100 | |
| 209 | * Verify word_class_id[10] = 100, word_class_id[20] = 100 | |
| 210 | * ═══════════════════════════════════════════════════════════════ */ | |
| 211 | ||
| 212 | printf("--- Test 1: Class assignment ---\n"); | |
| 213 | ||
| 214 | /* Reset word_class_id */ | |
| 215 | clEnqueueFillBuffer(queue, word_class_id, &pat_00, 1, 0, | |
| 216 | sizeof(uint32_t) * WORD_CAPACITY, 0, NULL, NULL); | |
| 217 | clFinish(queue); | |
| 218 | ||
| 219 | { | |
| 220 | uint32_t word_indices[] = {10, 20}; | |
| 221 | uint32_t class_ids[] = {100, 100}; | |
| 222 | cl_uint na = 2; | |
| 223 | ||
| 224 | cl_mem d_widx = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 225 | sizeof(uint32_t) * na, word_indices, &err); | |
| 226 | cl_mem d_cids = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 227 | sizeof(uint32_t) * na, class_ids, &err); | |
| 228 | ||
| 229 | clSetKernelArg(k_assign, 0, sizeof(cl_mem), &word_class_id); | |
| 230 | clSetKernelArg(k_assign, 1, sizeof(cl_mem), &d_widx); | |
| 231 | clSetKernelArg(k_assign, 2, sizeof(cl_mem), &d_cids); | |
| 232 | clSetKernelArg(k_assign, 3, sizeof(cl_uint), &na); | |
| 233 | ||
| 234 | size_t gs = ((na + local_size - 1) / local_size) * local_size; | |
| 235 | err = clEnqueueNDRangeKernel(queue, k_assign, 1, NULL, | |
| 236 | &gs, &local_size, 0, NULL, NULL); | |
| 237 | CL_CHECK(err, "enqueue assign"); | |
| 238 | clFinish(queue); | |
| 239 | ||
| 240 | /* Read back */ | |
| 241 | uint32_t h_cls[32]; | |
| 242 | clEnqueueReadBuffer(queue, word_class_id, CL_TRUE, 0, | |
| 243 | sizeof(uint32_t) * 32, h_cls, 0, NULL, NULL); | |
| 244 | ||
| 245 | printf(" word_class_id[10] = %u (expected 100)\n", h_cls[10]); | |
| 246 | printf(" word_class_id[20] = %u (expected 100)\n", h_cls[20]); | |
| 247 | printf(" word_class_id[0] = %u (expected 0)\n", h_cls[0]); | |
| 248 | printf(" word_class_id[15] = %u (expected 0)\n", h_cls[15]); | |
| 249 | ||
| 250 | int pass = (h_cls[10] == 100) && (h_cls[20] == 100) && | |
| 251 | (h_cls[0] == 0) && (h_cls[15] == 0); | |
| 252 | printf(" %s\n\n", pass ? "PASS" : "FAIL"); | |
| 253 | if (pass) pass_count++; else fail_count++; | |
| 254 | ||
| 255 | clReleaseMemObject(d_widx); | |
| 256 | clReleaseMemObject(d_cids); | |
| 257 | } | |
| 258 | ||
| 259 | /* ═══════════════════════════════════════════════════════════════ | |
| 260 | * TEST 2: Pair substitution (no dedup) | |
| 261 | * | |
| 262 | * Pairs: (10, 30) count=5, (20, 40) count=3 | |
| 263 | * Classes: word 10 → class 100 (already assigned from test 1) | |
| 264 | * | |
| 265 | * After substitution: | |
| 266 | * pair 0: (30, 100) count=5 [was (10, 30), recanonized] | |
| 267 | * pair 1: (20, 40) count=3 [word 20=class 100, so → (40, 100)] | |
| 268 | * | |
| 269 | * Wait — word 20 is ALSO class 100 from test 1! So: | |
| 270 | * pair 1: (40, 100) count=3 | |
| 271 | * ═══════════════════════════════════════════════════════════════ */ | |
| 272 | ||
| 273 | printf("--- Test 2: Pair substitution (no dedup) ---\n"); | |
| 274 | ||
| 275 | { | |
| 276 | uint32_t h_wa[] = {10, 20}; | |
| 277 | uint32_t h_wb[] = {30, 40}; | |
| 278 | double h_cnt[] = {5.0, 3.0}; | |
| 279 | double h_mi[] = {2.5, 1.5}; | |
| 280 | uint32_t h_flg[] = {0, 0}; | |
| 281 | cl_uint np = 2; | |
| 282 | ||
| 283 | clEnqueueWriteBuffer(queue, pair_word_a, CL_FALSE, 0, | |
| 284 | sizeof(uint32_t) * np, h_wa, 0, NULL, NULL); | |
| 285 | clEnqueueWriteBuffer(queue, pair_word_b, CL_FALSE, 0, | |
| 286 | sizeof(uint32_t) * np, h_wb, 0, NULL, NULL); | |
| 287 | clEnqueueWriteBuffer(queue, pair_count, CL_FALSE, 0, | |
| 288 | sizeof(double) * np, h_cnt, 0, NULL, NULL); | |
| 289 | clEnqueueWriteBuffer(queue, pair_mi, CL_FALSE, 0, | |
| 290 | sizeof(double) * np, h_mi, 0, NULL, NULL); | |
| 291 | clEnqueueWriteBuffer(queue, pair_flags, CL_FALSE, 0, | |
| 292 | sizeof(uint32_t) * np, h_flg, 0, NULL, NULL); | |
| 293 | ||
| 294 | clEnqueueWriteBuffer(queue, num_changed, CL_FALSE, 0, | |
| 295 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 296 | clEnqueueWriteBuffer(queue, num_eliminated, CL_FALSE, 0, | |
| 297 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 298 | clFinish(queue); | |
| 299 | ||
| 300 | clSetKernelArg(k_sub_pair, 0, sizeof(cl_mem), &pair_word_a); | |
| 301 | clSetKernelArg(k_sub_pair, 1, sizeof(cl_mem), &pair_word_b); | |
| 302 | clSetKernelArg(k_sub_pair, 2, sizeof(cl_mem), &pair_count); | |
| 303 | clSetKernelArg(k_sub_pair, 3, sizeof(cl_mem), &pair_mi); | |
| 304 | clSetKernelArg(k_sub_pair, 4, sizeof(cl_mem), &pair_flags); | |
| 305 | clSetKernelArg(k_sub_pair, 5, sizeof(cl_mem), &word_class_id); | |
| 306 | clSetKernelArg(k_sub_pair, 6, sizeof(cl_mem), &num_changed); | |
| 307 | clSetKernelArg(k_sub_pair, 7, sizeof(cl_mem), &num_eliminated); | |
| 308 | clSetKernelArg(k_sub_pair, 8, sizeof(cl_uint), &np); | |
| 309 | ||
| 310 | size_t gs = ((np + local_size - 1) / local_size) * local_size; | |
| 311 | err = clEnqueueNDRangeKernel(queue, k_sub_pair, 1, NULL, | |
| 312 | &gs, &local_size, 0, NULL, NULL); | |
| 313 | CL_CHECK(err, "enqueue substitute_pairs"); | |
| 314 | clFinish(queue); | |
| 315 | ||
| 316 | /* Read back */ | |
| 317 | uint32_t r_wa[2], r_wb[2], r_flg[2]; | |
| 318 | double r_cnt[2]; | |
| 319 | clEnqueueReadBuffer(queue, pair_word_a, CL_TRUE, 0, | |
| 320 | sizeof(uint32_t) * np, r_wa, 0, NULL, NULL); | |
| 321 | clEnqueueReadBuffer(queue, pair_word_b, CL_TRUE, 0, | |
| 322 | sizeof(uint32_t) * np, r_wb, 0, NULL, NULL); | |
| 323 | clEnqueueReadBuffer(queue, pair_count, CL_TRUE, 0, | |
| 324 | sizeof(double) * np, r_cnt, 0, NULL, NULL); | |
| 325 | clEnqueueReadBuffer(queue, pair_flags, CL_TRUE, 0, | |
| 326 | sizeof(uint32_t) * np, r_flg, 0, NULL, NULL); | |
| 327 | ||
| 328 | uint32_t r_nchanged; | |
| 329 | clEnqueueReadBuffer(queue, num_changed, CL_TRUE, 0, | |
| 330 | sizeof(uint32_t), &r_nchanged, 0, NULL, NULL); | |
| 331 | ||
| 332 | /* Pair 0: (10,30) → (100,30) → canonical (30,100), count=5 */ | |
| 333 | /* Pair 1: (20,40) → (100,40) → canonical (40,100), count=3 */ | |
| 334 | printf(" Pair 0: (%u, %u) count=%.0f flags=%u\n", | |
| 335 | r_wa[0], r_wb[0], r_cnt[0], r_flg[0]); | |
| 336 | printf(" Expected: (30, 100) count=5 flags=1\n"); | |
| 337 | printf(" Pair 1: (%u, %u) count=%.0f flags=%u\n", | |
| 338 | r_wa[1], r_wb[1], r_cnt[1], r_flg[1]); | |
| 339 | printf(" Expected: (40, 100) count=3 flags=1\n"); | |
| 340 | printf(" Changed: %u (expected 2)\n", r_nchanged); | |
| 341 | ||
| 342 | int pass = (r_wa[0] == 30) && (r_wb[0] == 100) && | |
| 343 | (fabs(r_cnt[0] - 5.0) < 0.01) && (r_flg[0] == 1) && | |
| 344 | (r_wa[1] == 40) && (r_wb[1] == 100) && | |
| 345 | (fabs(r_cnt[1] - 3.0) < 0.01) && (r_flg[1] == 1) && | |
| 346 | (r_nchanged == 2); | |
| 347 | printf(" %s\n\n", pass ? "PASS" : "FAIL"); | |
| 348 | if (pass) pass_count++; else fail_count++; | |
| 349 | } | |
| 350 | ||
| 351 | /* ═══════════════════════════════════════════════════════════════ | |
| 352 | * TEST 3: Pair merge — duplicate pairs after substitution | |
| 353 | * | |
| 354 | * Fresh setup: | |
| 355 | * Pair 0: (10, 30) count=5 | |
| 356 | * Pair 1: (20, 30) count=3 | |
| 357 | * Classes: word 10 → class 100, word 20 → class 100 | |
| 358 | * | |
| 359 | * After substitute_pairs: | |
| 360 | * Pair 0: (30, 100) count=5 | |
| 361 | * Pair 1: (30, 100) count=3 ← duplicate! | |
| 362 | * | |
| 363 | * After rebuild_pair_index: | |
| 364 | * One pair (30, 100) count=8, other zeroed | |
| 365 | * ═══════════════════════════════════════════════════════════════ */ | |
| 366 | ||
| 367 | printf("--- Test 3: Pair merge (duplicate pairs) ---\n"); | |
| 368 | ||
| 369 | /* Reset class IDs and reassign */ | |
| 370 | clEnqueueFillBuffer(queue, word_class_id, &pat_00, 1, 0, | |
| 371 | sizeof(uint32_t) * WORD_CAPACITY, 0, NULL, NULL); | |
| 372 | clFinish(queue); | |
| 373 | ||
| 374 | { | |
| 375 | uint32_t word_indices[] = {10, 20}; | |
| 376 | uint32_t class_ids[] = {100, 100}; | |
| 377 | cl_uint na = 2; | |
| 378 | cl_mem d_widx = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 379 | sizeof(uint32_t) * na, word_indices, &err); | |
| 380 | cl_mem d_cids = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 381 | sizeof(uint32_t) * na, class_ids, &err); | |
| 382 | clSetKernelArg(k_assign, 0, sizeof(cl_mem), &word_class_id); | |
| 383 | clSetKernelArg(k_assign, 1, sizeof(cl_mem), &d_widx); | |
| 384 | clSetKernelArg(k_assign, 2, sizeof(cl_mem), &d_cids); | |
| 385 | clSetKernelArg(k_assign, 3, sizeof(cl_uint), &na); | |
| 386 | size_t gs = ((na + local_size - 1) / local_size) * local_size; | |
| 387 | clEnqueueNDRangeKernel(queue, k_assign, 1, NULL, | |
| 388 | &gs, &local_size, 0, NULL, NULL); | |
| 389 | clFinish(queue); | |
| 390 | clReleaseMemObject(d_widx); | |
| 391 | clReleaseMemObject(d_cids); | |
| 392 | } | |
| 393 | ||
| 394 | { | |
| 395 | uint32_t h_wa[] = {10, 20}; | |
| 396 | uint32_t h_wb[] = {30, 30}; | |
| 397 | double h_cnt[] = {5.0, 3.0}; | |
| 398 | double h_mi[] = {2.5, 1.5}; | |
| 399 | uint32_t h_flg[] = {0, 0}; | |
| 400 | cl_uint np = 2; | |
| 401 | ||
| 402 | clEnqueueWriteBuffer(queue, pair_word_a, CL_FALSE, 0, | |
| 403 | sizeof(uint32_t) * np, h_wa, 0, NULL, NULL); | |
| 404 | clEnqueueWriteBuffer(queue, pair_word_b, CL_FALSE, 0, | |
| 405 | sizeof(uint32_t) * np, h_wb, 0, NULL, NULL); | |
| 406 | clEnqueueWriteBuffer(queue, pair_count, CL_FALSE, 0, | |
| 407 | sizeof(double) * np, h_cnt, 0, NULL, NULL); | |
| 408 | clEnqueueWriteBuffer(queue, pair_mi, CL_FALSE, 0, | |
| 409 | sizeof(double) * np, h_mi, 0, NULL, NULL); | |
| 410 | clEnqueueWriteBuffer(queue, pair_flags, CL_FALSE, 0, | |
| 411 | sizeof(uint32_t) * np, h_flg, 0, NULL, NULL); | |
| 412 | ||
| 413 | clEnqueueWriteBuffer(queue, num_changed, CL_FALSE, 0, | |
| 414 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 415 | clEnqueueWriteBuffer(queue, num_eliminated, CL_FALSE, 0, | |
| 416 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 417 | clEnqueueWriteBuffer(queue, num_merged, CL_FALSE, 0, | |
| 418 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 419 | clFinish(queue); | |
| 420 | ||
| 421 | /* Step 1: Substitute */ | |
| 422 | clSetKernelArg(k_sub_pair, 8, sizeof(cl_uint), &np); | |
| 423 | size_t gs2 = ((np + local_size - 1) / local_size) * local_size; | |
| 424 | clEnqueueNDRangeKernel(queue, k_sub_pair, 1, NULL, | |
| 425 | &gs2, &local_size, 0, NULL, NULL); | |
| 426 | clFinish(queue); | |
| 427 | ||
| 428 | /* Step 2: Clear pair HT and rebuild */ | |
| 429 | clEnqueueFillBuffer(queue, pht_keys, &pat_ff, 1, 0, | |
| 430 | sizeof(uint64_t) * PAIR_HT_CAPACITY, 0, NULL, NULL); | |
| 431 | clEnqueueFillBuffer(queue, pht_values, &pat_ff, 1, 0, | |
| 432 | sizeof(uint32_t) * PAIR_HT_CAPACITY, 0, NULL, NULL); | |
| 433 | clFinish(queue); | |
| 434 | ||
| 435 | clSetKernelArg(k_rebuild, 0, sizeof(cl_mem), &pair_word_a); | |
| 436 | clSetKernelArg(k_rebuild, 1, sizeof(cl_mem), &pair_word_b); | |
| 437 | clSetKernelArg(k_rebuild, 2, sizeof(cl_mem), &pair_count); | |
| 438 | clSetKernelArg(k_rebuild, 3, sizeof(cl_mem), &pair_mi); | |
| 439 | clSetKernelArg(k_rebuild, 4, sizeof(cl_mem), &pair_flags); | |
| 440 | clSetKernelArg(k_rebuild, 5, sizeof(cl_mem), &pht_keys); | |
| 441 | clSetKernelArg(k_rebuild, 6, sizeof(cl_mem), &pht_values); | |
| 442 | clSetKernelArg(k_rebuild, 7, sizeof(cl_mem), &num_merged); | |
| 443 | clSetKernelArg(k_rebuild, 8, sizeof(cl_uint), &np); | |
| 444 | ||
| 445 | clEnqueueNDRangeKernel(queue, k_rebuild, 1, NULL, | |
| 446 | &gs2, &local_size, 0, NULL, NULL); | |
| 447 | clFinish(queue); | |
| 448 | ||
| 449 | /* Read back */ | |
| 450 | double r_cnt[2]; | |
| 451 | clEnqueueReadBuffer(queue, pair_count, CL_TRUE, 0, | |
| 452 | sizeof(double) * np, r_cnt, 0, NULL, NULL); | |
| 453 | ||
| 454 | uint32_t r_nmerged; | |
| 455 | clEnqueueReadBuffer(queue, num_merged, CL_TRUE, 0, | |
| 456 | sizeof(uint32_t), &r_nmerged, 0, NULL, NULL); | |
| 457 | ||
| 458 | /* One pair should have count=8, other count=0 */ | |
| 459 | double total = r_cnt[0] + r_cnt[1]; | |
| 460 | int one_is_eight = (fabs(r_cnt[0] - 8.0) < 0.01) || (fabs(r_cnt[1] - 8.0) < 0.01); | |
| 461 | int one_is_zero = (fabs(r_cnt[0]) < 0.01) || (fabs(r_cnt[1]) < 0.01); | |
| 462 | ||
| 463 | printf(" Pair 0 count: %.1f\n", r_cnt[0]); | |
| 464 | printf(" Pair 1 count: %.1f\n", r_cnt[1]); | |
| 465 | printf(" Total: %.1f (expected 8.0)\n", total); | |
| 466 | printf(" Merged: %u (expected 1)\n", r_nmerged); | |
| 467 | ||
| 468 | int pass = (fabs(total - 8.0) < 0.01) && one_is_eight && | |
| 469 | one_is_zero && (r_nmerged == 1); | |
| 470 | printf(" %s\n\n", pass ? "PASS" : "FAIL"); | |
| 471 | if (pass) pass_count++; else fail_count++; | |
| 472 | } | |
| 473 | ||
| 474 | /* ═══════════════════════════════════════════════════════════════ | |
| 475 | * TEST 4: Self-pair elimination | |
| 476 | * | |
| 477 | * Pair: (10, 20) count=7 | |
| 478 | * Classes: word 10 → class 100, word 20 → class 100 | |
| 479 | * | |
| 480 | * After substitution: (100, 100) → self-pair → eliminated | |
| 481 | * ═══════════════════════════════════════════════════════════════ */ | |
| 482 | ||
| 483 | printf("--- Test 4: Self-pair elimination ---\n"); | |
| 484 | ||
| 485 | /* Class IDs still set from test 3: word 10→100, word 20→100 */ | |
| 486 | ||
| 487 | { | |
| 488 | uint32_t h_wa[] = {10}; | |
| 489 | uint32_t h_wb[] = {20}; | |
| 490 | double h_cnt[] = {7.0}; | |
| 491 | double h_mi[] = {3.0}; | |
| 492 | uint32_t h_flg[] = {0}; | |
| 493 | cl_uint np = 1; | |
| 494 | ||
| 495 | clEnqueueWriteBuffer(queue, pair_word_a, CL_FALSE, 0, | |
| 496 | sizeof(uint32_t) * np, h_wa, 0, NULL, NULL); | |
| 497 | clEnqueueWriteBuffer(queue, pair_word_b, CL_FALSE, 0, | |
| 498 | sizeof(uint32_t) * np, h_wb, 0, NULL, NULL); | |
| 499 | clEnqueueWriteBuffer(queue, pair_count, CL_FALSE, 0, | |
| 500 | sizeof(double) * np, h_cnt, 0, NULL, NULL); | |
| 501 | clEnqueueWriteBuffer(queue, pair_mi, CL_FALSE, 0, | |
| 502 | sizeof(double) * np, h_mi, 0, NULL, NULL); | |
| 503 | clEnqueueWriteBuffer(queue, pair_flags, CL_FALSE, 0, | |
| 504 | sizeof(uint32_t) * np, h_flg, 0, NULL, NULL); | |
| 505 | ||
| 506 | clEnqueueWriteBuffer(queue, num_changed, CL_FALSE, 0, | |
| 507 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 508 | clEnqueueWriteBuffer(queue, num_eliminated, CL_FALSE, 0, | |
| 509 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 510 | clFinish(queue); | |
| 511 | ||
| 512 | clSetKernelArg(k_sub_pair, 8, sizeof(cl_uint), &np); | |
| 513 | size_t gs = ((np + local_size - 1) / local_size) * local_size; | |
| 514 | clEnqueueNDRangeKernel(queue, k_sub_pair, 1, NULL, | |
| 515 | &gs, &local_size, 0, NULL, NULL); | |
| 516 | clFinish(queue); | |
| 517 | ||
| 518 | double r_cnt; | |
| 519 | uint32_t r_nelim; | |
| 520 | clEnqueueReadBuffer(queue, pair_count, CL_TRUE, 0, | |
| 521 | sizeof(double), &r_cnt, 0, NULL, NULL); | |
| 522 | clEnqueueReadBuffer(queue, num_eliminated, CL_TRUE, 0, | |
| 523 | sizeof(uint32_t), &r_nelim, 0, NULL, NULL); | |
| 524 | ||
| 525 | printf(" Pair count after: %.1f (expected 0.0 — eliminated)\n", r_cnt); | |
| 526 | printf(" Eliminated: %u (expected 1)\n", r_nelim); | |
| 527 | ||
| 528 | int pass = (fabs(r_cnt) < 0.01) && (r_nelim == 1); | |
| 529 | printf(" %s\n\n", pass ? "PASS" : "FAIL"); | |
| 530 | if (pass) pass_count++; else fail_count++; | |
| 531 | } | |
| 532 | ||
| 533 | /* ═══════════════════════════════════════════════════════════════ | |
| 534 | * TEST 5: Section word substitution | |
| 535 | * | |
| 536 | * Sections: (word=10, count=5), (word=30, count=2), (word=20, count=4) | |
| 537 | * Classes: word 10 → 100, word 20 → 100 (from test 3) | |
| 538 | * | |
| 539 | * After substitution: | |
| 540 | * Section 0: word=100 (was 10) | |
| 541 | * Section 1: word=30 (unchanged) | |
| 542 | * Section 2: word=100 (was 20) | |
| 543 | * ═══════════════════════════════════════════════════════════════ */ | |
| 544 | ||
| 545 | printf("--- Test 5: Section word substitution ---\n"); | |
| 546 | ||
| 547 | { | |
| 548 | uint32_t h_sw[] = {10, 30, 20}; | |
| 549 | double h_sc[] = {5.0, 2.0, 4.0}; | |
| 550 | cl_uint ns = 3; | |
| 551 | ||
| 552 | clEnqueueWriteBuffer(queue, sec_word, CL_FALSE, 0, | |
| 553 | sizeof(uint32_t) * ns, h_sw, 0, NULL, NULL); | |
| 554 | clEnqueueWriteBuffer(queue, sec_count_buf, CL_FALSE, 0, | |
| 555 | sizeof(double) * ns, h_sc, 0, NULL, NULL); | |
| 556 | clEnqueueWriteBuffer(queue, num_changed, CL_FALSE, 0, | |
| 557 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 558 | clFinish(queue); | |
| 559 | ||
| 560 | clSetKernelArg(k_sub_sec, 0, sizeof(cl_mem), &sec_word); | |
| 561 | clSetKernelArg(k_sub_sec, 1, sizeof(cl_mem), &sec_count_buf); | |
| 562 | clSetKernelArg(k_sub_sec, 2, sizeof(cl_mem), &word_class_id); | |
| 563 | clSetKernelArg(k_sub_sec, 3, sizeof(cl_mem), &num_changed); | |
| 564 | clSetKernelArg(k_sub_sec, 4, sizeof(cl_uint), &ns); | |
| 565 | ||
| 566 | size_t gs = ((ns + local_size - 1) / local_size) * local_size; | |
| 567 | clEnqueueNDRangeKernel(queue, k_sub_sec, 1, NULL, | |
| 568 | &gs, &local_size, 0, NULL, NULL); | |
| 569 | clFinish(queue); | |
| 570 | ||
| 571 | uint32_t r_sw[3]; | |
| 572 | uint32_t r_nch; | |
| 573 | clEnqueueReadBuffer(queue, sec_word, CL_TRUE, 0, | |
| 574 | sizeof(uint32_t) * ns, r_sw, 0, NULL, NULL); | |
| 575 | clEnqueueReadBuffer(queue, num_changed, CL_TRUE, 0, | |
| 576 | sizeof(uint32_t), &r_nch, 0, NULL, NULL); | |
| 577 | ||
| 578 | printf(" Section 0 word: %u (expected 100)\n", r_sw[0]); | |
| 579 | printf(" Section 1 word: %u (expected 30)\n", r_sw[1]); | |
| 580 | printf(" Section 2 word: %u (expected 100)\n", r_sw[2]); | |
| 581 | printf(" Changed: %u (expected 2)\n", r_nch); | |
| 582 | ||
| 583 | int pass = (r_sw[0] == 100) && (r_sw[1] == 30) && (r_sw[2] == 100) && | |
| 584 | (r_nch == 2); | |
| 585 | printf(" %s\n\n", pass ? "PASS" : "FAIL"); | |
| 586 | if (pass) pass_count++; else fail_count++; | |
| 587 | } | |
| 588 | ||
| 589 | /* ═══════════════════════════════════════════════════════════════ | |
| 590 | * TEST 6: Benchmark — 100K pairs, 1000 class assignments | |
| 591 | * | |
| 592 | * 100 words assigned to 20 classes. | |
| 593 | * 100K pairs with random word indices (0..999). | |
| 594 | * Measure substitute + rebuild time. | |
| 595 | * ═══════════════════════════════════════════════════════════════ */ | |
| 596 | ||
| 597 | printf("--- Test 6: Benchmark (100K pairs, 100 word→20 class) ---\n"); | |
| 598 | ||
| 599 | /* Reset class IDs */ | |
| 600 | clEnqueueFillBuffer(queue, word_class_id, &pat_00, 1, 0, | |
| 601 | sizeof(uint32_t) * WORD_CAPACITY, 0, NULL, NULL); | |
| 602 | clFinish(queue); | |
| 603 | ||
| 604 | srand(42); | |
| 605 | uint32_t bench_np = 100000; | |
| 606 | uint32_t num_classes = 20; | |
| 607 | uint32_t words_per_class = 5; | |
| 608 | uint32_t total_assigned = num_classes * words_per_class; /* 100 */ | |
| 609 | uint32_t vocab = 1000; | |
| 610 | ||
| 611 | /* Assign classes */ | |
| 612 | { | |
| 613 | uint32_t* h_widx = malloc(sizeof(uint32_t) * total_assigned); | |
| 614 | uint32_t* h_cids = malloc(sizeof(uint32_t) * total_assigned); | |
| 615 | for (uint32_t c = 0; c < num_classes; c++) { | |
| 616 | for (uint32_t w = 0; w < words_per_class; w++) { | |
| 617 | uint32_t idx = c * words_per_class + w; | |
| 618 | h_widx[idx] = c * words_per_class + w; /* words 0..99 */ | |
| 619 | h_cids[idx] = 10000 + c; /* class IDs 10000..10019 */ | |
| 620 | } | |
| 621 | } | |
| 622 | ||
| 623 | cl_mem d_widx = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 624 | sizeof(uint32_t) * total_assigned, h_widx, &err); | |
| 625 | cl_mem d_cids = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, | |
| 626 | sizeof(uint32_t) * total_assigned, h_cids, &err); | |
| 627 | ||
| 628 | clSetKernelArg(k_assign, 0, sizeof(cl_mem), &word_class_id); | |
| 629 | clSetKernelArg(k_assign, 1, sizeof(cl_mem), &d_widx); | |
| 630 | clSetKernelArg(k_assign, 2, sizeof(cl_mem), &d_cids); | |
| 631 | cl_uint na2 = total_assigned; | |
| 632 | clSetKernelArg(k_assign, 3, sizeof(cl_uint), &na2); | |
| 633 | size_t gs = ((na2 + local_size - 1) / local_size) * local_size; | |
| 634 | clEnqueueNDRangeKernel(queue, k_assign, 1, NULL, | |
| 635 | &gs, &local_size, 0, NULL, NULL); | |
| 636 | clFinish(queue); | |
| 637 | ||
| 638 | free(h_widx); free(h_cids); | |
| 639 | clReleaseMemObject(d_widx); | |
| 640 | clReleaseMemObject(d_cids); | |
| 641 | } | |
| 642 | ||
| 643 | /* Generate random pairs */ | |
| 644 | { | |
| 645 | uint32_t* h_wa = malloc(sizeof(uint32_t) * bench_np); | |
| 646 | uint32_t* h_wb = malloc(sizeof(uint32_t) * bench_np); | |
| 647 | double* h_cnt = malloc(sizeof(double) * bench_np); | |
| 648 | double* h_mi_arr = malloc(sizeof(double) * bench_np); | |
| 649 | uint32_t* h_flg = malloc(sizeof(uint32_t) * bench_np); | |
| 650 | ||
| 651 | for (uint32_t i = 0; i < bench_np; i++) { | |
| 652 | uint32_t a = rand() % vocab; | |
| 653 | uint32_t b = rand() % vocab; | |
| 654 | while (b == a) b = rand() % vocab; | |
| 655 | h_wa[i] = (a < b) ? a : b; | |
| 656 | h_wb[i] = (a < b) ? b : a; | |
| 657 | h_cnt[i] = 1.0 + (rand() % 100); | |
| 658 | h_mi_arr[i] = 0.5 + (double)(rand() % 100) / 50.0; | |
| 659 | h_flg[i] = 0; | |
| 660 | } | |
| 661 | ||
| 662 | clEnqueueWriteBuffer(queue, pair_word_a, CL_FALSE, 0, | |
| 663 | sizeof(uint32_t) * bench_np, h_wa, 0, NULL, NULL); | |
| 664 | clEnqueueWriteBuffer(queue, pair_word_b, CL_FALSE, 0, | |
| 665 | sizeof(uint32_t) * bench_np, h_wb, 0, NULL, NULL); | |
| 666 | clEnqueueWriteBuffer(queue, pair_count, CL_FALSE, 0, | |
| 667 | sizeof(double) * bench_np, h_cnt, 0, NULL, NULL); | |
| 668 | clEnqueueWriteBuffer(queue, pair_mi, CL_FALSE, 0, | |
| 669 | sizeof(double) * bench_np, h_mi_arr, 0, NULL, NULL); | |
| 670 | clEnqueueWriteBuffer(queue, pair_flags, CL_FALSE, 0, | |
| 671 | sizeof(uint32_t) * bench_np, h_flg, 0, NULL, NULL); | |
| 672 | ||
| 673 | clEnqueueWriteBuffer(queue, num_changed, CL_FALSE, 0, | |
| 674 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 675 | clEnqueueWriteBuffer(queue, num_eliminated, CL_FALSE, 0, | |
| 676 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 677 | clEnqueueWriteBuffer(queue, num_merged, CL_FALSE, 0, | |
| 678 | sizeof(uint32_t), &zero, 0, NULL, NULL); | |
| 679 | clFinish(queue); | |
| 680 | ||
| 681 | /* Substitute */ | |
| 682 | cl_uint np = bench_np; | |
| 683 | clSetKernelArg(k_sub_pair, 8, sizeof(cl_uint), &np); | |
| 684 | ||
| 685 | double t0 = now_ms(); | |
| 686 | size_t gs = ((np + local_size - 1) / local_size) * local_size; | |
| 687 | clEnqueueNDRangeKernel(queue, k_sub_pair, 1, NULL, | |
| 688 | &gs, &local_size, 0, NULL, NULL); | |
| 689 | clFinish(queue); | |
| 690 | double t1 = now_ms(); | |
| 691 | ||
| 692 | /* Rebuild */ | |
| 693 | clEnqueueFillBuffer(queue, pht_keys, &pat_ff, 1, 0, | |
| 694 | sizeof(uint64_t) * PAIR_HT_CAPACITY, 0, NULL, NULL); | |
| 695 | clEnqueueFillBuffer(queue, pht_values, &pat_ff, 1, 0, | |
| 696 | sizeof(uint32_t) * PAIR_HT_CAPACITY, 0, NULL, NULL); | |
| 697 | clFinish(queue); | |
| 698 | ||
| 699 | clSetKernelArg(k_rebuild, 0, sizeof(cl_mem), &pair_word_a); | |
| 700 | clSetKernelArg(k_rebuild, 1, sizeof(cl_mem), &pair_word_b); | |
| 701 | clSetKernelArg(k_rebuild, 2, sizeof(cl_mem), &pair_count); | |
| 702 | clSetKernelArg(k_rebuild, 3, sizeof(cl_mem), &pair_mi); | |
| 703 | clSetKernelArg(k_rebuild, 4, sizeof(cl_mem), &pair_flags); | |
| 704 | clSetKernelArg(k_rebuild, 5, sizeof(cl_mem), &pht_keys); | |
| 705 | clSetKernelArg(k_rebuild, 6, sizeof(cl_mem), &pht_values); | |
| 706 | clSetKernelArg(k_rebuild, 7, sizeof(cl_mem), &num_merged); | |
| 707 | clSetKernelArg(k_rebuild, 8, sizeof(cl_uint), &np); | |
| 708 | ||
| 709 | double t2 = now_ms(); | |
| 710 | clEnqueueNDRangeKernel(queue, k_rebuild, 1, NULL, | |
| 711 | &gs, &local_size, 0, NULL, NULL); | |
| 712 | clFinish(queue); | |
| 713 | double t3 = now_ms(); | |
| 714 | ||
| 715 | uint32_t r_nch, r_nel, r_nmg; | |
| 716 | clEnqueueReadBuffer(queue, num_changed, CL_TRUE, 0, | |
| 717 | sizeof(uint32_t), &r_nch, 0, NULL, NULL); | |
| 718 | clEnqueueReadBuffer(queue, num_eliminated, CL_TRUE, 0, | |
| 719 | sizeof(uint32_t), &r_nel, 0, NULL, NULL); | |
| 720 | clEnqueueReadBuffer(queue, num_merged, CL_TRUE, 0, | |
| 721 | sizeof(uint32_t), &r_nmg, 0, NULL, NULL); | |
| 722 | ||
| 723 | printf(" Pairs: %u\n", bench_np); | |
| 724 | printf(" Substitute: %.2f ms\n", t1 - t0); | |
| 725 | printf(" Rebuild: %.2f ms\n", t3 - t2); | |
| 726 | printf(" Total: %.2f ms\n", (t1 - t0) + (t3 - t2)); | |
| 727 | printf(" Changed: %u, Eliminated: %u, Merged: %u\n", | |
| 728 | r_nch, r_nel, r_nmg); | |
| 729 | printf(" Throughput: %.1fM pairs/sec\n", | |
| 730 | bench_np / (((t1 - t0) + (t3 - t2)) / 1000.0) / 1e6); | |
| 731 | ||
| 732 | int pass = (r_nch > 0) && ((t1 - t0) + (t3 - t2) < 1000.0); | |
| 733 | printf(" %s\n\n", pass ? "PASS" : "FAIL"); | |
| 734 | if (pass) pass_count++; else fail_count++; | |
| 735 | ||
| 736 | free(h_wa); free(h_wb); free(h_cnt); free(h_mi_arr); free(h_flg); | |
| 737 | } | |
| 738 | ||
| 739 | /* ─── Summary ─── */ | |
| 740 | ||
| 741 | printf("=== Results: %d PASS, %d FAIL ===\n", pass_count, fail_count); | |
| 742 | ||
| 743 | /* Cleanup */ | |
| 744 | free(src_ht); free(src_as); free(src_sub); free(combined); | |
| 745 | clReleaseMemObject(word_class_id); | |
| 746 | clReleaseMemObject(pair_word_a); | |
| 747 | clReleaseMemObject(pair_word_b); | |
| 748 | clReleaseMemObject(pair_count); | |
| 749 | clReleaseMemObject(pair_mi); | |
| 750 | clReleaseMemObject(pair_flags); | |
| 751 | clReleaseMemObject(pht_keys); | |
| 752 | clReleaseMemObject(pht_values); | |
| 753 | clReleaseMemObject(sec_word); | |
| 754 | clReleaseMemObject(sec_count_buf); | |
| 755 | clReleaseMemObject(num_changed); | |
| 756 | clReleaseMemObject(num_eliminated); | |
| 757 | clReleaseMemObject(num_merged); | |
| 758 | clReleaseKernel(k_assign); | |
| 759 | clReleaseKernel(k_sub_pair); | |
| 760 | clReleaseKernel(k_rebuild); | |
| 761 | clReleaseKernel(k_sub_sec); | |
| 762 | clReleaseProgram(program); | |
| 763 | clReleaseCommandQueue(queue); | |
| 764 | clReleaseContext(ctx); | |
| 765 | ||
| 766 | return fail_count > 0 ? 1 : 0; | |
| 767 | } |