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