1 package net.bmahe.genetics4j.gpu.spec.fitness;
2
3 import java.util.HashMap;
4 import java.util.HashSet;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.Map.Entry;
8 import java.util.Objects;
9 import java.util.Set;
10 import java.util.concurrent.CompletableFuture;
11 import java.util.concurrent.ConcurrentHashMap;
12 import java.util.concurrent.ExecutorService;
13
14 import org.apache.commons.collections4.MapUtils;
15 import org.apache.logging.log4j.LogManager;
16 import org.apache.logging.log4j.Logger;
17 import org.jocl.CL;
18 import org.jocl.Pointer;
19 import org.jocl.Sizeof;
20
21 import net.bmahe.genetics4j.core.Genotype;
22 import net.bmahe.genetics4j.gpu.opencl.OpenCLExecutionContext;
23 import net.bmahe.genetics4j.gpu.opencl.model.Device;
24 import net.bmahe.genetics4j.gpu.spec.fitness.cldata.CLData;
25 import net.bmahe.genetics4j.gpu.spec.fitness.kernelcontext.KernelExecutionContext;
26 import net.bmahe.genetics4j.gpu.spec.fitness.multistage.MultiStageDescriptor;
27 import net.bmahe.genetics4j.gpu.spec.fitness.multistage.StageDescriptor;
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126 public class MultiStageFitness<T extends Comparable<T>> extends OpenCLFitness<T> {
127 public static final Logger logger = LogManager.getLogger(MultiStageFitness.class);
128
129 private final MultiStageDescriptor multiStageDescriptor;
130 private final FitnessExtractor<T> fitnessExtractor;
131
132 private final Map<Device, Map<String, CLData>> staticData = new ConcurrentHashMap<>();
133
134 protected void clearStaticData(final Device device) {
135 if (MapUtils.isEmpty(staticData) || MapUtils.isEmpty(staticData.get(device))) {
136 return;
137 }
138
139 final Map<String, CLData> mapData = staticData.get(device);
140 for (final CLData clData : mapData.values()) {
141 CL.clReleaseMemObject(clData.clMem());
142 }
143
144 mapData.clear();
145 staticData.remove(device);
146 }
147
148 protected void clearData(final Map<Integer, CLData> data) {
149 if (MapUtils.isEmpty(data)) {
150 return;
151 }
152
153 for (final CLData clData : data.values()) {
154 CL.clReleaseMemObject(clData.clMem());
155 }
156
157 data.clear();
158 }
159
160 protected void clearResultData(final Map<Integer, CLData> resultData) {
161 if (MapUtils.isEmpty(resultData)) {
162 return;
163 }
164
165 for (final CLData clData : resultData.values()) {
166 CL.clReleaseMemObject(clData.clMem());
167 }
168
169 resultData.clear();
170 }
171
172 protected void prepareStaticData(final OpenCLExecutionContext openCLExecutionContext,
173 final StageDescriptor stageDescriptor) {
174 Objects.requireNonNull(openCLExecutionContext);
175 Objects.requireNonNull(stageDescriptor);
176
177 final var device = openCLExecutionContext.device();
178
179 logger.trace("[{}] Preparing static data", device.name());
180
181 final var kernels = openCLExecutionContext.kernels();
182 final var kernelName = stageDescriptor.kernelName();
183
184 final var kernel = kernels.get(kernelName);
185
186 final var mapStaticDataAsArgument = stageDescriptor.mapStaticDataAsArgument();
187 for (final var entry : mapStaticDataAsArgument.entrySet()) {
188 final var argumentName = entry.getKey();
189 final var argumentIndex = entry.getValue();
190
191 final var staticDataMap = staticData.get(device);
192
193 if (staticDataMap.containsKey(argumentName) == false) {
194 throw new IllegalArgumentException("Unknown static argument " + argumentName);
195 }
196
197 final CLData clStaticData = staticDataMap.get(argumentName);
198
199 logger.trace("[{}] Index {} - Loading static data with name {}", device.name(), argumentIndex, argumentName);
200
201 CL.clSetKernelArg(kernel, argumentIndex, Sizeof.cl_mem, Pointer.to(clStaticData.clMem()));
202 }
203 }
204
205 private void allocateLocalMemory(OpenCLExecutionContext openCLExecutionContext, StageDescriptor stageDescriptor,
206 long generation, List<Genotype> genotypes, final KernelExecutionContext kernelExecutionContext) {
207 Objects.requireNonNull(openCLExecutionContext);
208 Objects.requireNonNull(stageDescriptor);
209 Objects.requireNonNull(kernelExecutionContext);
210
211 final var device = openCLExecutionContext.device();
212
213 logger.trace("[{}] Allocating local memory", device.name());
214
215 final var kernels = openCLExecutionContext.kernels();
216 final var kernelName = stageDescriptor.kernelName();
217
218 final var kernel = kernels.get(kernelName);
219
220 final var localMemoryAllocators = stageDescriptor.localMemoryAllocators();
221 if (MapUtils.isNotEmpty(localMemoryAllocators)) {
222 for (final var entry : localMemoryAllocators.entrySet()) {
223 final int argumentIdx = entry.getKey();
224 final var localMemoryAllocator = entry.getValue();
225
226 final var size = localMemoryAllocator
227 .load(openCLExecutionContext, kernelExecutionContext, generation, genotypes);
228 logger.trace("[{}] Index {} - Setting local data with size of {}", device.name(), argumentIdx, size);
229
230 CL.clSetKernelArg(kernel, argumentIdx, size, null);
231 }
232 }
233 }
234
235 protected void loadData(final OpenCLExecutionContext openCLExecutionContext, final StageDescriptor stageDescriptor,
236 final Map<Integer, CLData> data, final long generation, final List<Genotype> genotypes) {
237 Objects.requireNonNull(openCLExecutionContext);
238 Objects.requireNonNull(stageDescriptor);
239 Objects.requireNonNull(data);
240
241 final var device = openCLExecutionContext.device();
242
243 logger.trace("[{}] Loading data", device.name());
244
245 final var kernels = openCLExecutionContext.kernels();
246 final var kernelName = stageDescriptor.kernelName();
247
248 final var kernel = kernels.get(kernelName);
249
250 final var dataLoaders = stageDescriptor.dataLoaders();
251 if (MapUtils.isNotEmpty(dataLoaders)) {
252 for (final var entry : dataLoaders.entrySet()) {
253 final int argumentIdx = entry.getKey();
254 final var dataLoader = entry.getValue();
255
256 final var clDdata = dataLoader.load(openCLExecutionContext, generation, genotypes);
257
258 if (data.put(argumentIdx, clDdata) != null) {
259 throw new IllegalArgumentException("Multiple data configured for index " + argumentIdx);
260 }
261 logger.trace("[{}] Index {} - Loading data of size {}", device.name(), argumentIdx, clDdata.size());
262
263 CL.clSetKernelArg(kernel, argumentIdx, Sizeof.cl_mem, Pointer.to(clDdata.clMem()));
264 }
265 }
266 }
267
268 @Override
269 public void beforeAllEvaluations(final OpenCLExecutionContext openCLExecutionContext,
270 final ExecutorService executorService) {
271 super.beforeAllEvaluations(openCLExecutionContext, executorService);
272
273 final var device = openCLExecutionContext.device();
274
275 logger.trace("[{}] Loading static data", device.name());
276 clearStaticData(device);
277
278 final var staticDataLoaders = multiStageDescriptor.staticDataLoaders();
279 for (final var entry : staticDataLoaders.entrySet()) {
280 final String argumentName = entry.getKey();
281 final var dataSupplier = entry.getValue();
282
283 if (logger.isTraceEnabled()) {
284 final var deviceName = openCLExecutionContext.device().name();
285 logger.trace("[{}] Loading static data for entry name {}", deviceName, argumentName);
286 }
287 final CLData clData = dataSupplier.load(openCLExecutionContext);
288
289 final var mapData = staticData.computeIfAbsent(device, k -> new HashMap<>());
290 if (mapData.put(argumentName, clData) != null) {
291 throw new IllegalArgumentException("Multiple data configured with name " + argumentName);
292 }
293 }
294 }
295
296
297
298
299
300
301
302
303 public MultiStageFitness(final MultiStageDescriptor _multiStageDescriptor,
304 final FitnessExtractor<T> _fitnessExtractor) {
305 Objects.requireNonNull(_multiStageDescriptor);
306 Objects.requireNonNull(_fitnessExtractor);
307
308 this.multiStageDescriptor = _multiStageDescriptor;
309 this.fitnessExtractor = _fitnessExtractor;
310 }
311
312 @Override
313 public CompletableFuture<List<T>> compute(final OpenCLExecutionContext openCLExecutionContext,
314 final ExecutorService executorService, final long generation, final List<Genotype> genotypes) {
315 Objects.requireNonNull(openCLExecutionContext);
316
317 return CompletableFuture.supplyAsync(() -> {
318
319 List<T> finalResults = null;
320
321 final var device = openCLExecutionContext.device();
322
323 final Map<Integer, CLData> data = new ConcurrentHashMap<>();
324 Map<Integer, CLData> resultData = new ConcurrentHashMap<>();
325
326 final var stageDescriptors = multiStageDescriptor.stageDescriptors();
327 for (int i = 0; i < stageDescriptors.size(); i++) {
328 final StageDescriptor stageDescriptor = stageDescriptors.get(i);
329 final var kernels = openCLExecutionContext.kernels();
330 final var kernelName = stageDescriptor.kernelName();
331
332 final var kernel = kernels.get(kernelName);
333
334 logger.debug("[{}] Executing {}-th stage for kernel {}", device.name(), i, kernelName);
335
336
337
338
339 final var kernelExecutionContextComputer = stageDescriptor.kernelExecutionContextComputer();
340 final var kernelExecutionContext = kernelExecutionContextComputer
341 .compute(openCLExecutionContext, kernelName, generation, genotypes);
342
343
344
345
346 final Map<Integer, CLData> oldResultData = new HashMap<>(resultData);
347 resultData = new ConcurrentHashMap<>();
348 final Map<Integer, Integer> reusePreviousResultSizeAsArguments = stageDescriptor
349 .reusePreviousResultSizeAsArguments();
350 final Map<Integer, Integer> reusePreviousResultAsArguments = stageDescriptor
351 .reusePreviousResultAsArguments();
352 final Set<CLData> reusedArguments = new HashSet<>();
353 if (MapUtils.isNotEmpty(reusePreviousResultAsArguments)
354 || MapUtils.isNotEmpty(reusePreviousResultSizeAsArguments)) {
355
356 if (MapUtils.isNotEmpty(reusePreviousResultAsArguments)) {
357 for (final Entry<Integer, Integer> entry : reusePreviousResultAsArguments.entrySet()) {
358 final var oldKeyArgument = entry.getKey();
359 final var newKeyArgument = entry.getValue();
360
361 final var previousResultData = oldResultData.get(oldKeyArgument);
362 if (previousResultData == null) {
363 logger.error(
364 "[{}] Could not find previous argument with index {}. Known previous arguments: {}",
365 device.name(),
366 oldKeyArgument,
367 oldResultData);
368
369 throw new IllegalArgumentException(
370 "Could not find previous argument with index " + oldKeyArgument);
371 }
372
373 logger.trace(
374 "[{}] Index {} - Reuse previous result that had index {}",
375 device.name(),
376 newKeyArgument,
377 oldKeyArgument);
378 CL.clSetKernelArg(kernel, newKeyArgument, Sizeof.cl_mem, Pointer.to(previousResultData.clMem()));
379 reusedArguments.add(previousResultData);
380 }
381 }
382
383 if (MapUtils.isNotEmpty(reusePreviousResultSizeAsArguments)) {
384 for (final Entry<Integer, Integer> entry : reusePreviousResultSizeAsArguments.entrySet()) {
385 final var oldKeyArgument = entry.getKey();
386 final var newKeyArgument = entry.getValue();
387
388 final var previousResultData = oldResultData.get(oldKeyArgument);
389 if (previousResultData == null) {
390 logger.error(
391 "[{}] Could not find previous argument with index {}. Known previous arguments: {}",
392 device.name(),
393 oldKeyArgument,
394 oldResultData);
395
396 throw new IllegalArgumentException(
397 "Could not find previous argument with index " + oldKeyArgument);
398 }
399
400 if (logger.isTraceEnabled()) {
401 logger.trace(
402 "[{}] Index {} - Setting previous result size of {} of previous argument index {}",
403 device.name(),
404 newKeyArgument,
405 previousResultData.size(),
406 oldKeyArgument);
407 }
408
409 CL.clSetKernelArg(
410 kernel,
411 newKeyArgument,
412 Sizeof.cl_int,
413 Pointer.to(new int[] { previousResultData.size() }));
414 }
415 }
416
417
418 final var previousResultsToKeep = reusePreviousResultAsArguments.keySet();
419 for (Entry<Integer, CLData> entry2 : oldResultData.entrySet()) {
420 if (previousResultsToKeep.contains(entry2.getKey()) == false) {
421 CL.clReleaseMemObject(entry2.getValue().clMem());
422 }
423 }
424 }
425
426 prepareStaticData(openCLExecutionContext, stageDescriptor);
427 loadData(openCLExecutionContext, stageDescriptor, data, generation, genotypes);
428 allocateLocalMemory(openCLExecutionContext, stageDescriptor, generation, genotypes, kernelExecutionContext);
429
430
431
432
433 final var resultAllocators = stageDescriptor.resultAllocators();
434 if (MapUtils.isNotEmpty(resultAllocators)) {
435 logger.trace("[{}] Result allocators: {}", device.name(), resultAllocators);
436
437 for (final var entry : resultAllocators.entrySet()) {
438 final int argumentIdx = entry.getKey();
439 final var resultAllocator = entry.getValue();
440
441 final var clDdata = resultAllocator
442 .load(openCLExecutionContext, kernelExecutionContext, generation, genotypes);
443
444 if (resultData.put(argumentIdx, clDdata) != null) {
445 throw new IllegalArgumentException(
446 "Multiple result allocators configured for index " + argumentIdx);
447 }
448 if (logger.isTraceEnabled()) {
449 logger.trace(
450 "[{}] Index {} - Allocate result data memory of type {} and size {}",
451 device.name(),
452 argumentIdx,
453 clDdata.clType(),
454 clDdata.size());
455 }
456
457 CL.clSetKernelArg(kernel, argumentIdx, Sizeof.cl_mem, Pointer.to(clDdata.clMem()));
458 }
459 } else {
460 logger.trace("[{}] No result allocator found", device.name());
461 }
462
463 final var clCommandQueue = openCLExecutionContext.clCommandQueue();
464 final var globalWorkDimensions = kernelExecutionContext.globalWorkDimensions();
465 final var globalWorkSize = kernelExecutionContext.globalWorkSize();
466 final long[] workGroupSize = kernelExecutionContext.workGroupSize().orElse(null);
467
468 logger.trace(
469 "[{}] Starting computation on kernel {} for {} genotypes and global work size {} and local work size {}",
470 device.name(),
471 kernelName,
472 genotypes.size(),
473 globalWorkSize,
474 workGroupSize);
475 try {
476 final long startTime = System.nanoTime();
477 CL.clEnqueueNDRangeKernel(
478 clCommandQueue,
479 kernel,
480 globalWorkDimensions,
481 null,
482 globalWorkSize,
483 workGroupSize,
484 0,
485 null,
486 null);
487
488
489
490 final long endTime = System.nanoTime();
491 final long duration = endTime - startTime;
492 if (logger.isDebugEnabled()) {
493 final var deviceName = openCLExecutionContext.device().name();
494 logger.debug(
495 "[{}] - Stage {} - Took {} microsec for {} genotypes",
496 deviceName,
497 i,
498 duration / 1000.,
499 genotypes.size());
500 }
501 } catch (Exception e) {
502 logger.error("[{}] Failure to compute", device.name(), e);
503 throw e;
504 }
505
506 if (i == stageDescriptors.size() - 1) {
507 finalResults = fitnessExtractor.compute(
508 openCLExecutionContext,
509 kernelExecutionContext,
510 executorService,
511 generation,
512 genotypes,
513 new ResultExtractor(Map.of(device, resultData)));
514
515 clearResultData(resultData);
516 }
517
518 for (final CLData clData : reusedArguments) {
519 CL.clReleaseMemObject(clData.clMem());
520 }
521 clearData(data);
522 }
523
524 if (finalResults == null) {
525 throw new IllegalStateException("final results cannot be null");
526 }
527 return finalResults;
528 }, executorService);
529 }
530
531 @Override
532 public void afterEvaluation(OpenCLExecutionContext openCLExecutionContext, ExecutorService executorService,
533 long generation, List<Genotype> genotypes) {
534 super.afterEvaluation(openCLExecutionContext, executorService, generation, genotypes);
535
536 final var device = openCLExecutionContext.device();
537 logger.trace("[{}] Releasing data", device.name());
538 }
539
540 @Override
541 public void afterAllEvaluations(final OpenCLExecutionContext openCLExecutionContext,
542 final ExecutorService executorService) {
543 super.afterAllEvaluations(openCLExecutionContext, executorService);
544
545 final var device = openCLExecutionContext.device();
546 logger.trace("[{}] Releasing static data", device.name());
547 clearStaticData(device);
548 }
549
550
551
552
553
554
555
556
557
558
559 public static <U extends Comparable<U>> MultiStageFitness<U> of(final MultiStageDescriptor multiStageDescriptor,
560 final FitnessExtractor<U> fitnessExtractor) {
561 Objects.requireNonNull(multiStageDescriptor);
562 Objects.requireNonNull(fitnessExtractor);
563
564 return new MultiStageFitness<>(multiStageDescriptor, fitnessExtractor);
565 }
566 }