| … | … | @@ -141,7 +141,7 @@ |
| 141 | 141 | |
| 142 | 142 | (cog-set-value! |
| 143 | 143 | (Anchor "some place") (Predicate "second vector") |
| 144 | (RandomStream 10))) | |
| 144 | (RandomStream 10)) | |
| 145 | 145 | |
| 146 | 146 | (define second-vec-location |
| 147 | 147 | (ValueOf (Anchor "some place") (Predicate "second vector"))) |
| … | … | @@ -24,7 +24,7 @@ |
| 24 | 24 | #include <opencog/atomspace/AtomSpace.h> |
| 25 | 25 | #include <opencog/atoms/base/Link.h> |
| 26 | 26 | #include <opencog/atoms/core/NumberNode.h> |
| 27 | #include <opencog/atoms/core/TypeNode.h> | |
| 27 | #include <opencog/atoms/signature/TypeNode.h> | |
| 28 | 28 | #include <opencog/atoms/value/StringValue.h> |
| 29 | 29 | #include <opencog/atoms/value/ValueFactory.h> |
| 30 | 30 | #include <opencog/opencl/types/atom_types.h> |
| … | … | @@ -37,7 +37,8 @@ using namespace opencog; |
| 37 | 37 | |
| 38 | 38 | OpenclJobValue::OpenclJobValue(Handle defn) : |
| 39 | 39 | LinkValue(OPENCL_JOB_VALUE), |
| 40 | _kernel{} | |
| 40 | _kernel{}, | |
| 41 | _is_built(false) | |
| 41 | 42 | { |
| 42 | 43 | if (not defn->is_type(SECTION)) |
| 43 | 44 | throw RuntimeException(TRACE_INFO, |
| … | … | @@ -161,10 +162,11 @@ OpenclJobValue::get_floats(const Handle& oclno, ValuePtr vp) |
| 161 | 162 | else |
| 162 | 163 | ofv = createOpenclFloatValue(*vals); |
| 163 | 164 | |
| 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. | |
| 166 | 168 | ofv->set_context(oclno); |
| 167 | ofv->send_buffer(oclno); | |
| 169 | _pending_uploads.push_back(ofv); | |
| 168 | 170 | return ofv; |
| 169 | 171 | } |
| 170 | 172 | |
| … | … | @@ -274,6 +276,16 @@ void OpenclJobValue::check_signature(const Handle& kern, |
| 274 | 276 | |
| 275 | 277 | // ============================================================== |
| 276 | 278 | |
| 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. | |
| 282 | void 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 | ||
| 277 | 289 | void OpenclJobValue::build(const Handle& oclno) |
| 278 | 290 | { |
| 279 | 291 | if (not oclno->is_type(OPENCL_NODE)) |
| … | … | @@ -315,6 +327,8 @@ void OpenclJobValue::build(const Handle& oclno) |
| 315 | 327 | _kernel.setArg(pos, _dim); |
| 316 | 328 | pos++; |
| 317 | 329 | } |
| 330 | ||
| 331 | _is_built = true; | |
| 318 | 332 | } |
| 319 | 333 | |
| 320 | 334 | void OpenclJobValue::run(const Handle& oclno) |
| … | … | @@ -23,8 +23,10 @@ |
| 23 | 23 | #ifndef _OPENCOG_OPENCL_JOB_VALUE_H |
| 24 | 24 | #define _OPENCOG_OPENCL_JOB_VALUE_H |
| 25 | 25 | |
| 26 | #include <vector> | |
| 26 | 27 | #include <opencog/atoms/opencl/opencl-headers.h> |
| 27 | 28 | #include <opencog/atoms/value/LinkValue.h> |
| 29 | #include <opencog/atoms/opencl/OpenclFloatValue.h> | |
| 28 | 30 | |
| 29 | 31 | namespace opencog |
| 30 | 32 | { |
| … | … | @@ -42,13 +44,31 @@ class OpenclJobValue : |
| 42 | 44 | friend class OpenclNode; |
| 43 | 45 | |
| 44 | 46 | protected: |
| 45 | OpenclJobValue(Type t) : LinkValue(t) {} | |
| 47 | OpenclJobValue(Type t) : LinkValue(t), _is_built(false) {} | |
| 46 | 48 | |
| 47 | 49 | Handle _definition; |
| 48 | 50 | cl::Kernel _kernel; |
| 49 | 51 | size_t _dim; |
| 50 | 52 | |
| 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 | ||
| 51 | 70 | void build(const Handle&); |
| 71 | void upload_inputs(const Handle&); | |
| 52 | 72 | void run(const Handle&); |
| 53 | 73 | void check_signature(const Handle&, const Handle&, const ValueSeq&); |
| 54 | 74 |
| … | … | @@ -300,6 +300,15 @@ void OpenclNode::queue_job(const ValuePtr& vp) |
| 300 | 300 | if (vp->is_type(OPENCL_JOB_VALUE)) |
| 301 | 301 | { |
| 302 | 302 | 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()); | |
| 303 | 312 | ojv->run(get_handle()); |
| 304 | 313 | _event_handler.wait(); |
| 305 | 314 | _qvp->add(ojv); |
| … | … | @@ -352,8 +361,13 @@ void OpenclNode::do_write(const ValuePtr& vp) |
| 352 | 361 | |
| 353 | 362 | if (vp->is_type(SECTION)) |
| 354 | 363 | { |
| 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. | |
| 355 | 369 | OpenclJobValuePtr kern = createOpenclJobValue(HandleCast(vp)); |
| 356 | kern->build(get_handle()); | |
| 370 | kern->set_opencl_node(get_handle()); | |
| 357 | 371 | _dispatch_queue.enqueue(kern); |
| 358 | 372 | return; |
| 359 | 373 | } |