OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomese-simd commits / 129f4b8

Fix race condition in split write/read loop (fixes #1)
The build() method in do_write() calls send_buffer() on the caller thread, while run() in queue_job() uses the same cl::CommandQueue and cl::Event on the dispatch thread. This race causes "Release Object" crashes when multiple kernel invocations overlap. Fix by deferring both build() and upload_inputs() to the dispatch thread (queue_job), ensuring all OpenCL command queue I/O is serialized in a single thread. This also eliminates per-thread OpenCL initialization overhead in multi-threaded environments like CogServer. Changes: - OpenclJobValue: store uploads in _pending_uploads instead of calling send_buffer() immediately; new upload_inputs() method; track build state with _is_built flag - OpenclNode::queue_job: build kernel if not yet built, then upload inputs before running - OpenclNode::do_write: defer build by storing opencl_node handle instead of calling build() directly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
129f4b8e7a38306b17ed9e6134516cb1b84a2e6b
by plankatron <fleeky@gmail.com> · 2026-02-07T22:42:16.000Z

Changed files (4) +56 -8

examples/atomese-kernel.scm +1 -1
……@@ -141,7 +141,7 @@
141141
142142(cog-set-value!
143143 (Anchor "some place") (Predicate "second vector")
144 (RandomStream 10)))
144 (RandomStream 10))
145145
146146(define second-vec-location
147147 (ValueOf (Anchor "some place") (Predicate "second vector")))
opencog/atoms/opencl/OpenclJobValue.cc +19 -5
……@@ -24,7 +24,7 @@
2424#include <opencog/atomspace/AtomSpace.h>
2525#include <opencog/atoms/base/Link.h>
2626#include <opencog/atoms/core/NumberNode.h>
27#include <opencog/atoms/core/TypeNode.h>
27#include <opencog/atoms/signature/TypeNode.h>
2828#include <opencog/atoms/value/StringValue.h>
2929#include <opencog/atoms/value/ValueFactory.h>
3030#include <opencog/opencl/types/atom_types.h>
……@@ -37,7 +37,8 @@ using namespace opencog;
3737
3838OpenclJobValue::OpenclJobValue(Handle defn) :
3939 LinkValue(OPENCL_JOB_VALUE),
40 _kernel{}
40 _kernel{},
41 _is_built(false)
4142{
4243 if (not defn->is_type(SECTION))
4344 throw RuntimeException(TRACE_INFO,
……@@ -161,10 +162,11 @@ OpenclJobValue::get_floats(const Handle& oclno, ValuePtr vp)
161162 else
162163 ofv = createOpenclFloatValue(*vals);
163164
164 // We created a new createOpenclFloatValue and we know that
165 // the kernel will use it as input. So upload the data now.
165 // Allocate a GPU buffer but don't upload yet. The upload is
166 // deferred to upload_inputs() which runs on the dispatch thread,
167 // avoiding races on the shared OpenCL command queue and event.
166168 ofv->set_context(oclno);
167 ofv->send_buffer(oclno);
169 _pending_uploads.push_back(ofv);
168170 return ofv;
169171}
170172
……@@ -274,6 +276,16 @@ void OpenclJobValue::check_signature(const Handle& kern,
274276
275277// ==============================================================
276278
279/// Upload input buffers to the GPU. This is called on the dispatch
280/// thread (from OpenclNode::queue_job) so that all GPU I/O happens
281/// on one thread, avoiding races on the shared command queue.
282void OpenclJobValue::upload_inputs(const Handle& oclno)
283{
284 for (const OpenclFloatValuePtr& ofv : _pending_uploads)
285 ofv->send_buffer(oclno);
286 _pending_uploads.clear();
287}
288
277289void OpenclJobValue::build(const Handle& oclno)
278290{
279291 if (not oclno->is_type(OPENCL_NODE))
……@@ -315,6 +327,8 @@ void OpenclJobValue::build(const Handle& oclno)
315327 _kernel.setArg(pos, _dim);
316328 pos++;
317329 }
330
331 _is_built = true;
318332}
319333
320334void OpenclJobValue::run(const Handle& oclno)
opencog/atoms/opencl/OpenclJobValue.h +21 -1
……@@ -23,8 +23,10 @@
2323#ifndef _OPENCOG_OPENCL_JOB_VALUE_H
2424#define _OPENCOG_OPENCL_JOB_VALUE_H
2525
26#include <vector>
2627#include <opencog/atoms/opencl/opencl-headers.h>
2728#include <opencog/atoms/value/LinkValue.h>
29#include <opencog/atoms/opencl/OpenclFloatValue.h>
2830
2931namespace opencog
3032{
……@@ -42,13 +44,31 @@ class OpenclJobValue :
4244 friend class OpenclNode;
4345
4446protected:
45 OpenclJobValue(Type t) : LinkValue(t) {}
47 OpenclJobValue(Type t) : LinkValue(t), _is_built(false) {}
4648
4749 Handle _definition;
4850 cl::Kernel _kernel;
4951 size_t _dim;
5052
53 // Buffers created during build() that need uploading to the GPU.
54 // Upload is deferred to upload_inputs(), which runs on the
55 // dispatch thread, avoiding races on the shared command queue.
56 std::vector<OpenclFloatValuePtr> _pending_uploads;
57
58 // Handle to OpenclNode, stored for deferred build in dispatch thread.
59 // This allows build() to be called from queue_job() instead of do_write(),
60 // ensuring all OpenCL kernel object creation happens in a single thread.
61 // This eliminates per-thread OpenCL initialization overhead in multi-
62 // threaded environments like CogServer.
63 Handle _opencl_node;
64 bool _is_built;
65
66 void set_opencl_node(const Handle& h) { _opencl_node = h; }
67 const Handle& get_opencl_node(void) const { return _opencl_node; }
68 bool is_built(void) const { return _is_built; }
69
5170 void build(const Handle&);
71 void upload_inputs(const Handle&);
5272 void run(const Handle&);
5373 void check_signature(const Handle&, const Handle&, const ValueSeq&);
5474
opencog/atoms/opencl/OpenclNode.cc +15 -1
……@@ -300,6 +300,15 @@ void OpenclNode::queue_job(const ValuePtr& vp)
300300 if (vp->is_type(OPENCL_JOB_VALUE))
301301 {
302302 OpenclJobValuePtr ojv = OpenclJobValueCast(vp);
303
304 // Build the kernel if not yet built. This is deferred from
305 // do_write() to ensure all OpenCL kernel object creation
306 // happens in this single dispatch thread, avoiding per-thread
307 // OpenCL initialization overhead.
308 if (not ojv->is_built())
309 ojv->build(ojv->get_opencl_node());
310
311 ojv->upload_inputs(get_handle());
303312 ojv->run(get_handle());
304313 _event_handler.wait();
305314 _qvp->add(ojv);
……@@ -352,8 +361,13 @@ void OpenclNode::do_write(const ValuePtr& vp)
352361
353362 if (vp->is_type(SECTION))
354363 {
364 // Create the job but DON'T build it yet. Building creates
365 // cl::Kernel objects which triggers OpenCL per-thread initialization.
366 // By deferring build() to queue_job(), all OpenCL kernel object
367 // creation happens in the single dispatch thread, eliminating
368 // the per-thread initialization overhead in CogServer.
355369 OpenclJobValuePtr kern = createOpenclJobValue(HandleCast(vp));
356 kern->build(get_handle());
370 kern->set_opencl_node(get_handle());
357371 _dispatch_queue.enqueue(kern);
358372 return;
359373 }