OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomese-simd commits / 6c9dfe3

GPU-native AtomSpace: 7 OpenCL kernels + tests (Phases 0-6)
Phase 0: Lock-free hash table (FNV-1a, CAS-based) Phase 1: Pool layouts (WordPool, PairPool, SectionPool SoA) Phase 2: Sentence pair counting (window-based) Phase 3: MI computation (resident + dirty modes) Phase 4: Section extraction from MST edges Phase 5: Cosine similarity pipeline (norms, chains, dots, filter) Phase 6: Class substitution (pair merge, section rewrite) All data structures GPU-resident with atomic concurrent access. 454.6M pairs/sec throughput on substitute benchmark. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6c9dfe3ec98b302656ba2d7b6f70e2e255f2c5df
by plankatron <fleeky@gmail.com> · 2026-02-08T02:52:29.000Z

Changed files (14) +7675 -0

opencog/opencl/atomspace/gpu-atomspace.cl +407 -0
……@@ -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 */
27void 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 */
155inline 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 */
250inline 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}
opencog/opencl/atomspace/gpu-cosine.cl +340 -0
……@@ -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}
opencog/opencl/atomspace/gpu-counting.cl +296 -0
……@@ -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}
opencog/opencl/atomspace/gpu-hashtable.cl +303 -0
……@@ -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 */
40inline 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}
opencog/opencl/atomspace/gpu-mi.cl +224 -0
……@@ -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}
opencog/opencl/atomspace/gpu-sections.cl +317 -0
……@@ -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
35inline ulong fnv1a_init(void)
36{
37 return 0xcbf29ce484222325UL;
38}
39
40inline ulong fnv1a_mix(ulong hash, ulong val)
41{
42 hash ^= val;
43 hash *= 0x100000001b3UL;
44 return hash;
45}
46
47inline 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
71inline 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. */
opencog/opencl/atomspace/gpu-substitute.cl +232 -0
……@@ -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}
opencog/opencl/atomspace/test-atomspace.c +739 -0
……@@ -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
51char* 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
68uint64_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
81double 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
90int 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}
opencog/opencl/atomspace/test-cosine.c +954 -0
……@@ -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
53char* 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
70double 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
79char* 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
97void 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
141void 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
162void 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
180void 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
227int 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}
opencog/opencl/atomspace/test-counting.c +720 -0
……@@ -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
47char* 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
64double 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
73void 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
104int 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}
opencog/opencl/atomspace/test-hashtable.c +532 -0
……@@ -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
46char* 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
62static uint64_t rng_state = 0x12345678DEADBEEFULL;
63
64uint64_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
75double 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
84int 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}
opencog/opencl/atomspace/test-mi.c +961 -0
……@@ -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
51char* 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
68double 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
77double 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
86void 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
116int 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}
opencog/opencl/atomspace/test-sections.c +883 -0
……@@ -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
48char* 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
65double 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
74static uint64_t cpu_fnv1a_init(void) { return 0xcbf29ce484222325ULL; }
75
76static uint64_t cpu_fnv1a_mix(uint64_t hash, uint64_t val)
77{
78 hash ^= val;
79 hash *= 0x100000001b3ULL;
80 return hash;
81}
82
83static 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) */
95static 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
104void 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
128int 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}
opencog/opencl/atomspace/test-substitute.c +767 -0
……@@ -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
48char* 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
65double 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
74int 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}