1 package net.bmahe.genetics4j.gpu;
2
3 import java.io.IOException;
4 import java.nio.charset.StandardCharsets;
5 import java.util.ArrayList;
6 import java.util.HashMap;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Objects;
10 import java.util.Set;
11 import java.util.concurrent.CompletableFuture;
12 import java.util.concurrent.ExecutorService;
13
14 import org.apache.commons.collections4.ListUtils;
15 import org.apache.commons.io.IOUtils;
16 import org.apache.commons.lang3.Validate;
17 import org.apache.commons.lang3.tuple.Pair;
18 import org.apache.logging.log4j.LogManager;
19 import org.apache.logging.log4j.Logger;
20 import org.jocl.CL;
21 import org.jocl.cl_command_queue;
22 import org.jocl.cl_context;
23 import org.jocl.cl_context_properties;
24 import org.jocl.cl_device_id;
25 import org.jocl.cl_kernel;
26 import org.jocl.cl_platform_id;
27 import org.jocl.cl_program;
28 import org.jocl.cl_queue_properties;
29
30 import net.bmahe.genetics4j.core.Genotype;
31 import net.bmahe.genetics4j.core.evaluation.FitnessEvaluator;
32 import net.bmahe.genetics4j.gpu.opencl.DeviceReader;
33 import net.bmahe.genetics4j.gpu.opencl.DeviceUtils;
34 import net.bmahe.genetics4j.gpu.opencl.KernelInfoReader;
35 import net.bmahe.genetics4j.gpu.opencl.OpenCLExecutionContext;
36 import net.bmahe.genetics4j.gpu.opencl.PlatformReader;
37 import net.bmahe.genetics4j.gpu.opencl.PlatformUtils;
38 import net.bmahe.genetics4j.gpu.opencl.model.Device;
39 import net.bmahe.genetics4j.gpu.opencl.model.KernelInfo;
40 import net.bmahe.genetics4j.gpu.opencl.model.Platform;
41 import net.bmahe.genetics4j.gpu.spec.GPUEAConfiguration;
42 import net.bmahe.genetics4j.gpu.spec.GPUEAExecutionContext;
43 import net.bmahe.genetics4j.gpu.spec.Program;
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
127
128
129
130
131
132
133
134 public class GPUFitnessEvaluator<T extends Comparable<T>> implements FitnessEvaluator<T> {
135 public static final Logger logger = LogManager.getLogger(GPUFitnessEvaluator.class);
136
137 private final GPUEAExecutionContext<T> gpuEAExecutionContext;
138 private final GPUEAConfiguration<T> gpuEAConfiguration;
139 private final ExecutorService executorService;
140
141 private List<Pair<Platform, Device>> selectedPlatformToDevice;
142
143 final List<cl_context> clContexts = new ArrayList<>();
144 final List<cl_command_queue> clCommandQueues = new ArrayList<>();
145 final List<cl_program> clPrograms = new ArrayList<>();
146 final List<Map<String, cl_kernel>> clKernels = new ArrayList<>();
147 final List<OpenCLExecutionContext> clExecutionContexts = new ArrayList<>();
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163 public GPUFitnessEvaluator(final GPUEAExecutionContext<T> _gpuEAExecutionContext,
164 final GPUEAConfiguration<T> _gpuEAConfiguration,
165 final ExecutorService _executorService) {
166 Objects.requireNonNull(_gpuEAExecutionContext);
167 Objects.requireNonNull(_gpuEAConfiguration);
168 Objects.requireNonNull(_executorService);
169
170 this.gpuEAExecutionContext = _gpuEAExecutionContext;
171 this.gpuEAConfiguration = _gpuEAConfiguration;
172 this.executorService = _executorService;
173
174 CL.setExceptionsEnabled(true);
175 }
176
177 private String loadResource(final String filename) {
178 Validate.notBlank(filename);
179
180 try {
181 return IOUtils.resourceToString(filename, StandardCharsets.UTF_8);
182 } catch (IOException e) {
183 throw new IllegalStateException("Unable to load resource " + filename, e);
184 }
185 }
186
187 private List<String> grabProgramSources() {
188 final Program programSpec = gpuEAConfiguration.program();
189
190 logger.info("Load program source: {}", programSpec);
191
192 final List<String> sources = new ArrayList<>();
193
194 sources.addAll(programSpec.content());
195
196 programSpec.resources().stream().map(this::loadResource).forEach(program -> sources.add(program));
197
198 return sources;
199 }
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229 @Override
230 public void preEvaluation() {
231 logger.trace("Init...");
232 FitnessEvaluator.super.preEvaluation();
233
234 final var platformReader = new PlatformReader();
235 final var deviceReader = new DeviceReader();
236 final var kernelInfoReader = new KernelInfoReader();
237
238 final int numPlatforms = PlatformUtils.numPlatforms();
239 logger.info("Found {} platforms", numPlatforms);
240
241 final List<cl_platform_id> platformIds = PlatformUtils.platformIds(numPlatforms);
242
243 logger.info("Selecting platform and devices");
244 final var platformFilters = gpuEAExecutionContext.platformFilters();
245 final var deviceFilters = gpuEAExecutionContext.deviceFilters();
246
247 selectedPlatformToDevice = platformIds.stream()
248 .map(platformReader::read)
249 .filter(platformFilters)
250 .flatMap(platform -> {
251 final var platformId = platform.platformId();
252 final int numDevices = DeviceUtils.numDevices(platformId);
253 logger.trace("\tPlatform {}: {} devices", platform.name(), numDevices);
254
255 final var deviceIds = DeviceUtils.getDeviceIds(platformId, numDevices);
256 return deviceIds.stream().map(deviceId -> Pair.of(platform, deviceId));
257 })
258 .map(platformToDeviceId -> {
259 final var platform = platformToDeviceId.getLeft();
260 final var platformId = platform.platformId();
261 final var deviceID = platformToDeviceId.getRight();
262
263 return Pair.of(platform, deviceReader.read(platformId, deviceID));
264 })
265 .filter(platformToDevice -> deviceFilters.test(platformToDevice.getRight()))
266 .toList();
267
268 if (logger.isTraceEnabled()) {
269 logger.trace("============================");
270 logger.trace("Selected devices:");
271 selectedPlatformToDevice.forEach(pd -> {
272 logger.trace("{}", pd.getLeft());
273 logger.trace("\t{}", pd.getRight());
274 });
275 logger.trace("============================");
276 }
277
278 Validate.isTrue(selectedPlatformToDevice.isEmpty() == false, "No compatible devices found for the given filters");
279
280 final List<String> programs = grabProgramSources();
281 final String[] programsArr = programs.toArray(new String[programs.size()]);
282
283 for (final var platformAndDevice : selectedPlatformToDevice) {
284 final var platform = platformAndDevice.getLeft();
285 final var device = platformAndDevice.getRight();
286
287 logger.info("Processing platform [{}] / device [{}]", platform.name(), device.name());
288
289 logger.info("\tCreating context");
290 cl_context_properties contextProperties = new cl_context_properties();
291 contextProperties.addProperty(CL.CL_CONTEXT_PLATFORM, platform.platformId());
292
293 final cl_context context = CL
294 .clCreateContext(contextProperties, 1, new cl_device_id[] { device.deviceId() }, null, null, null);
295
296 logger.info("\tCreating command queue");
297 final cl_queue_properties queueProperties = new cl_queue_properties();
298 queueProperties.addProperty(
299 CL.CL_QUEUE_PROPERTIES,
300 CL.CL_QUEUE_PROFILING_ENABLE | CL.CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE);
301 final cl_command_queue commandQueue = CL
302 .clCreateCommandQueueWithProperties(context, device.deviceId(), queueProperties, null);
303
304 logger.info("\tCreate program");
305 final cl_program program = CL.clCreateProgramWithSource(context, programsArr.length, programsArr, null, null);
306
307 final var programSpec = gpuEAConfiguration.program();
308 final var buildOptions = programSpec.buildOptions().orElse(null);
309 logger.info("\tBuilding program with options: {}", buildOptions);
310 CL.clBuildProgram(program, 0, null, buildOptions, null, null);
311
312 final Set<String> kernelNames = gpuEAConfiguration.program().kernelNames();
313
314 final Map<String, cl_kernel> kernels = new HashMap<>();
315 final Map<String, KernelInfo> kernelInfos = new HashMap<>();
316 for (final String kernelName : kernelNames) {
317
318 logger.info("\tCreate kernel {}", kernelName);
319 final cl_kernel kernel = CL.clCreateKernel(program, kernelName, null);
320 Objects.requireNonNull(kernel);
321
322 kernels.put(kernelName, kernel);
323
324 final var kernelInfo = kernelInfoReader.read(device.deviceId(), kernel, kernelName);
325 logger.trace("\t{}", kernelInfo);
326 kernelInfos.put(kernelName, kernelInfo);
327 }
328
329 clContexts.add(context);
330 clCommandQueues.add(commandQueue);
331 clKernels.add(kernels);
332 clPrograms.add(program);
333
334 final var openCLExecutionContext = OpenCLExecutionContext.builder()
335 .platform(platform)
336 .device(device)
337 .clContext(context)
338 .clCommandQueue(commandQueue)
339 .kernels(kernels)
340 .kernelInfos(kernelInfos)
341 .clProgram(program)
342 .build();
343
344 clExecutionContexts.add(openCLExecutionContext);
345 }
346
347 final var fitness = gpuEAConfiguration.fitness();
348 fitness.beforeAllEvaluations();
349 for (final OpenCLExecutionContext clExecutionContext : clExecutionContexts) {
350 fitness.beforeAllEvaluations(clExecutionContext, executorService);
351 }
352 }
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395 @Override
396 public List<T> evaluate(final long generation, final List<Genotype> genotypes) {
397
398 final var fitness = gpuEAConfiguration.fitness();
399
400
401
402
403 final int partitionSize = (int) (Math.ceil((double) genotypes.size() / clExecutionContexts.size()));
404 final var subGenotypes = ListUtils.partition(genotypes, partitionSize);
405 logger.debug("Genotype decomposed in {} partition(s)", subGenotypes.size());
406 if (logger.isTraceEnabled()) {
407 for (int i = 0; i < subGenotypes.size(); i++) {
408 final List<Genotype> subGenotype = subGenotypes.get(i);
409 logger.trace("\tPartition {} with {} elements", i, subGenotype.size());
410 }
411 }
412
413 final List<CompletableFuture<List<T>>> subResultsCF = new ArrayList<>();
414 for (int i = 0; i < subGenotypes.size(); i++) {
415 final var openCLExecutionContext = clExecutionContexts.get(i % clExecutionContexts.size());
416 final var subGenotype = subGenotypes.get(i);
417
418 fitness.beforeEvaluation(generation, subGenotype);
419 fitness.beforeEvaluation(openCLExecutionContext, executorService, generation, subGenotype);
420
421 final var resultsCF = fitness.compute(openCLExecutionContext, executorService, generation, subGenotype)
422 .thenApply(results -> {
423
424 fitness.afterEvaluation(openCLExecutionContext, executorService, generation, subGenotype);
425 fitness.afterEvaluation(generation, subGenotype);
426
427 return results;
428 });
429
430 subResultsCF.add(resultsCF);
431 }
432
433 final List<T> resultsEvaluation = new ArrayList<>(genotypes.size());
434 for (final CompletableFuture<List<T>> subResultCF : subResultsCF) {
435 final var fitnessResults = subResultCF.join();
436 resultsEvaluation.addAll(fitnessResults);
437 }
438 return resultsEvaluation;
439 }
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470 @Override
471 public void postEvaluation() {
472
473 final var fitness = gpuEAConfiguration.fitness();
474
475 for (final OpenCLExecutionContext clExecutionContext : clExecutionContexts) {
476 fitness.afterAllEvaluations(clExecutionContext, executorService);
477 }
478 fitness.afterAllEvaluations();
479
480 logger.debug("Releasing kernels");
481
482 for (final Map<String, cl_kernel> kernels : clKernels) {
483 for (final cl_kernel clKernel : kernels.values()) {
484 CL.clReleaseKernel(clKernel);
485 }
486 }
487 clKernels.clear();
488
489 logger.debug("Releasing programs");
490 for (final cl_program clProgram : clPrograms) {
491 CL.clReleaseProgram(clProgram);
492 }
493 clPrograms.clear();
494
495 logger.debug("Releasing command queues");
496 for (final cl_command_queue clCommandQueue : clCommandQueues) {
497 CL.clReleaseCommandQueue(clCommandQueue);
498 }
499 clCommandQueues.clear();
500
501 logger.debug("Releasing contexts");
502 for (final cl_context clContext : clContexts) {
503 CL.clReleaseContext(clContext);
504 }
505 clContexts.clear();
506
507 clExecutionContexts.clear();
508 selectedPlatformToDevice = null;
509
510 FitnessEvaluator.super.postEvaluation();
511 }
512 }