| … | … | @@ -12,6 +12,8 @@ |
| 12 | 12 | |
| 13 | 13 | #include <opencog/atoms/base/Link.h> |
| 14 | 14 | #include <opencog/atoms/base/Node.h> |
| 15 | #include <opencog/atoms/atom_types/atom_types.h> | |
| 16 | #include <opencog/atoms/value/UnisetValue.h> | |
| 15 | 17 | #include <opencog/util/exceptions.h> |
| 16 | 18 | #include <opencog/persist/gpu/GpuStorageNode.h> |
| 17 | 19 | |
| … | … | @@ -309,6 +311,66 @@ void GpuStorageNode::fetchIncomingByType(AtomSpace* as, const Handle& h, Type t) |
| 309 | 311 | void GpuStorageNode::removeAtom(AtomSpace*, const Handle&, bool) {} |
| 310 | 312 | void GpuStorageNode::loadType(AtomSpace*, Type) {} |
| 311 | 313 | |
| 314 | // Values are not persisted on the GPU yet (Step 4). No-ops keep the default | |
| 315 | // BackingStore::runQuery (which caches results via storeValue) from throwing. | |
| 316 | void GpuStorageNode::storeValue(const Handle&, const Handle&) {} | |
| 317 | void GpuStorageNode::loadValue(const Handle&, const Handle&) {} | |
| 318 | ||
| 319 | // ================================================================ | |
| 320 | // GPU-resident query (the runQuery override). | |
| 321 | // | |
| 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. | |
| 326 | ||
| 327 | bool GpuStorageNode::gpu_run_meet(const Handle& query, const Handle& key) | |
| 328 | { | |
| 329 | if (query->get_type() != MEET_LINK || query->get_arity() != 2) return false; | |
| 330 | 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; | |
| 336 | AtomSpace* as = query->getAtomSpace(); | |
| 337 | if (nullptr == as) return false; | |
| 338 | ||
| 339 | 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 | |
| 342 | ensure_incoming(); | |
| 343 | ||
| 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) | |
| 349 | { | |
| 350 | std::vector<uint32_t> links(count); | |
| 351 | gpu_fetch_incoming(&_gpu_table, slot, links.data(), count, &count); | |
| 352 | for (uint32_t ls : links) | |
| 353 | { | |
| 354 | uint16_t lt; uint32_t obuf[8]; uint16_t ar = 8; | |
| 355 | if (gpu_fetch_link(&_gpu_table, ls, <, 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)); | |
| 360 | } | |
| 361 | } | |
| 362 | svp->close(); | |
| 363 | query->setValue(key, svp); | |
| 364 | return true; | |
| 365 | } | |
| 366 | ||
| 367 | void GpuStorageNode::runQuery(const Handle& query, const Handle& key, | |
| 368 | const Handle& meta, bool fresh) | |
| 369 | { | |
| 370 | if (gpu_run_meet(query, key)) return; // GPU-resident fast-path | |
| 371 | BackingStore::runQuery(query, key, meta, fresh); // default: CPU crawl, GPU-served incoming | |
| 372 | } | |
| 373 | ||
| 312 | 374 | // ================================================================ |
| 313 | 375 | // Synchronization |
| 314 | 376 |
| … | … | @@ -50,6 +50,8 @@ private: |
| 50 | 50 | void ensure_incoming(); // build incoming-set index if stale |
| 51 | 51 | Handle fetch_link_to(AtomSpace*, uint32_t link_slot, Type type_filter); |
| 52 | 52 | void fetch_incoming_impl(AtomSpace*, const Handle&, Type type_filter); |
| 53 | // GPU-resident matcher for supported query shapes; true if handled. | |
| 54 | bool gpu_run_meet(const Handle& query, const Handle& key); | |
| 53 | 55 | |
| 54 | 56 | public: |
| 55 | 57 | GpuStorageNode(Type t, const std::string&& uri); |
| … | … | @@ -78,6 +80,18 @@ public: |
| 78 | 80 | void removeAtom(AtomSpace*, const Handle&, bool recursive); |
| 79 | 81 | void loadType(AtomSpace*, Type); |
| 80 | 82 | |
| 83 | // Values are not persisted on the GPU yet (Step 4); no-ops so the | |
| 84 | // default BackingStore::runQuery (which caches results via storeValue) | |
| 85 | // does not throw. | |
| 86 | void storeValue(const Handle& atom, const Handle& key); | |
| 87 | void loadValue(const Handle& atom, const Handle& key); | |
| 88 | ||
| 89 | // GPU-resident query: fast-path supported shapes on the GPU, else | |
| 90 | // fall back to the default (CPU crawl, GPU-served incoming sets). | |
| 91 | void runQuery(const Handle& query, const Handle& key, | |
| 92 | const Handle& metadata_key = Handle::UNDEFINED, | |
| 93 | bool fresh = false); | |
| 94 | ||
| 81 | 95 | // Expose GPU table for verification in tests. |
| 82 | 96 | const GpuAtomTable& gpu_table() const { return _gpu_table; } |
| 83 | 97 |
| … | … | @@ -57,6 +57,7 @@ public: |
| 57 | 57 | void test_v1_multihop(void); |
| 58 | 58 | void test_v4_neurosymbolic(void); |
| 59 | 59 | void test_agent_loop(void); |
| 60 | void test_runquery_gpu(void); | |
| 60 | 61 | }; |
| 61 | 62 | |
| 62 | 63 | // Execute a 3-variable MeetLink; return (X,Z) pairs = (outgoing[0], outgoing[2]). |
| … | … | @@ -709,4 +710,59 @@ void GpuBasicSaveUTest::test_agent_loop(void) |
| 709 | 710 | logger().debug("END TEST: %s", __FUNCTION__); |
| 710 | 711 | } |
| 711 | 712 | |
| 713 | // ============================================================ | |
| 714 | // runQuery GPU override: matching runs on the GPU (not the CPU crawl). | |
| 715 | // (Meet $X:Concept (Inheritance $X animal)) answered via the GPU fast-path. | |
| 716 | ||
| 717 | void GpuBasicSaveUTest::test_runquery_gpu(void) | |
| 718 | { | |
| 719 | logger().debug("BEGIN TEST: %s", __FUNCTION__); | |
| 720 | ||
| 721 | GpuStorageNode* store = new GpuStorageNode(GPU_STORAGE_NODE, "gpu://test"); | |
| 722 | store->open(); | |
| 723 | ||
| 724 | AtomSpacePtr as1 = createAtomSpace(); | |
| 725 | Handle cat = as1->add_node(CONCEPT_NODE, "cat"); | |
| 726 | Handle dog = as1->add_node(CONCEPT_NODE, "dog"); | |
| 727 | Handle bear = as1->add_node(CONCEPT_NODE, "bear"); | |
| 728 | Handle animal = as1->add_node(CONCEPT_NODE, "animal"); | |
| 729 | Handle fish = as1->add_node(CONCEPT_NODE, "fish"); | |
| 730 | Handle salmon = as1->add_node(CONCEPT_NODE, "salmon"); | |
| 731 | as1->add_link(INHERITANCE_LINK, cat, animal); | |
| 732 | as1->add_link(INHERITANCE_LINK, dog, animal); | |
| 733 | as1->add_link(INHERITANCE_LINK, bear, animal); | |
| 734 | as1->add_link(INHERITANCE_LINK, salmon, fish); | |
| 735 | store->storeAtomSpace(as1.get()); | |
| 736 | store->barrier(); | |
| 737 | ||
| 738 | // Query through the real runQuery() API — exercises the GPU fast-path. | |
| 739 | AtomSpacePtr as = createAtomSpace(); | |
| 740 | Handle animal2 = as->add_node(CONCEPT_NODE, "animal"); | |
| 741 | Handle X = as->add_node(VARIABLE_NODE, "$X"); | |
| 742 | Handle vd = as->add_link(TYPED_VARIABLE_LINK, X, as->add_node(TYPE_NODE, "ConceptNode")); | |
| 743 | Handle meet = as->add_link(MEET_LINK, vd, as->add_link(INHERITANCE_LINK, X, animal2)); | |
| 744 | Handle qkey = as->add_node(PREDICATE_NODE, "*gpu-query-key*"); | |
| 745 | ||
| 746 | store->runQuery(meet, qkey); | |
| 747 | ||
| 748 | std::set<std::string> got; | |
| 749 | LinkValuePtr lv = LinkValueCast(meet->getValue(qkey)); | |
| 750 | if (lv) | |
| 751 | for (const Handle& h : lv->to_handle_seq()) | |
| 752 | if (h->is_node()) got.insert(h->get_name()); | |
| 753 | ||
| 754 | std::set<std::string> exp = {"cat", "dog", "bear"}; | |
| 755 | printf("\nrunQuery (GPU override) -> {"); | |
| 756 | for (auto& s : got) printf("%s ", s.c_str()); | |
| 757 | printf("}\n"); | |
| 758 | TSM_ASSERT_EQUALS("runQuery result size 3", got.size(), (size_t)3); | |
| 759 | TSM_ASSERT("runQuery GPU result == {cat,dog,bear}", got == exp); | |
| 760 | ||
| 761 | store->close(); | |
| 762 | delete store; | |
| 763 | ||
| 764 | printf("test_runquery_gpu: PASSED (matching on GPU via runQuery override)\n"); | |
| 765 | logger().debug("END TEST: %s", __FUNCTION__); | |
| 766 | } | |
| 767 | ||
| 712 | 768 | /* ============================= END OF FILE ================= */ |