OverviewFilesCommitsIssuesDocsReleasesPeers

atomspace-gpu commits / b082b9c

General multi-clause GPU matcher in GpuStorageNode.runQuery
gpu_run_meet now handles (Meet $X body) with one variable and body = a single clause or AndLink of clauses, each: (a) a direct binary link (T $X c)/(T c $X) or (b) an Eval relation (Evaluation (Predicate P) (List $X c))/(List c $X). Per-clause candidate var-bindings via the GPU incoming index; answer = their intersection. Multi-variable joins fall back to default BackingStore::runQuery. Test test_runquery_multiclause: (is-a mammal AND lives-in water) -> {whale, dolphin,seal} via runQuery. All 12 GpuBasicSaveUTest cases pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
b082b9ca74c1e60e8b338bd74b55b0f9b37e869d
by plankatron <fleeky@gmail.com> · 2026-06-26T01:33:55.000Z

Changed files (3) +200 -26

opencog/persist/gpu/GpuStorageNode.cc +135 -26
……@@ -10,6 +10,9 @@
1010 * SPDX-License-Identifier: AGPL-3.0-or-later
1111 */
1212
13#include <algorithm>
14#include <set>
15
1316#include <opencog/atoms/base/Link.h>
1417#include <opencog/atoms/base/Node.h>
1518#include <opencog/atoms/atom_types/atom_types.h>
……@@ -317,47 +320,153 @@ void GpuStorageNode::storeValue(const Handle&, const Handle&) {}
317320void GpuStorageNode::loadValue(const Handle&, const Handle&) {}
318321
319322// ================================================================
320// GPU-resident query (the runQuery override).
323// GPU-resident query (the runQuery override) — GENERAL multi-clause.
321324//
322// Fast-path for (Meet <vardecl> (Inheritance $X <const>)) — a single
323// typed clause with one variable in position 0 and a constant in position 1.
324// The match is the incoming InheritanceLinks of <const> on the GPU; the
325// groundings are their position-0 atoms. Returns true if handled.
325// Handles (Meet <vardecl> body) where there is exactly ONE variable and
326// body is a single clause or an AndLink of clauses, each clause one of:
327// (a) a direct binary link (T $X c) / (T c $X) [e.g. InheritanceLink]
328// (b) an Eval relation (Evaluation (Predicate P) (List $X c)) / (List c $X)
329// Each clause's candidate var-bindings are computed via the GPU incoming
330// index; the answer is their intersection. Anything else (multi-variable
331// joins, nesting, etc.) returns false → default BackingStore::runQuery.
332
333// Single query variable, or UNDEFINED if not exactly one.
334static Handle gpu_single_var(const Handle& vardecl)
335{
336 Type t = vardecl->get_type();
337 if (VARIABLE_NODE == t) return vardecl;
338 if (TYPED_VARIABLE_LINK == t) return vardecl->getOutgoingAtom(0);
339 if (VARIABLE_LIST == t or VARIABLE_SET == t)
340 {
341 if (1 != vardecl->get_arity()) return Handle::UNDEFINED;
342 return gpu_single_var(vardecl->getOutgoingAtom(0));
343 }
344 return Handle::UNDEFINED;
345}
346
347bool GpuStorageNode::gpu_clause_candidates(const Handle& clause, const Handle& var,
348 std::set<uint32_t>& out)
349{
350 Type ct = clause->get_type();
351
352 // --- shape (b): Eval relation (Predicate, List) ---
353 if (EVALUATION_LINK == ct and 2 == clause->get_arity() and
354 LIST_LINK == clause->getOutgoingAtom(1)->get_type())
355 {
356 const Handle& pred = clause->getOutgoingAtom(0);
357 const Handle& lst = clause->getOutgoingAtom(1);
358 if (2 != lst->get_arity()) return false;
359 int varpos = (lst->getOutgoingAtom(0) == var) ? 0
360 : (lst->getOutgoingAtom(1) == var) ? 1 : -1;
361 if (varpos < 0) return false;
362 const Handle& cst = lst->getOutgoingAtom(1 - varpos);
363 if (not cst->is_node() or not pred->is_node()) return false;
364 auto pit = _handle_to_slot.find(pred); if (pit == _handle_to_slot.end()) return false;
365 auto cit = _handle_to_slot.find(cst); if (cit == _handle_to_slot.end()) return false;
366 uint32_t pslot = pit->second, cslot = cit->second, cstpos = 1 - varpos;
367
368 uint32_t n = 0;
369 gpu_fetch_incoming(&_gpu_table, cslot, nullptr, 0, &n);
370 if (0 == n) return true;
371 std::vector<uint32_t> cin(n);
372 gpu_fetch_incoming(&_gpu_table, cslot, cin.data(), n, &n);
373 for (uint32_t lslot : cin) // candidate ListLink slots
374 {
375 uint16_t lt; uint32_t lob[8]; uint16_t lar = 8;
376 if (gpu_fetch_link(&_gpu_table, lslot, &lt, lob, &lar) != 0) continue;
377 if ((Type)lt != LIST_LINK or lar != 2) continue;
378 if (lob[cstpos] != cslot) continue; // const in the right list position
379 uint32_t binding = lob[varpos];
380 // require an EvaluationLink(pred, thisList)
381 uint32_t m = 0;
382 gpu_fetch_incoming(&_gpu_table, lslot, nullptr, 0, &m);
383 if (0 == m) continue;
384 std::vector<uint32_t> lin(m);
385 gpu_fetch_incoming(&_gpu_table, lslot, lin.data(), m, &m);
386 for (uint32_t eslot : lin)
387 {
388 uint16_t et; uint32_t eob[8]; uint16_t ear = 8;
389 if (gpu_fetch_link(&_gpu_table, eslot, &et, eob, &ear) != 0) continue;
390 if ((Type)et == EVALUATION_LINK and ear == 2 and
391 eob[0] == pslot and eob[1] == lslot)
392 { out.insert(binding); break; }
393 }
394 }
395 return true;
396 }
397
398 // --- shape (a): direct binary link (T $X c) / (T c $X) ---
399 if (2 == clause->get_arity())
400 {
401 const Handle& o0 = clause->getOutgoingAtom(0);
402 const Handle& o1 = clause->getOutgoingAtom(1);
403 int varpos = (o0 == var) ? 0 : (o1 == var) ? 1 : -1;
404 if (varpos < 0) return false;
405 const Handle& cst = (0 == varpos) ? o1 : o0;
406 if (not cst->is_node()) return false;
407 auto it = _handle_to_slot.find(cst); if (it == _handle_to_slot.end()) return false;
408 uint32_t cslot = it->second, cstpos = 1 - varpos;
409
410 uint32_t n = 0;
411 gpu_fetch_incoming(&_gpu_table, cslot, nullptr, 0, &n);
412 if (0 == n) return true;
413 std::vector<uint32_t> cin(n);
414 gpu_fetch_incoming(&_gpu_table, cslot, cin.data(), n, &n);
415 for (uint32_t ls : cin)
416 {
417 uint16_t lt; uint32_t ob[8]; uint16_t ar = 8;
418 if (gpu_fetch_link(&_gpu_table, ls, &lt, ob, &ar) != 0) continue;
419 if ((Type)lt != ct or ar != 2) continue;
420 if (ob[cstpos] != cslot) continue;
421 out.insert(ob[varpos]);
422 }
423 return true;
424 }
425
426 return false;
427}
326428
327429bool GpuStorageNode::gpu_run_meet(const Handle& query, const Handle& key)
328430{
329431 if (query->get_type() != MEET_LINK || query->get_arity() != 2) return false;
432 Handle var = gpu_single_var(query->getOutgoingAtom(0));
433 if (nullptr == var || var->get_type() != VARIABLE_NODE) return false; // single-var only
434
330435 const Handle& body = query->getOutgoingAtom(1);
331 if (body->get_type() != INHERITANCE_LINK || body->get_arity() != 2) return false;
332 const Handle& var = body->getOutgoingAtom(0);
333 const Handle& cst = body->getOutgoingAtom(1);
334 if (var->get_type() != VARIABLE_NODE) return false; // shape: (Inheritance $X const)
335 if (not cst->is_node()) return false;
436 HandleSeq clauses;
437 if (AND_LINK == body->get_type()) clauses = body->getOutgoingSet();
438 else clauses.push_back(body);
439 if (clauses.empty()) return false;
440
336441 AtomSpace* as = query->getAtomSpace();
337442 if (nullptr == as) return false;
338443
339444 std::lock_guard<std::mutex> lk(_mtx);
340 auto it = _handle_to_slot.find(cst);
341 if (it == _handle_to_slot.end()) return false; // const not on GPU → default path
342445 ensure_incoming();
343446
344 uint32_t slot = it->second, count = 0;
345 gpu_fetch_incoming(&_gpu_table, slot, nullptr, 0, &count);
346
347 UnisetValuePtr svp(createUnisetValue());
348 if (count > 0)
447 std::set<uint32_t> result;
448 bool first = true;
449 for (const Handle& c : clauses)
349450 {
350 std::vector<uint32_t> links(count);
351 gpu_fetch_incoming(&_gpu_table, slot, links.data(), count, &count);
352 for (uint32_t ls : links)
451 std::set<uint32_t> cand;
452 if (not gpu_clause_candidates(c, var, cand)) return false; // unsupported → fallback
453 if (first) { result = std::move(cand); first = false; }
454 else
353455 {
354 uint16_t lt; uint32_t obuf[8]; uint16_t ar = 8;
355 if (gpu_fetch_link(&_gpu_table, ls, &lt, obuf, &ar) != 0) continue;
356 if ((Type)lt != INHERITANCE_LINK || ar != 2) continue;
357 if ((uint32_t)obuf[1] != slot) continue; // const must be in position 1
358 Handle x = _tlb.getAtom((UUID)obuf[0]);
359 if (x) svp->add(as->add_atom(x));
456 std::set<uint32_t> inter;
457 std::set_intersection(result.begin(), result.end(),
458 cand.begin(), cand.end(),
459 std::inserter(inter, inter.begin()));
460 result = std::move(inter);
360461 }
462 if (result.empty()) break;
463 }
464
465 UnisetValuePtr svp(createUnisetValue());
466 for (uint32_t s : result)
467 {
468 Handle h = _tlb.getAtom((UUID)s);
469 if (h) svp->add(as->add_atom(h));
361470 }
362471 svp->close();
363472 query->setValue(key, svp);
opencog/persist/gpu/GpuStorageNode.h +4 -0
……@@ -15,6 +15,7 @@
1515#define _OPENCOG_GPU_STORAGE_NODE_H
1616
1717#include <mutex>
18#include <set>
1819#include <unordered_map>
1920#include <vector>
2021
……@@ -52,6 +53,9 @@ private:
5253 void fetch_incoming_impl(AtomSpace*, const Handle&, Type type_filter);
5354 // GPU-resident matcher for supported query shapes; true if handled.
5455 bool gpu_run_meet(const Handle& query, const Handle& key);
56 // Candidate var-bindings (atom slots) for one clause, via the GPU index.
57 bool gpu_clause_candidates(const Handle& clause, const Handle& var,
58 std::set<uint32_t>& out);
5559
5660public:
5761 GpuStorageNode(Type t, const std::string&& uri);
tests/persist/gpu/GpuBasicSaveUTest.cxxtest +61 -0
……@@ -58,6 +58,7 @@ public:
5858 void test_v4_neurosymbolic(void);
5959 void test_agent_loop(void);
6060 void test_runquery_gpu(void);
61 void test_runquery_multiclause(void);
6162};
6263
6364// Execute a 3-variable MeetLink; return (X,Z) pairs = (outgoing[0], outgoing[2]).
……@@ -765,4 +766,64 @@ void GpuBasicSaveUTest::test_runquery_gpu(void)
765766 logger().debug("END TEST: %s", __FUNCTION__);
766767}
767768
769// ============================================================
770// General multi-clause GPU matcher via runQuery: a conjunction mixing a
771// direct InheritanceLink clause + an Eval-relation clause, single variable.
772
773void GpuBasicSaveUTest::test_runquery_multiclause(void)
774{
775 logger().debug("BEGIN TEST: %s", __FUNCTION__);
776
777 GpuStorageNode* store = new GpuStorageNode(GPU_STORAGE_NODE, "gpu://test");
778 store->open();
779
780 AtomSpacePtr as1 = createAtomSpace();
781 auto isa = [&](const char* x, const char* t) {
782 as1->add_link(INHERITANCE_LINK,
783 as1->add_node(CONCEPT_NODE, x), as1->add_node(CONCEPT_NODE, t)); };
784 auto lives = [&](const char* x, const char* p) {
785 as1->add_link(EVALUATION_LINK, as1->add_node(PREDICATE_NODE, "lives-in"),
786 as1->add_link(LIST_LINK,
787 as1->add_node(CONCEPT_NODE, x), as1->add_node(CONCEPT_NODE, p))); };
788 isa("whale","mammal"); isa("dolphin","mammal"); isa("seal","mammal");
789 isa("bat","mammal"); isa("cat","mammal");
790 isa("shark","fish"); isa("salmon","fish");
791 lives("whale","water"); lives("dolphin","water"); lives("seal","water");
792 lives("shark","water"); lives("salmon","water");
793 lives("bat","air"); lives("cat","land");
794 store->storeAtomSpace(as1.get());
795 store->barrier();
796
797 // (Meet $X (And (Inheritance $X mammal) (Eval (Pred lives-in)(List $X water))))
798 AtomSpacePtr as = createAtomSpace();
799 Handle X = as->add_node(VARIABLE_NODE, "$X");
800 Handle vd = as->add_link(TYPED_VARIABLE_LINK, X, as->add_node(TYPE_NODE, "ConceptNode"));
801 Handle c1 = as->add_link(INHERITANCE_LINK, X, as->add_node(CONCEPT_NODE, "mammal"));
802 Handle c2 = as->add_link(EVALUATION_LINK, as->add_node(PREDICATE_NODE, "lives-in"),
803 as->add_link(LIST_LINK, X, as->add_node(CONCEPT_NODE, "water")));
804 Handle meet = as->add_link(MEET_LINK, vd, as->add_link(AND_LINK, c1, c2));
805 Handle qkey = as->add_node(PREDICATE_NODE, "*mc-key*");
806
807 store->runQuery(meet, qkey);
808
809 std::set<std::string> got;
810 LinkValuePtr lv = LinkValueCast(meet->getValue(qkey));
811 if (lv)
812 for (const Handle& h : lv->to_handle_seq())
813 if (h->is_node()) got.insert(h->get_name());
814
815 std::set<std::string> exp = {"whale", "dolphin", "seal"};
816 printf("\nrunQuery multi-clause (is-a mammal AND lives-in water) -> {");
817 for (auto& s : got) printf("%s ", s.c_str());
818 printf("}\n");
819 TSM_ASSERT_EQUALS("multi-clause result size 3", got.size(), (size_t)3);
820 TSM_ASSERT("multi-clause GPU result == {whale,dolphin,seal}", got == exp);
821
822 store->close();
823 delete store;
824
825 printf("test_runquery_multiclause: PASSED (general multi-clause matcher on GPU)\n");
826 logger().debug("END TEST: %s", __FUNCTION__);
827}
828
768829/* ============================= END OF FILE ================= */