Graph Framework
Loading...
Searching...
No Matches
metal_context.hpp
Go to the documentation of this file.
1//------------------------------------------------------------------------------
6//------------------------------------------------------------------------------
7
8#ifndef metal_context_h
9#define metal_context_h
10
11#import <Metal/Metal.h>
12
13#include "random.hpp"
14#include "timing.hpp"
15#include "piecewise.hpp"
16
18namespace gpu {
19//------------------------------------------------------------------------------
23//------------------------------------------------------------------------------
24 template<bool SAFE_MATH=false>
26 private:
28 id<MTLDevice> device;
30 id<MTLCommandQueue> queue;
32 std::unordered_map<graph::leaf_node<float, SAFE_MATH> *, id<MTLBuffer>> kernel_arguments;
34 std::unordered_map<void *, id<MTLTexture>> texture_arguments;
36 id<MTLCommandBuffer> command_buffer;
38 id<MTLLibrary> library;
40 std::unordered_map<std::string, std::vector<MTLMutability>> bufferMutability;
41
42 public:
44 constexpr static size_t random_state_scale = 3000;
46 constexpr static size_t random_state_size = 1024*random_state_scale;
47
50
51//------------------------------------------------------------------------------
55//------------------------------------------------------------------------------
56 static size_t max_concurrency() {
57 return MTLCopyAllDevices().count;
58 }
59
60//------------------------------------------------------------------------------
62//------------------------------------------------------------------------------
63 static std::string device_type() {
64 return "Metal GPU";
65 }
66
67//------------------------------------------------------------------------------
71//------------------------------------------------------------------------------
72 metal_context(const size_t index) :
73 device([MTLCopyAllDevices() objectAtIndex:index]),
74 queue([device newCommandQueue]) {}
75
76//------------------------------------------------------------------------------
82//------------------------------------------------------------------------------
83 void compile(const std::string kernel_source,
84 std::vector<std::string> names,
85 const bool add_reduction=false) {
86 NSError *error;
87 library = [device newLibraryWithSource:[NSString stringWithCString:kernel_source.c_str()
88 encoding:NSUTF8StringEncoding]
89 options:compile_options()
90 error:&error];
91
92 if (error) {
93 NSLog(@"%@", error);
94 }
95
96 if (jit::verbose) {
97 std::cout << "Metal GPU info." << std::endl
98 << " Max thread group memory : " << device.maxThreadgroupMemoryLength << std::endl
99 << " Max thread per group : " << device.maxThreadsPerThreadgroup.width << std::endl
100 << " Device name : " << [device.name cStringUsingEncoding:NSString.defaultCStringEncoding] << std::endl
101 << " Architecture : " << [device.architecture.name cStringUsingEncoding:NSString.defaultCStringEncoding] << std::endl
102 << " Max buffer length : " << device.maxBufferLength << std::endl
103 << " Max working set : " << device.recommendedMaxWorkingSetSize << std::endl;
104 }
105 }
106
107//------------------------------------------------------------------------------
119//------------------------------------------------------------------------------
120 std::function<void(void)> create_kernel_call(const std::string kernel_name,
125 const size_t num_rays,
126 const jit::texture1d_list &tex1d_list,
127 const jit::texture2d_list &tex2d_list) {
128 NSError *error;
129
130 MTLFunctionDescriptor *funcDesc = [MTLFunctionDescriptor new];
131 funcDesc.options = MTLFunctionOptionNone;
132 funcDesc.name = [NSString stringWithCString:kernel_name.c_str()
133 encoding:NSUTF8StringEncoding];
134
135 id<MTLFunction> function = [library newFunctionWithDescriptor:funcDesc
136 error:&error];
137
138 MTLComputePipelineDescriptor *compute = [MTLComputePipelineDescriptor new];
139 compute.threadGroupSizeIsMultipleOfThreadExecutionWidth = YES;
140 compute.computeFunction = function;
141 compute.maxTotalThreadsPerThreadgroup = 1024;
142 for (size_t i = 0, ie = bufferMutability[kernel_name].size(); i < ie; i++) {
143 compute.buffers[i].mutability = bufferMutability[kernel_name][i];
144 }
145
146 id<MTLComputePipelineState> pipeline = [device newComputePipelineStateWithDescriptor:compute
147 options:MTLPipelineOptionNone
148 reflection:NULL
149 error:&error];
150
151 if (error) {
152 NSLog(@"%@", error);
153 }
154
155 std::vector<id<MTLBuffer>> buffers;
156 std::unordered_set<graph::leaf_node<float, SAFE_MATH> *> needed_buffers;
157
158 const size_t buffer_element_size = sizeof(float);
159 for (graph::shared_variable<float, SAFE_MATH> &input : inputs) {
160 if (!kernel_arguments.contains(input.get())) {
161 backend::buffer<float> buffer = input->evaluate();
162 kernel_arguments[input.get()] = [device newBufferWithBytes:buffer.data()
163 length:buffer.size()*buffer_element_size
164 options:MTLResourceStorageModeShared];
165 }
166 if (!needed_buffers.contains(input.get())) {
167 buffers.push_back(kernel_arguments[input.get()]);
168 needed_buffers.insert(input.get());
169 }
170 }
173 if (!kernel_arguments.contains(output.get())) {
174 kernel_arguments[output.get()] = [device newBufferWithLength:num_rays*sizeof(float)
175 options:MTLResourceStorageModeShared];
176 }
177 if (!needed_buffers.contains(output.get())) {
178 buffers.push_back(kernel_arguments[output.get()]);
179 needed_buffers.insert(output.get());
180 }
181 }
182 }
183 for (graph::shared_variable<float, SAFE_MATH> &atomic : atomics) {
184 if (!kernel_arguments.contains(atomic.get())) {
185 kernel_arguments[atomic.get()] = [device newBufferWithLength:atomic->size()*buffer_element_size
186 options:MTLResourceStorageModeShared];
187 }
188 if (!needed_buffers.contains(atomic.get())) {
189 buffers.push_back(kernel_arguments[atomic.get()]);
190 needed_buffers.insert(atomic.get());
191 }
192 }
193 if (state.get()) {
194 if (!kernel_arguments.contains(state.get())) {
195 kernel_arguments[state.get()] = [device newBufferWithBytes:state->data()
196 length:state->get_size_bytes()
197 options:MTLResourceCPUCacheModeWriteCombined |
198 MTLResourceStorageModeShared |
199 MTLResourceHazardTrackingModeUntracked];
200 }
201 buffers.push_back(kernel_arguments[state.get()]);
202 }
203
204 std::vector<id<MTLTexture>> textures;
205 command_buffer = [queue commandBuffer];
206 id<MTLBlitCommandEncoder> encoder = [command_buffer blitCommandEncoder];
207 for (auto &[data, size] : tex1d_list) {
208 if (!texture_arguments.contains(data)) {
209 MTLTextureDescriptor *descriptor = [MTLTextureDescriptor new];
210 descriptor.textureType = MTLTextureType1D;
211 descriptor.pixelFormat = MTLPixelFormatR32Float;
212 descriptor.width = size;
213 descriptor.storageMode = MTLStorageModeShared;
214 descriptor.cpuCacheMode = MTLCPUCacheModeWriteCombined;
215 descriptor.hazardTrackingMode = MTLHazardTrackingModeUntracked;
216 descriptor.usage = MTLTextureUsageShaderRead;
217 texture_arguments[data] = [device newTextureWithDescriptor:descriptor];
218 [texture_arguments[data] replaceRegion:MTLRegionMake1D(0, size)
219 mipmapLevel:0
220 withBytes:reinterpret_cast<float *> (data)
221 bytesPerRow:4*size];
222
223 [encoder optimizeContentsForGPUAccess:texture_arguments[data]];
224 }
225 textures.push_back(texture_arguments[data]);
226 }
227 for (auto &[data, size] : tex2d_list) {
228 if (!texture_arguments.contains(data)) {
229 MTLTextureDescriptor *descriptor = [MTLTextureDescriptor new];
230 descriptor.textureType = MTLTextureType2D;
231 descriptor.pixelFormat = MTLPixelFormatR32Float;
232 descriptor.width = size[1];
233 descriptor.height = size[0];
234 descriptor.storageMode = MTLStorageModeShared;
235 descriptor.cpuCacheMode = MTLCPUCacheModeWriteCombined;
236 descriptor.hazardTrackingMode = MTLHazardTrackingModeUntracked;
237 descriptor.usage = MTLTextureUsageShaderRead;
238 texture_arguments[data] = [device newTextureWithDescriptor:descriptor];
239 [texture_arguments[data] replaceRegion:MTLRegionMake2D(0, 0, size[1], size[0])
240 mipmapLevel:0
241 withBytes:reinterpret_cast<float *> (data)
242 bytesPerRow:4*size[1]];
243
244 [encoder optimizeContentsForGPUAccess:texture_arguments[data]];
245 }
246 textures.push_back(texture_arguments[data]);
247 }
248 [encoder endEncoding];
249 [command_buffer commit];
250
251 std::vector<NSUInteger> offsets(buffers.size(), 0);
252 NSRange range = NSMakeRange(0, buffers.size());
253 NSRange tex_range = NSMakeRange(0, textures.size());
254
255 NSUInteger total_parallel = state.get() ? state->size() : num_rays;
256 NSUInteger thread_width = pipeline.threadExecutionWidth;
257 NSUInteger threads_per_group = total_parallel < pipeline.maxTotalThreadsPerThreadgroup ?
258 thread_width :
259 pipeline.maxTotalThreadsPerThreadgroup;
260 NSUInteger thread_groups = total_parallel/threads_per_group + (total_parallel%threads_per_group ? 1 : 0);
261
262 if (jit::verbose) {
263 std::cout << " Kernel name : " << kernel_name << std::endl
264 << " Thread execution width : " << thread_width << std::endl
265 << " Threads per group : " << threads_per_group << std::endl
266 << " Number of groups : " << thread_groups << std::endl
267 << " Total problem size : " << threads_per_group*thread_groups << std::endl
268 << " Total parallel size : " << total_parallel << std::endl
269 << " Current allocation size : " << device.currentAllocatedSize << std::endl;
270 }
271
272 if (state.get()) {
273
274 return [this, num_rays, pipeline, buffers, offsets, range, tex_range, thread_groups, threads_per_group, textures
275#ifdef PROFILE_KERNELS
276 , kernel_name
277#endif
278 ] () mutable {
279 command_buffer = [queue commandBuffer];
280 for (NSUInteger i = 0, ie = thread_groups*threads_per_group; i < num_rays; i += ie) {
281 id<MTLComputeCommandEncoder> encoder = [command_buffer computeCommandEncoderWithDispatchType:MTLDispatchTypeSerial];
282
283 [encoder setComputePipelineState:pipeline];
284 [encoder setBuffers:buffers.data()
285 offsets:offsets.data()
286 withRange:range];
287 [encoder setBytes:&i
288 length:sizeof(uint32_t)
289 atIndex:buffers.size()];
290 [encoder setTextures:textures.data()
291 withRange:tex_range];
292
293 [encoder dispatchThreadgroups:MTLSizeMake(thread_groups, 1, 1)
294 threadsPerThreadgroup:MTLSizeMake(threads_per_group, 1, 1)];
295 [encoder endEncoding];
296 }
297#ifdef PROFILE_KERNELS
298 [command_buffer addCompletedHandler:[kernel_name](id<MTLCommandBuffer> commandBuffer) {
299 std::cout << std::endl << " " << kernel_name << " : " << commandBuffer.GPUEndTime - commandBuffer.GPUStartTime << " s" << std::endl << std::endl;
300 }];
301#endif
302 [command_buffer commit];
303 };
304 } else {
305 return [this, pipeline, buffers, offsets, range, tex_range, thread_groups, threads_per_group, textures
306#ifdef PROFILE_KERNELS
307 , kernel_name
308#endif
309 ] () mutable {
310 command_buffer = [queue commandBuffer];
311 id<MTLComputeCommandEncoder> encoder = [command_buffer computeCommandEncoderWithDispatchType:MTLDispatchTypeSerial];
312
313 [encoder setComputePipelineState:pipeline];
314 [encoder setBuffers:buffers.data()
315 offsets:offsets.data()
316 withRange:range];
317 [encoder setTextures:textures.data()
318 withRange:tex_range];
319
320 [encoder dispatchThreadgroups:MTLSizeMake(thread_groups, 1, 1)
321 threadsPerThreadgroup:MTLSizeMake(threads_per_group, 1, 1)];
322 [encoder endEncoding];
323#ifdef PROFILE_KERNELS
324 [command_buffer addCompletedHandler:[kernel_name](id<MTLCommandBuffer> commandBuffer) {
325 std::cout << std::endl << " " << kernel_name << " : " << commandBuffer.GPUEndTime - commandBuffer.GPUStartTime << " s" << std::endl << std::endl;
326 }];
327#endif
328 [command_buffer commit];
329 };
330 }
331 }
332
333//------------------------------------------------------------------------------
339//------------------------------------------------------------------------------
340 std::function<float(void)> create_max_call(graph::shared_leaf<float, SAFE_MATH> &argument,
341 std::function<void(void)> run) {
342 NSError *error;
343
344 MTLFunctionDescriptor *funcDesc = [MTLFunctionDescriptor new];
345 funcDesc.options = MTLFunctionOptionNone;
346 funcDesc.name = @"max_reduction";
347
348 id<MTLFunction> function = [library newFunctionWithDescriptor:funcDesc
349 error:&error];
350
351 MTLComputePipelineDescriptor *compute = [MTLComputePipelineDescriptor new];
352 compute.threadGroupSizeIsMultipleOfThreadExecutionWidth = YES;
353 compute.computeFunction = function;
354 compute.maxTotalThreadsPerThreadgroup = 1024;
355 compute.buffers[0].mutability = MTLMutabilityImmutable;
356
357 id<MTLComputePipelineState> max_state = [device newComputePipelineStateWithDescriptor:compute
358 options:MTLPipelineOptionNone
359 reflection:NULL
360 error:&error];
361 if (error) {
362 NSLog(@"%@", error);
363 }
364
365 id<MTLBuffer> result = [device newBufferWithLength:sizeof(float)
366 options:MTLResourceStorageModeShared];
367
368 id<MTLBuffer> buffer = kernel_arguments[argument.get()];
369
370 NSUInteger threads_per_group = max_state.maxTotalThreadsPerThreadgroup;
371 NSUInteger thread_width = max_state.threadExecutionWidth;
372 if (jit::verbose) {
373 std::cout << " Kernel name : max_reduction" << std::endl;
374 std::cout << " Thread execution width : " << thread_width << std::endl;
375 std::cout << " Threads per group : " << threads_per_group << std::endl;
376 std::cout << " Number of groups : " << 1 << std::endl;
377 std::cout << " Total problem size : " << threads_per_group*1 << std::endl;
378 }
379
380 return [this, run, buffer, result, max_state] () mutable {
381 run();
382 command_buffer = [queue commandBuffer];
383
384 id<MTLComputeCommandEncoder> encoder = [command_buffer computeCommandEncoderWithDispatchType:MTLDispatchTypeSerial];
385
386 [encoder setComputePipelineState:max_state];
387 [encoder setBuffer:buffer offset:0 atIndex:0];
388 [encoder setBuffer:result offset:0 atIndex:1];
389 [encoder dispatchThreadgroups:MTLSizeMake(1, 1, 1)
390 threadsPerThreadgroup:MTLSizeMake(1024, 1, 1)];
391 [encoder endEncoding];
392
393 [command_buffer commit];
394 [command_buffer waitUntilCompleted];
395
396 return static_cast<float *> (result.contents)[0];
397 };
398 }
399
400//------------------------------------------------------------------------------
405//------------------------------------------------------------------------------
406 std::function<void(void)> create_zero_call(graph::input_nodes<float, SAFE_MATH> &inputs) {
407 std::vector<id<MTLBuffer>> buffers;
408 for (auto &input : inputs) {
409 if (!kernel_arguments.contains(input.get())) {
410 kernel_arguments[input.get()] = [device newBufferWithBytes:input->data()
411 length:input->size()*sizeof(float)
412 options:MTLResourceStorageModeShared];
413 }
414 buffers.push_back(kernel_arguments[input.get()]);
415 }
416
417 std::vector<NSRange> ranges;
418 for (id<MTLBuffer> buffer : buffers) {
419 ranges.push_back(NSMakeRange(0, buffer.length));
420 }
421
422 return [this, buffers, ranges] () mutable {
423 command_buffer = [queue commandBuffer];
424 id<MTLBlitCommandEncoder> encoder = [command_buffer blitCommandEncoder];
425
426 for (size_t i = 0, ie = buffers.size(); i < ie; i++) {
427 [encoder fillBuffer:buffers[i]
428 range:ranges[i]
429 value:0];
430 }
431 [encoder endEncoding];
432#ifdef PROFILE_KERNELS
433 [command_buffer addCompletedHandler:[](id<MTLCommandBuffer> commandBuffer) {
434 std::cout << std::endl << " zero buffer : " << commandBuffer.GPUEndTime - commandBuffer.GPUStartTime << " s" << std::endl << std::endl;
435 }];
436#endif
437 [command_buffer commit];
438 };
439 }
440
441//------------------------------------------------------------------------------
446//------------------------------------------------------------------------------
447 std::function<void(void)> create_copy_call(graph::copy_nodes<float, SAFE_MATH> &setters) {
448 std::vector<id<MTLBuffer>> sources;
449 std::vector<id<MTLBuffer>> destinations;
450
451 for (auto &[out, in] : setters) {
452 if (!kernel_arguments.contains(in.get())) {
453 kernel_arguments[in.get()] = [device newBufferWithBytes:in->data()
454 length:in->size()*sizeof(float)
455 options:MTLResourceStorageModeShared];
456 }
457 destinations.push_back(kernel_arguments[in.get()]);
458
459 if (!kernel_arguments.contains(out.get())) {
460 kernel_arguments[out.get()] = [device newBufferWithBytes:out->data()
461 length:out->size()*sizeof(float)
462 options:MTLResourceStorageModeShared];
463 }
464 sources.push_back(kernel_arguments[out.get()]);
465 }
466
467 return [this, sources, destinations] () mutable {
468 command_buffer = [queue commandBuffer];
469 id<MTLBlitCommandEncoder> encoder = [command_buffer blitCommandEncoder];
470
471 for (size_t i = 0, ie = sources.size(); i < ie; i++) {
472 [encoder copyFromBuffer:sources[i]
473 sourceOffset:0
474 toBuffer:destinations[i]
475 destinationOffset:0
476 size:sources[i].length];
477 }
478 [encoder endEncoding];
479#ifdef PROFILE_KERNELS
480 [command_buffer addCompletedHandler:[](id<MTLCommandBuffer> commandBuffer) {
481 std::cout << std::endl << " copy buffer : " << commandBuffer.GPUEndTime - commandBuffer.GPUStartTime << " s" << std::endl << std::endl;
482 }];
483#endif
484 [command_buffer commit];
485 };
486 }
487
488//------------------------------------------------------------------------------
490//------------------------------------------------------------------------------
491 MTLCompileOptions *compile_options() {
492 MTLCompileOptions *options = [MTLCompileOptions new];
493 options.mathMode = MTLMathModeFast;
494 options.mathFloatingPointFunctions = MTLMathFloatingPointFunctionsFast;
495 options.optimizationLevel = MTLLibraryOptimizationLevelDefault;
496 options.languageVersion = MTLLanguageVersion3_2;
497 return options;
498 }
499
500//------------------------------------------------------------------------------
502//------------------------------------------------------------------------------
503 void wait() {
504 command_buffer = [queue commandBuffer];
505
506 [command_buffer commit];
507 [command_buffer waitUntilCompleted];
508 }
509
510//------------------------------------------------------------------------------
515//------------------------------------------------------------------------------
516 std::function<void(void)> run_function(std::function<void(void)> callback) {
517 return [this, callback]() {
518 command_buffer = [queue commandBuffer];
519
520 [command_buffer addCompletedHandler:[callback](id<MTLCommandBuffer> commandBuffer) {
521#ifdef PROFILE_KERNELS
522 timing::measure_diagnostic timer("callback");
523#endif
524 callback();
525#ifdef PROFILE_KERNELS
526 timer.print();
527#endif
528 }];
529
530 [command_buffer commit];
531 };
532 }
533
534//------------------------------------------------------------------------------
539//------------------------------------------------------------------------------
540 void print_results(const size_t index,
542 wait();
543 for (auto &out : nodes) {
544 std::cout << static_cast<float *> ([kernel_arguments[out.get()] contents])[index] << " ";
545 }
546 std::cout << std::endl;
547 }
548
549//------------------------------------------------------------------------------
555//------------------------------------------------------------------------------
556 float check_value(const size_t index,
558 wait();
559 return static_cast<float *> ([kernel_arguments[node.get()] contents])[index];
560 }
561
562//------------------------------------------------------------------------------
567//------------------------------------------------------------------------------
569 float *source) {
570 const size_t size = [kernel_arguments[node.get()] length];
571 memcpy([kernel_arguments[node.get()] contents],
572 source, size);
573 }
574
575//------------------------------------------------------------------------------
580//------------------------------------------------------------------------------
582 float *destination) {
583 command_buffer = [queue commandBuffer];
584
585 [command_buffer commit];
586 [command_buffer waitUntilCompleted];
587
588 memcpy(destination,
589 kernel_arguments[node.get()].contents,
590 kernel_arguments[node.get()].length);
591 }
592
593//------------------------------------------------------------------------------
597//------------------------------------------------------------------------------
598 void create_header(std::ostringstream &source_buffer) {
599 source_buffer << "#include <metal_stdlib>" << std::endl;
600 source_buffer << "#include <metal_simdgroup>" << std::endl;
601 source_buffer << "using namespace metal;" << std::endl;
602 }
603
604//------------------------------------------------------------------------------
622//------------------------------------------------------------------------------
623 void create_kernel_prefix(std::ostringstream &source_buffer,
624 const std::string name,
629 const size_t size,
630 const std::vector<bool> &is_constant,
631 jit::register_map &registers,
632 const jit::register_usage &usage,
633 jit::texture1d_list &textures1d,
634 jit::texture2d_list &textures2d,
635 jit::argument_set &thread_shared,
636 jit::register_map &thread_mem,
637 const size_t iterations=1) {
638 source_buffer << std::endl;
639 source_buffer << "kernel void " << name << "(" << std::endl;
640
641 bufferMutability[name] = std::vector<MTLMutability> ();
642
643 size_t used_thread_mem = 0;
644
645 size_t buffer_count = 0;
646 jit::argument_set used_args;
647 for (size_t i = 0, ie = inputs.size(); i < ie; i++) {
648 if (!used_args.contains(inputs[i].get())) {
649#ifdef USE_INPUT_CACHE
650 if (!is_constant[i] && iterations > 1) {
651 const size_t needed_mem = inputs[i]->size() > 1024 ?
652 1024*4 :
653 32*4;
654 if (used_thread_mem + needed_mem < device.maxThreadgroupMemoryLength) {
655 used_thread_mem += needed_mem;
656 thread_shared.insert(inputs[i].get());
657 }
658 } else if (is_constant[i] &&
659 inputs[i]->size() < size &&
660 inputs[i]->size() < 1024) {
661 const size_t needed_mem = inputs[i]->size()*4;
662 if (used_thread_mem + needed_mem < device.maxThreadgroupMemoryLength) {
663 used_thread_mem += needed_mem;
664 thread_shared.insert(inputs[i].get());
665 thread_mem[inputs[i].get()] = jit::to_string('t', inputs[i].get());
666 }
667 }
668#endif
669 bufferMutability[name].push_back(is_constant[i] ? MTLMutabilityMutable : MTLMutabilityImmutable);
670 source_buffer << " " << (is_constant[i] ? "constant" : "device")
671 << " float *"
672 << jit::to_string('v', inputs[i].get())
673 << " [[buffer(" << buffer_count++ << ")]]";
674 inputs[i]->endline(source_buffer, usage, ',');
675 used_args.insert(inputs[i].get());
676 }
677 }
678 assert(used_args.size() == inputs.size() &&
679 "Kernel inputs contain duplicates.");
680
681 for (size_t i = 0, ie = outputs.size(); i < ie; i++) {
682 if (!used_args.contains(outputs[i].get()) &&
683 !graph::atomic_accumulate_1D_cast(outputs[i]).get()) {
684 bufferMutability[name].push_back(MTLMutabilityMutable);
685 source_buffer << " device float *"
686 << jit::to_string('o', outputs[i].get())
687 << " [[buffer(" << buffer_count++ << ")]],"
688 << std::endl;
689 used_args.insert(outputs[i].get());
690 }
691 }
692 assert(used_args.size() == inputs.size() + outputs.size() &&
693 "Kernel outputs contain duplicates.");
694
695 for (size_t i = 0, ie = atomics.size(); i < ie; i++) {
696 if (!used_args.contains(atomics[i].get())) {
697 bufferMutability[name].push_back(MTLMutabilityMutable);
698 source_buffer << " device atomic_float *"
699 << jit::to_string('v', atomics[i].get())
700 << " [[buffer(" << buffer_count++ << ")]],"
701 << std::endl;
702 used_args.insert(atomics[i].get());
703 }
704 }
705 assert(used_args.size() == inputs.size() + outputs.size() +
706 atomics.size() &&
707 "Kernel atomics contain duplicates.");
708
709 if (state.get()) {
710 bufferMutability[name].push_back(MTLMutabilityMutable);
711 source_buffer << " device mt_state *"
712 << jit::to_string('s', state.get())
713 << " [[buffer(" << buffer_count++ << ")]],"
714 << std::endl
715 << " constant uint32_t &offset [[buffer("
716 << buffer_count++ << ")]],"
717 << std::endl;
718 }
719 size_t index = 0;
720 for (auto &[key, value] : textures1d) {
721 source_buffer << " const texture1d<float, access::read> "
722 << jit::to_string('a', key)
723 << " [[texture(" << index++ << ")]],"
724 << std::endl;
725 }
726 for (auto &[key, value] : textures2d) {
727 source_buffer << " const texture2d<float, access::read> "
728 << jit::to_string('a', key)
729 << " [[texture(" << index++ << ")]],"
730 << std::endl;
731 }
732 if (thread_shared.size()) {
733 source_buffer << " ushort t_index [[thread_position_in_threadgroup]]," << std::endl;
734 source_buffer << " ushort t_total [[threads_per_threadgroup]]," << std::endl;
735 }
736 source_buffer << " "
737 << jit::smallest_uint_type<float> (size)
738 << " index [[thread_position_in_grid]]) {" << std::endl
739 << " if (";
740 if (state.get()) {
741 source_buffer << "offset + ";
742 }
743 source_buffer << "index < " << size << ") {" << std::endl;
744
745 for (size_t i = 0, ie = inputs.size(); i < ie; i++) {
746 if (is_constant[i]) {
747#ifdef USE_INPUT_CACHE
748 if (usage.at(inputs[i].get()) && inputs[i]->size() == size) {
749 registers[inputs[i].get()] = jit::to_string('r', inputs[i].get());
750 source_buffer << " const ";
751 jit::add_type<float> (source_buffer);
752 source_buffer << " " << registers[inputs[i].get()] << " = "
753 << jit::to_string('v', inputs[i].get())
754 << "[";
755 if (state.get()) {
756 source_buffer << "offset + ";
757 }
758 source_buffer << "index]";
759 inputs[i]->endline(source_buffer, usage);
760 }
761#else
762 registers[inputs[i].get()] = jit::to_string('v', inputs[i].get())
763 + "["
764 + (state.get() ? "offset + " : "")
765 + "index]";
766#endif
767 }
768 }
769
770 if (thread_shared.size()) {
771 for (size_t i = 0, ie = inputs.size(); i < ie; i++) {
772 if (thread_shared.contains(inputs[i].get()) && is_constant[i]) {
773 source_buffer << " threadgroup float "
774 << jit::to_string('t', inputs[i].get())
775 << "[" << inputs[i]->size() << "]";
776 inputs[i]->endline(source_buffer, usage);
777 }
778 }
779 for (size_t i = 0, ie = inputs.size(); i < ie; i++) {
780 if (thread_shared.contains(inputs[i].get()) && is_constant[i]) {
781 source_buffer << " for(int j = t_index; j < "
782 << inputs[i]->size()
783 << "; j += t_total) {" << std::endl
784 << " "
785 << jit::to_string('t', inputs[i].get())
786 << "[j] = "
787 << jit::to_string('v', inputs[i].get())
788 << "[j]";
789 inputs[i]->endline(source_buffer, usage);
790 source_buffer << " }" << std::endl;
791 }
792 }
793 for (size_t i = 0, ie = inputs.size(); i < ie; i++) {
794 if (thread_shared.contains(inputs[i].get()) && is_constant[i]) {
795 source_buffer << " threadgroup_barrier(mem_flags::mem_threadgroup);"
796 << std::endl;
797 break;
798 }
799 }
800 for (size_t i = 0, ie = inputs.size(); i < ie; i++) {
801 if (thread_shared.contains(inputs[i].get()) && is_constant[i]) {
802 thread_shared.erase(inputs[i].get());
803 }
804 }
805 for (size_t i = 0, ie = inputs.size(); i < ie; i++) {
806 if (thread_shared.contains(inputs[i].get()) && !is_constant[i]) {
807 source_buffer << " threadgroup float "
808 << jit::to_string('t', inputs[i].get())
809 << "["
810 << (inputs[i]->size() > 1024 ? 1024 : 32)
811 << "]";
812 inputs[i]->endline(source_buffer, usage);
813 source_buffer << " "
814 << jit::to_string('t', inputs[i].get())
815 << "[t_index] = "
816 << jit::to_string('v', inputs[i].get())
817 << "[";
818 if (state.get()) {
819 source_buffer << "offset + ";
820 }
821 source_buffer << "index]";
822 inputs[i]->endline(source_buffer, usage);
823 }
824 }
825 }
826
827 if (iterations > 1) {
828 source_buffer << " for (size_t j = 0; j < " << iterations << "; j++) {" << std::endl;
829 }
830
831 for (size_t i = 0, ie = inputs.size(); i < ie; i++) {
832 if (!is_constant[i]) {
833#ifdef USE_INPUT_CACHE
834 if (usage.at(inputs[i].get())) {
835 registers[inputs[i].get()] = jit::to_string('r', inputs[i].get());
836 source_buffer << " const ";
837 jit::add_type<float> (source_buffer);
838 source_buffer << " " << registers[inputs[i].get()] << " = ";
839 if (thread_shared.contains(inputs[i].get())) {
840 source_buffer << jit::to_string('t', inputs[i].get())
841 << "[t_index]";
842 } else {
843 source_buffer << jit::to_string('v', inputs[i].get())
844 << "[";
845 if (state.get()) {
846 source_buffer << "offset + ";
847 }
848 source_buffer << "index]";
849 }
850 inputs[i]->endline(source_buffer, usage);
851 }
852#else
853 if (thread_shared.contains(inputs[i].get())) {
854 registers[inputs[i].get()] = jit::to_string('t', inputs[i].get()) + "[t_index]";
855 } else {
856 registers[inputs[i].get()] = jit::to_string('v', inputs[i].get()) + "["
857 + (state.get() ? "offset + " : "")
858 + "index]";
859 }
860#endif
861 }
862 }
863 if (state.get()) {
864#ifdef USE_INPUT_CACHE
865 registers[state.get()] = jit::to_string('r', state.get());
866 source_buffer << " device mt_state &" << registers[state.get()]
867 << " = " << jit::to_string('s', state.get())
868 << "[index]";
869 state->endline(source_buffer, usage);
870#else
871 registers[state.get()] = jit::to_string('s', state.get())
872 + "[index]";
873#endif
874 }
875 }
876
877//------------------------------------------------------------------------------
889//------------------------------------------------------------------------------
890 void create_kernel_postfix(std::ostringstream &source_buffer,
894 jit::register_map &registers,
895 const jit::register_usage &usage,
896 const jit::argument_set &thread_shared,
897 jit::register_map &thread_mem,
898 const size_t iterations=1) {
899 jit::argument_set out_registers;
900 for (auto &[out, in] : setters) {
901 if (!out->is_match(in)) {
902 auto a = out->compile(source_buffer, registers,
903 thread_mem, usage);
904 source_buffer << " ";
905 if (thread_shared.contains(in.get())) {
906 source_buffer << jit::to_string('t', in.get())
907 << "[t_index] = ";
908 } else {
909 source_buffer << jit::to_string('v', in.get())
910 << "[";
911 if (state.get()) {
912 source_buffer << "offset + ";
913 }
914 source_buffer << "index] = ";
915 }
916 if constexpr (SAFE_MATH) {
917 source_buffer << "isnan(" << registers[a.get()]
918 << ") ? 0.0 : ";
919 }
920 source_buffer << registers[a.get()] << ";" << std::endl;
921 out_registers.insert(out.get());
922 }
923 }
924
925 for (auto &out : outputs) {
926 if (!graph::variable_cast(out).get() &&
928 !out_registers.contains(out.get())) {
929 auto a = out->compile(source_buffer, registers,
930 thread_mem, usage);
931 source_buffer << " " << jit::to_string('o', out.get())
932 << "[";
933 if (state.get()) {
934 source_buffer << "offset + ";
935 }
936 source_buffer << "index] = ";
937 if constexpr (SAFE_MATH) {
938 if (!graph::random_cast(a).get()) {
939 source_buffer << "isnan(" << registers[a.get()]
940 << ") ? 0.0 : ";
941 }
942 }
943 source_buffer << registers[a.get()] << ";" << std::endl;
944 out_registers.insert(out.get());
945 }
946 }
947
948 if (iterations > 1) {
949 source_buffer << " }" << std::endl;
950 }
951 for (auto &[out, in] : setters) {
952 if (thread_shared.contains(in.get())) {
953 source_buffer << " "
954 << jit::to_string('v', in.get())
955 << "[";
956 if (state.get()) {
957 source_buffer << "offset + ";
958 }
959 source_buffer << "index] = "
960 << jit::to_string('t', in.get())
961 << "[t_index];" << std::endl;
962 }
963 }
964 source_buffer << " }" << std::endl << "}" << std::endl;
965 }
966
967//------------------------------------------------------------------------------
972//------------------------------------------------------------------------------
973 void create_reduction(std::ostringstream &source_buffer,
974 const size_t size) {
975 source_buffer << std::endl;
976 source_buffer << "kernel void max_reduction(" << std::endl;
977 source_buffer << " constant float *input [[buffer(0)]]," << std::endl;
978 source_buffer << " device float *result [[buffer(1)]]," << std::endl;
979 source_buffer << " uint i [[thread_position_in_grid]]," << std::endl;
980 source_buffer << " uint j [[simdgroup_index_in_threadgroup]]," << std::endl;
981 source_buffer << " uint k [[thread_index_in_simdgroup]]) {" << std::endl;
982 source_buffer << " if (i < " << size << ") {" << std::endl;
983 source_buffer << " float sub_max = input[i];" << std::endl;
984 source_buffer << " for (size_t index = i + 1024; index < " << size <<"; index += 1024) {" << std::endl;
985 source_buffer << " sub_max = max(sub_max, input[index]);" << std::endl;
986 source_buffer << " }" << std::endl;
987 source_buffer << " threadgroup float thread_max[32];" << std::endl;
988 source_buffer << " thread_max[j] = simd_max(sub_max);" << std::endl;
989 source_buffer << " threadgroup_barrier(mem_flags::mem_threadgroup);" << std::endl;
990 source_buffer << " if (j == 0) {" << std::endl;
991 source_buffer << " *result = simd_max(thread_max[k]);" << std::endl;
992 source_buffer << " }" << std::endl;
993 source_buffer << " }" << std::endl;
994 source_buffer << "}" << std::endl << std::endl;
995 }
996
997//------------------------------------------------------------------------------
1001//------------------------------------------------------------------------------
1003 return static_cast<float *> ([kernel_arguments[node.get()] contents]);
1004 }
1005 };
1006}
1007
1008#endif /* metal_context_h */
Class representing a generic buffer.
Definition backend.hpp:29
Class representing a metal gpu context.
Definition metal_context.hpp:25
std::function< void(void)> create_copy_call(graph::copy_nodes< float, SAFE_MATH > &setters)
Create kernel call that will to copy one buffer to another.
Definition metal_context.hpp:447
void create_reduction(std::ostringstream &source_buffer, const size_t size)
Create reduction.
Definition metal_context.hpp:973
void create_kernel_postfix(std::ostringstream &source_buffer, graph::output_nodes< float, SAFE_MATH > &outputs, graph::map_nodes< float, SAFE_MATH > &setters, graph::shared_random_state< float, SAFE_MATH > state, jit::register_map &registers, const jit::register_usage &usage, const jit::argument_set &thread_shared, jit::register_map &thread_mem, const size_t iterations=1)
Create kernel postfix.
Definition metal_context.hpp:890
static size_t max_concurrency()
Get the maximum number of concurrent instances.
Definition metal_context.hpp:56
MTLCompileOptions * compile_options()
Get the compile options.
Definition metal_context.hpp:491
void wait()
Hold the current thread until the command buffer has completed.
Definition metal_context.hpp:503
std::function< void(void)> run_function(std::function< void(void)> callback)
Run a callback function in the queue.
Definition metal_context.hpp:516
float check_value(const size_t index, const graph::shared_leaf< float, SAFE_MATH > &node)
Check the value.
Definition metal_context.hpp:556
void create_kernel_prefix(std::ostringstream &source_buffer, const std::string name, graph::input_nodes< float, SAFE_MATH > &inputs, graph::output_nodes< float, SAFE_MATH > &outputs, graph::input_nodes< float, SAFE_MATH > atomics, graph::shared_random_state< float, SAFE_MATH > &state, const size_t size, const std::vector< bool > &is_constant, jit::register_map &registers, const jit::register_usage &usage, jit::texture1d_list &textures1d, jit::texture2d_list &textures2d, jit::argument_set &thread_shared, jit::register_map &thread_mem, const size_t iterations=1)
Create kernel prefix.
Definition metal_context.hpp:623
void compile(const std::string kernel_source, std::vector< std::string > names, const bool add_reduction=false)
Compile the kernels.
Definition metal_context.hpp:83
void print_results(const size_t index, const graph::output_nodes< float, SAFE_MATH > &nodes)
Print out the results.
Definition metal_context.hpp:540
float * get_buffer(graph::shared_leaf< float, SAFE_MATH > &node)
Get the buffer for a node.
Definition metal_context.hpp:1002
static std::string device_type()
Device description.
Definition metal_context.hpp:63
void copy_to_device(graph::shared_leaf< float, SAFE_MATH > node, float *source)
Copy buffer contents to the device.
Definition metal_context.hpp:568
metal_context(const size_t index)
Construct a metal context.
Definition metal_context.hpp:72
static constexpr size_t random_state_size
Size of random state needed.
Definition metal_context.hpp:46
std::function< void(void)> create_kernel_call(const std::string kernel_name, graph::input_nodes< float, SAFE_MATH > inputs, graph::output_nodes< float, SAFE_MATH > outputs, graph::input_nodes< float, SAFE_MATH > atomics, graph::shared_random_state< float, SAFE_MATH > state, const size_t num_rays, const jit::texture1d_list &tex1d_list, const jit::texture2d_list &tex2d_list)
Create a kernel calling function.
Definition metal_context.hpp:120
int remaining_const_memory
Remaining constant memory in bytes. NOT USED.
Definition metal_context.hpp:49
static constexpr size_t random_state_scale
Random state size multiplier.
Definition metal_context.hpp:44
std::function< float(void)> create_max_call(graph::shared_leaf< float, SAFE_MATH > &argument, std::function< void(void)> run)
Create a max compute kernel calling function.
Definition metal_context.hpp:340
void copy_to_host(graph::shared_leaf< float, SAFE_MATH > node, float *destination)
Copy buffer contents to host.
Definition metal_context.hpp:581
std::function< void(void)> create_zero_call(graph::input_nodes< float, SAFE_MATH > &inputs)
Create kernel call that will be memset a buffer to zero.
Definition metal_context.hpp:406
void create_header(std::ostringstream &source_buffer)
Create the source header.
Definition metal_context.hpp:598
A timing object.
Definition timing.hpp:18
void print() const
Print the result.
Definition timing.hpp:52
subroutine assert(test, message)
Assert check.
Definition f_binding_test.f90:38
Name space for GPU backends.
Definition cpu_context.hpp:52
std::shared_ptr< variable_node< T, SAFE_MATH > > shared_variable
Convenience type alias for shared variable nodes.
Definition node.hpp:2146
shared_atomic_accumulate_1D< T, SAFE_MATH > atomic_accumulate_1D_cast(shared_leaf< T, SAFE_MATH > x)
Cast to a atomic accumulate 1D node.
Definition piecewise.hpp:2426
std::shared_ptr< random_state_node< T, SAFE_MATH > > shared_random_state
Convenience type alias for shared sqrt nodes.
Definition random.hpp:269
std::vector< shared_variable< T, SAFE_MATH > > input_nodes
Convenience type alias for a vector of inputs.
Definition node.hpp:2149
shared_variable< T, SAFE_MATH > variable_cast(shared_leaf< T, SAFE_MATH > x)
Cast to a variable node.
Definition node.hpp:2169
std::vector< std::pair< shared_variable< T, SAFE_MATH >, shared_variable< T, SAFE_MATH > > > copy_nodes
Convenience type alias for copying buffers.
Definition node.hpp:2157
std::shared_ptr< leaf_node< T, SAFE_MATH > > shared_leaf
Convenience type alias for shared leaf nodes.
Definition node.hpp:691
std::vector< std::pair< shared_leaf< T, SAFE_MATH >, shared_variable< T, SAFE_MATH > > > map_nodes
Convenience type alias for mapping end codes back to inputs.
Definition node.hpp:2153
shared_random< T, SAFE_MATH > random_cast(shared_leaf< T, SAFE_MATH > x)
Cast to a random node.
Definition random.hpp:539
std::vector< shared_leaf< T, SAFE_MATH > > output_nodes
Convenience type alias for a vector of output nodes.
Definition node.hpp:706
std::unordered_map< void *, size_t > register_usage
Type alias for counting register usage.
Definition register.hpp:261
std::unordered_map< void *, std::string > register_map
Type alias for mapping node pointers to register names.
Definition register.hpp:259
std::unordered_map< void *, std::array< size_t, 2 > > texture2d_list
Type alias for indexing 2D textures.
Definition register.hpp:267
std::unordered_map< void *, size_t > texture1d_list
Type alias for indexing 1D textures.
Definition register.hpp:265
std::string to_string(const char prefix, const NODE *pointer)
Convert a graph::leaf_node pointer to a string.
Definition register.hpp:247
std::unordered_set< void * > argument_set
Type for tacking thread shared memory.
Definition register.hpp:271
Name space for output files.
Definition output.hpp:16
Piecewise nodes.
Random constants and distributions.
Routines to time the ray execution.