Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
sparse_cholesky_cl.h
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#pragma once
20
21// Reusable GPU sparse SPD Cholesky solver (double precision, level-scheduled), factored out
22// of the highlights harmonic-transposition code. Host-side symbolic analysis (reusing the
23// CPU solver's elimination tree / reach), device-side numeric factorization and triangular
24// solves. The numeric kernels live in data/kernels/highlights_sparse.cl; the caller owns
25// them and passes their handles through _sp_chol_cl_kernels_t.
26
27#include "common/darktable.h"
28#include "common/solvers/sparse_cholesky.h" // _sp_etree / _sp_ereach (host symbolic)
29
30#ifdef HAVE_OPENCL
31#include "common/opencl.h"
32
33// Handles of the four data/kernels/highlights_sparse.cl kernels the solver enqueues.
34typedef struct
35{
36 int update_level; // sparse_chol_update_level
37 int final_level; // sparse_chol_final_level
38 int fwd_level; // sparse_chol_fwd_level
39 int bwd_level; // sparse_chol_bwd_level
41
42// GPU sparse Cholesky factor handle (device buffers + host-side symbolic metadata).
43typedef struct
44{
47 int nlev;
48 int *lev_off; // host: nlev+1 offsets into the level column list
51 int *lev_pos_off; // host: nlev+1 offsets into the level-grouped entry-position list
52 cl_mem values, colptr, rowind, contptr, contsrc, contljk, posof;
53 cl_mem rowptr, rowcol, rowpos, diagpos, levcols, levrows_bwd;
54 int devid;
56
57// ===== GPU sparse Cholesky (double precision, level-scheduled) ==============================
58// Same symmetric-positive-definite systems as the CPU solver, factored and solved on the
59// device: the host performs the symbolic analysis on integer metadata only (elimination tree,
60// pattern of the factor L, flat column-modification update schedules, level schedule = groups
61// of mutually independent columns that can be processed in parallel), then the kernels in
62// data/kernels/highlights_sparse.cl run the numeric work in 64-bit floats. This is the
63// foundation of the no-roundtrip GPU pipeline (no mid-pipeline download/re-upload of pixels):
64// the right-hand side b and the solution x live in device buffers.
65
66// release a GPU sparse Cholesky factor (device buffers + host offsets); NULL-safe
88
89// allocate a device buffer and upload host data into it; returns NULL on failure
90static inline cl_mem _sp_cl_upload(const int devid, const void *data, const size_t bytes)
91{
92 cl_mem mem = dt_opencl_alloc_device_buffer(devid, bytes);
93 if(IS_NULL_PTR(mem)) return NULL;
94 if(dt_opencl_write_buffer_to_device(devid, (void *)data, mem, 0, bytes, CL_TRUE) != CL_SUCCESS)
95 {
97 return NULL;
98 }
99 return mem;
100}
101
102// Factor the matrix A (upper-triangular compressed-sparse-column, symmetric positive
103// definite, double precision) on the GPU: host-side symbolic analysis (integer metadata),
104// device-side numeric factorization, level by level. gd carries the kernel handles.
105// Mirrors _sp_chol_factor on the CPU: any change here must be mirrored there and re-validated
106// with the HL_SPCL_TEST self-test (_sp_chol_cl_selftest).
107//
108// Maths bridge: this computes the same factorization A = L * L^T as the CPU _sp_chol_factor
109// (SPD region-PDE / biharmonic-dome systems, article "Guided laplacian highlights"), but
110// reorganized for the GPU. The numeric recurrences are unchanged --
111// L[i,j] = ( A[i,j] - sum_{k<j} L[i,k] L[j,k] ) / L[j,j] , L[j,j] = sqrt( A[j,j] - sum_k L[j,k]^2 )
112// -- only the schedule differs: the host precomputes (1) the symbolic pattern of L, (2) every
113// cmod(j,k) contribution -L[i,k]*L[j,k] grouped by the destination entry it lands in, and (3) an
114// elimination-tree LEVEL SCHEDULE, so the device factors one whole level of mutually independent
115// columns at a time (article "Performance -> The OpenCL pipe": the level-scheduled factorization).
116static inline _sp_chol_cl_t *_sp_chol_factor_cl(const int devid, const _sp_chol_cl_kernels_t kernels, const int dimension,
117 const int *const restrict matrix_col_ptr,
118 const int *const restrict matrix_row_index,
119 const double *const restrict matrix_values)
120{
121 if(devid < 0 || kernels.update_level < 0 || kernels.final_level < 0) return NULL;
122
123 int *parent = malloc(sizeof(int) * dimension);
124 int *ancestor = malloc(sizeof(int) * dimension);
125 int *workspace = malloc(sizeof(int) * dimension);
126 int *sstk = malloc(sizeof(int) * dimension);
127 _sp_chol_cl_t *factor = calloc(1, sizeof(_sp_chol_cl_t));
128 int *colptr = NULL, *rowind = NULL, *colfill = NULL, *col_level = NULL;
129 int *updoff = NULL, *updljk = NULL, *updk = NULL;
130 int *contptr_h = NULL, *contsrc_h = NULL, *contljk_h = NULL, *fillc = NULL;
131 int *posof_h = NULL, *levcols = NULL;
132 int *rowptr = NULL, *rowcol = NULL, *rowpos = NULL, *diagpos = NULL, *rowfill = NULL;
133 double *values_host = NULL;
134 if(!parent || !ancestor || !workspace || !sstk || !factor) goto fail;
135 factor->devid = devid;
136 factor->dimension = dimension;
137
138 const double _tf0 = dt_get_wtime();
139 _sp_etree(dimension, matrix_col_ptr, matrix_row_index, parent, ancestor);
140
141 // pass 1a: column counts of L (diag + one entry per (row j, col k) pattern element)
142 colptr = calloc(dimension + 1, sizeof(int));
143 updoff = calloc(dimension + 2, sizeof(int));
144 if(!colptr || !updoff) goto fail;
145 for(int i = 0; i < dimension; i++) workspace[i] = -1;
146 size_t nupd = 0;
147 for(int j = 0; j < dimension; j++)
148 {
149 colptr[j + 1] += 1; // diagonal
150 const int stack_top = _sp_ereach(dimension, matrix_col_ptr, matrix_row_index, j, parent, sstk, workspace);
151 for(int stack_pos = stack_top; stack_pos < dimension; stack_pos++)
152 {
153 colptr[sstk[stack_pos] + 1] += 1;
154 nupd++;
155 }
156 updoff[j + 1] = (int)nupd;
157 }
158 for(int j = 0; j < dimension; j++) colptr[j + 1] += colptr[j];
159 factor->n_nonzero = colptr[dimension];
160
161 // pass 1b: fill the sorted column patterns (ascending rows: j increases) + the update list
162 rowind = malloc(sizeof(int) * factor->n_nonzero);
163 colfill = malloc(sizeof(int) * dimension);
164 updljk = malloc(sizeof(int) * (nupd ? nupd : 1));
165 updk = malloc(sizeof(int) * (nupd ? nupd : 1));
166 if(!rowind || !colfill || !updljk || !updk) goto fail;
167 for(int j = 0; j < dimension; j++)
168 {
169 rowind[colptr[j]] = j; // diag first
170 colfill[j] = 1;
171 }
172 for(int i = 0; i < dimension; i++) workspace[i] = -1;
173 size_t upd_index = 0;
174 size_t npairs = 0;
175 for(int j = 0; j < dimension; j++)
176 {
177 const int stack_top = _sp_ereach(dimension, matrix_col_ptr, matrix_row_index, j, parent, sstk, workspace);
178 for(int stack_pos = stack_top; stack_pos < dimension; stack_pos++)
179 {
180 const int k = sstk[stack_pos];
181 const int pos = colptr[k] + colfill[k];
182 rowind[pos] = j;
183 colfill[k]++;
184 updljk[upd_index] = pos; // position of L[j,k]
185 updk[upd_index] = k;
186 upd_index++;
187 }
188 }
189
190 // pass 2: column-modification ("cmod") contribution streams, grouped BY DESTINATION entry.
191 // For update (j,k): rows >= j of column k map into positions of column j; storing each
192 // contribution under the entry it subtracts INTO lets the numeric kernel run one thread per
193 // matrix entry with no atomics and the exact ascending-update summation order (the target
194 // columns' destination sets are disjoint, so both merge sweeps parallelize over j).
195 // Maths bridge: for k in reach(j), each shared row `row` (>= j) contributes the single product
196 // -L[row,k]*L[j,k] to entry (row,j) of L -- i.e. it realizes one term of the sum
197 // A[row,j] - sum_{k} L[row,k] L[j,k]. contsrc = position of L[row,k], contljk = position of
198 // L[j,k]; the update kernel later sums all contributions landing on each entry.
199 contptr_h = calloc((size_t)factor->n_nonzero + 1, sizeof(int));
200 fillc = calloc(factor->n_nonzero, sizeof(int));
201 if(!contptr_h || !fillc) goto fail;
202
203 OMP_PRAGMA(omp parallel for default(firstprivate) schedule(dynamic, 16))
204 for(int j = 0; j < dimension; j++)
205 {
206 for(int upd_slot = updoff[j]; upd_slot < updoff[j + 1]; upd_slot++)
207 {
208 const int k = updk[upd_slot];
209 int pos_j = colptr[j]; // both sorted ascending; rows >= j of col k are a subset
210 for(int pos_k = updljk[upd_slot]; pos_k < colptr[k + 1]; pos_k++)
211 {
212 const int row = rowind[pos_k];
213 while(rowind[pos_j] != row) pos_j++;
214 contptr_h[pos_j + 1]++;
215 }
216 }
217 }
218 for(int entry = 0; entry < factor->n_nonzero; entry++) contptr_h[entry + 1] += contptr_h[entry];
219 npairs = (size_t)contptr_h[factor->n_nonzero];
220 contsrc_h = malloc(sizeof(int) * (npairs ? npairs : 1));
221 contljk_h = malloc(sizeof(int) * (npairs ? npairs : 1));
222 if(!contsrc_h || !contljk_h) goto fail;
223
224 OMP_PRAGMA(omp parallel for default(firstprivate) schedule(dynamic, 16))
225 for(int j = 0; j < dimension; j++)
226 {
227 for(int upd_slot = updoff[j]; upd_slot < updoff[j + 1]; upd_slot++)
228 {
229 const int k = updk[upd_slot];
230 const int ljk_pos = updljk[upd_slot];
231 int pos_j = colptr[j];
232 for(int pos_k = ljk_pos; pos_k < colptr[k + 1]; pos_k++)
233 {
234 const int row = rowind[pos_k];
235 while(rowind[pos_j] != row) pos_j++;
236 const int dest_index = contptr_h[pos_j] + fillc[pos_j]++;
237 contsrc_h[dest_index] = pos_k;
238 contljk_h[dest_index] = ljk_pos;
239 }
240 }
241 }
242
243 // level schedule: group columns into dependency levels -- columns in the same level are
244 // mutually independent and can be factored/solved in parallel. Forward levels serve the
245 // factorization and the forward solve; backward levels are built separately below.
246 // Maths bridge: level(j) = 1 + max_{k in reach(j)} level(k) = the longest chain of column
247 // dependencies feeding j in the elimination tree. Columns sharing a level have disjoint
248 // dependency cones, so factoring/solving them together is exact -- this is the parallelism the
249 // sequential CPU column sweep cannot expose.
250 col_level = calloc(dimension, sizeof(int));
251 if(!col_level) goto fail;
252 int maxlev = 0;
253 for(int j = 0; j < dimension; j++)
254 {
255 int neighbor_max = -1;
256 for(int upd_slot = updoff[j]; upd_slot < updoff[j + 1]; upd_slot++)
257 if(col_level[updk[upd_slot]] > neighbor_max) neighbor_max = col_level[updk[upd_slot]]; // deepest predecessor
258 col_level[j] = neighbor_max + 1; // one level after all columns j depends on
259 if(col_level[j] > maxlev) maxlev = col_level[j];
260 }
261 factor->nlev = maxlev + 1;
262 factor->lev_off = calloc(factor->nlev + 1, sizeof(int));
263 levcols = malloc(sizeof(int) * dimension);
264 if(!factor->lev_off || !levcols) goto fail;
265 for(int j = 0; j < dimension; j++) factor->lev_off[col_level[j] + 1]++;
266 for(int level = 0; level < factor->nlev; level++) factor->lev_off[level + 1] += factor->lev_off[level];
267 {
268 int *fill = calloc(factor->nlev, sizeof(int));
269 if(!fill) goto fail;
270 for(int j = 0; j < dimension; j++) levcols[factor->lev_off[col_level[j]] + fill[col_level[j]]++] = j;
271 free(fill);
272 }
273
274 // entry positions grouped by level: drives the one-thread-per-entry update kernel
275 posof_h = malloc(sizeof(int) * factor->n_nonzero);
276 factor->lev_pos_off = calloc(factor->nlev + 1, sizeof(int));
277 if(!posof_h || !factor->lev_pos_off) goto fail;
278 {
279 int dest_index = 0;
280 for(int level = 0; level < factor->nlev; level++)
281 {
282 factor->lev_pos_off[level] = dest_index;
283 for(int c = factor->lev_off[level]; c < factor->lev_off[level + 1]; c++)
284 {
285 const int j = levcols[c];
286 for(int pos = colptr[j]; pos < colptr[j + 1]; pos++) posof_h[dest_index++] = pos;
287 }
288 }
289 factor->lev_pos_off[factor->nlev] = dest_index;
290 }
291
292 // backward levels: x[j] depends on x[rows > j of column j] (its ancestors)
293 // Maths bridge: the backward solve L^T x = y computes x_j = (y_j - sum_{i>j} L[i,j] x_i)/L[j,j],
294 // so x_j needs every x_i for the below-diagonal rows i of column j first -- the dependency order
295 // is the reverse of the factorization, hence a separately built level schedule.
296 {
297 int *col_level_bwd = calloc(dimension, sizeof(int));
298 int *level_rows = malloc(sizeof(int) * dimension);
299 if(!col_level_bwd || !level_rows)
300 {
301 free(col_level_bwd);
302 free(level_rows);
303 goto fail;
304 }
305 int max_bwd = 0;
306 for(int j = dimension - 1; j >= 0; j--)
307 {
308 int neighbor_max = -1;
309 for(int pos = colptr[j] + 1; pos < colptr[j + 1]; pos++)
310 if(col_level_bwd[rowind[pos]] > neighbor_max) neighbor_max = col_level_bwd[rowind[pos]];
311 col_level_bwd[j] = neighbor_max + 1;
312 if(col_level_bwd[j] > max_bwd) max_bwd = col_level_bwd[j];
313 }
314 factor->nlev_bwd = max_bwd + 1;
315 factor->lev_off_bwd = calloc(factor->nlev_bwd + 1, sizeof(int));
316 if(!factor->lev_off_bwd)
317 {
318 free(col_level_bwd);
319 free(level_rows);
320 goto fail;
321 }
322 for(int j = 0; j < dimension; j++) factor->lev_off_bwd[col_level_bwd[j] + 1]++;
323 for(int level = 0; level < factor->nlev_bwd; level++)
324 factor->lev_off_bwd[level + 1] += factor->lev_off_bwd[level];
325 int *fill = calloc(factor->nlev_bwd, sizeof(int));
326 if(!fill)
327 {
328 free(col_level_bwd);
329 free(level_rows);
330 goto fail;
331 }
332 for(int j = 0; j < dimension; j++)
333 level_rows[factor->lev_off_bwd[col_level_bwd[j]] + fill[col_level_bwd[j]]++] = j;
334 free(fill);
335 factor->levrows_bwd = _sp_cl_upload(devid, level_rows, sizeof(int) * dimension);
336 free(col_level_bwd);
337 free(level_rows);
338 if(IS_NULL_PTR(factor->levrows_bwd)) goto fail;
339 }
340
341 // row-wise (compressed-sparse-row) mirror of the off-diagonal pattern for the forward solve,
342 // plus the position of each column's diagonal value
343 rowptr = calloc(dimension + 1, sizeof(int));
344 diagpos = malloc(sizeof(int) * dimension);
345 rowfill = calloc(dimension, sizeof(int));
346 if(!rowptr || !diagpos || !rowfill) goto fail;
347 for(int c = 0; c < dimension; c++)
348 {
349 diagpos[c] = colptr[c];
350 for(int pos = colptr[c] + 1; pos < colptr[c + 1]; pos++) rowptr[rowind[pos] + 1]++;
351 }
352 for(int j = 0; j < dimension; j++) rowptr[j + 1] += rowptr[j];
353 rowcol = malloc(sizeof(int) * (factor->n_nonzero - dimension + 1));
354 rowpos = malloc(sizeof(int) * (factor->n_nonzero - dimension + 1));
355 if(!rowcol || !rowpos) goto fail;
356 for(int c = 0; c < dimension; c++)
357 for(int pos = colptr[c] + 1; pos < colptr[c + 1]; pos++)
358 {
359 const int row = rowind[pos];
360 const int dest_index = rowptr[row] + rowfill[row]++;
361 rowcol[dest_index] = c;
362 rowpos[dest_index] = pos;
363 }
364
365 // numeric init: scatter the upper-CSC A into the (lower) L pattern
366 values_host = calloc(factor->n_nonzero, sizeof(double));
367 if(!values_host) goto fail;
368 for(int c = 0; c < dimension; c++)
369 for(int pos = matrix_col_ptr[c]; pos < matrix_col_ptr[c + 1]; pos++)
370 {
371 const int row = matrix_row_index[pos]; // r <= c: lower entry (c, r) -> column r
372 if(row == c)
373 values_host[colptr[c]] = matrix_values[pos];
374 else
375 {
376 int dest_index = colptr[row];
377 while(rowind[dest_index] != c) dest_index++;
378 values_host[dest_index] = matrix_values[pos];
379 }
380 }
381
382 // upload all the symbolic metadata + the seeded values to the device
383 const double _tf1 = dt_get_wtime();
384 factor->values = _sp_cl_upload(devid, values_host, sizeof(double) * factor->n_nonzero);
385 factor->colptr = _sp_cl_upload(devid, colptr, sizeof(int) * (dimension + 1));
386 factor->rowind = _sp_cl_upload(devid, rowind, sizeof(int) * factor->n_nonzero);
387 factor->contptr = _sp_cl_upload(devid, contptr_h, sizeof(int) * ((size_t)factor->n_nonzero + 1));
388 factor->contsrc = _sp_cl_upload(devid, contsrc_h, sizeof(int) * (npairs ? npairs : 1));
389 factor->contljk = _sp_cl_upload(devid, contljk_h, sizeof(int) * (npairs ? npairs : 1));
390 factor->posof = _sp_cl_upload(devid, posof_h, sizeof(int) * factor->n_nonzero);
391 factor->rowptr = _sp_cl_upload(devid, rowptr, sizeof(int) * (dimension + 1));
392 factor->rowcol = _sp_cl_upload(devid, rowcol, sizeof(int) * (factor->n_nonzero - dimension + 1));
393 factor->rowpos = _sp_cl_upload(devid, rowpos, sizeof(int) * (factor->n_nonzero - dimension + 1));
394 factor->diagpos = _sp_cl_upload(devid, diagpos, sizeof(int) * dimension);
395 factor->levcols = _sp_cl_upload(devid, levcols, sizeof(int) * dimension);
396 if(IS_NULL_PTR(factor->values) || IS_NULL_PTR(factor->colptr) || IS_NULL_PTR(factor->rowind)
397 || IS_NULL_PTR(factor->contptr) || IS_NULL_PTR(factor->contsrc) || IS_NULL_PTR(factor->contljk)
398 || IS_NULL_PTR(factor->posof) || IS_NULL_PTR(factor->rowptr) || IS_NULL_PTR(factor->rowcol)
399 || IS_NULL_PTR(factor->rowpos) || IS_NULL_PTR(factor->diagpos) || IS_NULL_PTR(factor->levcols))
400 goto fail;
401
402 // level-scheduled numeric factorization: per level, one thread per matrix entry applies
403 // every contribution scheduled onto its entry, then each column of the level finalizes
404 // (sqrt of the diagonal, scale of the sub-diagonal)
405 // Maths bridge, per level: sparse_chol_update_level forms A[i,j] - sum_k L[i,k] L[j,k] into
406 // every entry (the cmod sum from pass 2); sparse_chol_final_level then sets
407 // L[j,j] = sqrt(that diagonal value) and L[i,j] /= L[j,j] for the sub-diagonal rows -- together
408 // the two recurrences at the top of this function, run in parallel across the level's columns.
409 const double _tf2 = dt_get_wtime();
410 {
411 const int kernel_update = kernels.update_level;
412 const int kernel_final = kernels.final_level;
413 const int local_size = 64;
414 for(int level = 0; level < factor->nlev; level++)
415 {
416 const int n_level_cols = factor->lev_off[level + 1] - factor->lev_off[level];
417 if(!n_level_cols) continue;
418 const int npos = factor->lev_pos_off[level + 1] - factor->lev_pos_off[level];
419 if(npos)
420 {
421 size_t sizes[3] = { ROUNDUP(npos, 64), 1, 1 };
422 dt_opencl_set_kernel_arg(devid, kernel_update, 0, sizeof(cl_mem), &factor->values);
423 dt_opencl_set_kernel_arg(devid, kernel_update, 1, sizeof(cl_mem), &factor->contptr);
424 dt_opencl_set_kernel_arg(devid, kernel_update, 2, sizeof(cl_mem), &factor->contsrc);
425 dt_opencl_set_kernel_arg(devid, kernel_update, 3, sizeof(cl_mem), &factor->contljk);
426 dt_opencl_set_kernel_arg(devid, kernel_update, 4, sizeof(cl_mem), &factor->posof);
427 dt_opencl_set_kernel_arg(devid, kernel_update, 5, sizeof(int), &factor->lev_pos_off[level]);
428 dt_opencl_set_kernel_arg(devid, kernel_update, 6, sizeof(int), &npos);
429 if(dt_opencl_enqueue_kernel_2d(devid, kernel_update, sizes) != CL_SUCCESS) goto fail;
430 }
431 {
432 size_t sizes[3] = { (size_t)n_level_cols * local_size, 1, 1 };
433 size_t local[3] = { local_size, 1, 1 };
434 dt_opencl_set_kernel_arg(devid, kernel_final, 0, sizeof(cl_mem), &factor->values);
435 dt_opencl_set_kernel_arg(devid, kernel_final, 1, sizeof(cl_mem), &factor->colptr);
436 dt_opencl_set_kernel_arg(devid, kernel_final, 2, sizeof(cl_mem), &factor->levcols);
437 dt_opencl_set_kernel_arg(devid, kernel_final, 3, sizeof(int), &factor->lev_off[level]);
438 dt_opencl_set_kernel_arg(devid, kernel_final, 4, sizeof(int), &n_level_cols);
439 if(dt_opencl_enqueue_kernel_2d_with_local(devid, kernel_final, sizes, local) != CL_SUCCESS) goto fail;
440 }
441 }
442 }
443
444 {
445 const double _tf3 = dt_get_wtime();
446 dt_opencl_finish(devid);
448 "[sparse cholesky] factor n=%d nnz=%d nlev=%d npairs=%llu: sym=%.0fms up=%.0fms"
449 " enq=%.0fms gpu=%.0fms\n",
450 dimension, factor->n_nonzero, factor->nlev, (unsigned long long)npairs, (_tf1 - _tf0) * 1e3,
451 (_tf2 - _tf1) * 1e3, (_tf3 - _tf2) * 1e3, (dt_get_wtime() - _tf3) * 1e3);
452 }
453
454 free(parent);
455 free(ancestor);
456 free(workspace);
457 free(sstk);
458 free(colptr);
459 free(rowind);
460 free(colfill);
461 free(col_level);
462 free(updoff);
463 free(updljk);
464 free(updk);
465 free(contptr_h);
466 free(contsrc_h);
467 free(contljk_h);
468 free(fillc);
469 free(posof_h);
470 free(levcols);
471 free(rowptr);
472 free(rowcol);
473 free(rowpos);
474 free(diagpos);
475 free(rowfill);
476 free(values_host);
477 return factor;
478
479fail:
480 free(parent);
481 free(ancestor);
482 free(workspace);
483 free(sstk);
484 free(colptr);
485 free(rowind);
486 free(colfill);
487 free(col_level);
488 free(updoff);
489 free(updljk);
490 free(updk);
491 free(contptr_h);
492 free(contsrc_h);
493 free(contljk_h);
494 free(fillc);
495 free(posof_h);
496 free(levcols);
497 free(rowptr);
498 free(rowcol);
499 free(rowpos);
500 free(diagpos);
501 free(rowfill);
502 free(values_host);
504 return NULL;
505}
506
507// Solve the factored system L L^T x = b on the GPU, with b in a device double buffer
508// (n doubles), overwritten in place with the solution; level-scheduled forward then backward
509// substitution. Returns 0 on success. gd = kernel handles.
510// Mirrors _sp_chol_solve on the CPU: any change here must be mirrored there and re-validated
511// with the HL_SPCL_TEST self-test (_sp_chol_cl_selftest).
512//
513// Maths bridge: x = A^{-1} b via the two triangular solves L y = b (forward) then L^T x = y
514// (backward), identical to the CPU _sp_chol_solve, but each solve runs level by level (all rows/
515// columns of one level are mutually independent and solved by one work-group each). Forward uses
516// the CSR row mirror of L; backward uses the native CSC columns of L (= rows of L^T).
517static inline int _sp_chol_solve_cl(const _sp_chol_cl_t *const factor, const _sp_chol_cl_kernels_t kernels, cl_mem rhs)
518{
519 const int devid = factor->devid;
520 const int local_size = 64;
521
522 // forward substitution (L y = b), one launch per dependency level
523 const int kernel_fwd = kernels.fwd_level;
524 for(int level = 0; level < factor->nlev; level++)
525 {
526 const int n_level_cols = factor->lev_off[level + 1] - factor->lev_off[level];
527 if(!n_level_cols) continue;
528 size_t sizes[3] = { (size_t)n_level_cols * local_size, 1, 1 };
529 size_t local[3] = { local_size, 1, 1 };
530 dt_opencl_set_kernel_arg(devid, kernel_fwd, 0, sizeof(cl_mem), &rhs);
531 dt_opencl_set_kernel_arg(devid, kernel_fwd, 1, sizeof(cl_mem), &factor->values);
532 dt_opencl_set_kernel_arg(devid, kernel_fwd, 2, sizeof(cl_mem), &factor->rowptr);
533 dt_opencl_set_kernel_arg(devid, kernel_fwd, 3, sizeof(cl_mem), &factor->rowcol);
534 dt_opencl_set_kernel_arg(devid, kernel_fwd, 4, sizeof(cl_mem), &factor->rowpos);
535 dt_opencl_set_kernel_arg(devid, kernel_fwd, 5, sizeof(cl_mem), &factor->diagpos);
536 dt_opencl_set_kernel_arg(devid, kernel_fwd, 6, sizeof(cl_mem), &factor->levcols);
537 dt_opencl_set_kernel_arg(devid, kernel_fwd, 7, sizeof(int), &factor->lev_off[level]);
538 dt_opencl_set_kernel_arg(devid, kernel_fwd, 8, sizeof(int), &n_level_cols);
539 dt_opencl_set_kernel_arg(devid, kernel_fwd, 9, sizeof(double) * local_size, NULL);
540 if(dt_opencl_enqueue_kernel_2d_with_local(devid, kernel_fwd, sizes, local) != CL_SUCCESS) return 1;
541 }
542
543 // backward substitution (L^T x = y), one launch per backward dependency level
544 const int kernel_bwd = kernels.bwd_level;
545 for(int level = 0; level < factor->nlev_bwd; level++)
546 {
547 const int n_level_cols = factor->lev_off_bwd[level + 1] - factor->lev_off_bwd[level];
548 if(!n_level_cols) continue;
549 size_t sizes[3] = { (size_t)n_level_cols * local_size, 1, 1 };
550 size_t local[3] = { local_size, 1, 1 };
551 dt_opencl_set_kernel_arg(devid, kernel_bwd, 0, sizeof(cl_mem), &rhs);
552 dt_opencl_set_kernel_arg(devid, kernel_bwd, 1, sizeof(cl_mem), &factor->values);
553 dt_opencl_set_kernel_arg(devid, kernel_bwd, 2, sizeof(cl_mem), &factor->colptr);
554 dt_opencl_set_kernel_arg(devid, kernel_bwd, 3, sizeof(cl_mem), &factor->rowind);
555 dt_opencl_set_kernel_arg(devid, kernel_bwd, 4, sizeof(cl_mem), &factor->levrows_bwd);
556 dt_opencl_set_kernel_arg(devid, kernel_bwd, 5, sizeof(int), &factor->lev_off_bwd[level]);
557 dt_opencl_set_kernel_arg(devid, kernel_bwd, 6, sizeof(int), &n_level_cols);
558 dt_opencl_set_kernel_arg(devid, kernel_bwd, 7, sizeof(double) * local_size, NULL);
559 if(dt_opencl_enqueue_kernel_2d_with_local(devid, kernel_bwd, sizes, local) != CL_SUCCESS) return 1;
560 }
561 return 0;
562}
563
564#endif // HAVE_OPENCL
static const int row
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
@ DT_DEBUG_PERF
Definition darktable.h:741
static double dt_get_wtime(void)
Definition darktable.h:976
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
int dimension(struct dt_imageio_module_format_t *self, dt_imageio_module_data_t *data, uint32_t *width, uint32_t *height)
float *const restrict const size_t k
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2164
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:2580
int dt_opencl_write_buffer_to_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2348
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2155
gboolean dt_opencl_finish(const int devid)
Definition opencl.c:1375
int dt_opencl_enqueue_kernel_2d_with_local(const int dev, const int kernel, const size_t *sizes, const size_t *local)
Definition opencl.c:2170
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2415
#define ROUNDUP(a, n)
Definition opencl.h:78
const float factor
Definition pdf.h:90
static int _sp_ereach(const int dimension, const int *const restrict col_ptr, const int *const restrict row_index, const int k, const int *const restrict parent, int *const restrict pattern_stack, int *const restrict mark)
static void _sp_etree(const int dimension, const int *const restrict col_ptr, const int *const restrict row_index, int *const restrict parent, int *const restrict ancestor)
static void _sp_chol_cl_free(_sp_chol_cl_t *factor)
static _sp_chol_cl_t * _sp_chol_factor_cl(const int devid, const _sp_chol_cl_kernels_t kernels, const int dimension, const int *const restrict matrix_col_ptr, const int *const restrict matrix_row_index, const double *const restrict matrix_values)
static int _sp_chol_solve_cl(const _sp_chol_cl_t *const factor, const _sp_chol_cl_kernels_t kernels, cl_mem rhs)
static cl_mem _sp_cl_upload(const int devid, const void *data, const size_t bytes)