Skip to content

OpenCL cholesky_decompose returns a wrong factor on GPU for matrices with more than 64 rows #3429

Description

@jachymb

Description

The cholesky_decompose kernel (stan/math/opencl/kernels/cholesky_decompose.hpp) runs as one work group with one work item per row. The work items pass intermediate results to each other through the __global buffer A: work item 0 writes the diagonal entry of column j, and after a barrier every work item i > j reads that entry and row j, which work item j wrote in earlier steps. The barriers are barrier(CLK_LOCAL_MEM_FENCE). That flag orders operations on local memory only; reading buffer contents written by other work items before the barrier needs CLK_GLOBAL_MEM_FENCE.

On an AMD Radeon integrated GPU (OpenCL device gfx1035, AMD-APP 3652.0, Windows) the difference is visible. As soon as the work group has more than 64 work items, some of them read entries of A as they were before other work items overwrote them. The factor is wrong, by more than 100 % in some entries, and no error is raised. The blocked algorithm in stan/math/opencl/cholesky_decompose.hpp passes blocks of up to 256 rows to this kernel, so any matrix_cl Cholesky decomposition with more than 64 rows is affected. With a standalone copy of the kernel, 60 of 60 runs with N between 65 and 256 were wrong and 40 of 40 runs with N up to 64 were correct; with barrier(CLK_GLOBAL_MEM_FENCE) all 100 were correct.

With PoCL and with the Intel OpenCL CPU runtime the kernel gives correct results as it is.

The tests cholesky_decompose_small, _big and _big_tuning_opts in test/unit/math/opencl/cholesky_decompose_test.cpp do not run the kernel on any device: they call the Eigen overload of cholesky_decompose, which no longer dispatches to OpenCL, so they compare Eigen with Eigen.

Example

int N = 200;
Eigen::MatrixXd m = Eigen::MatrixXd::Random(N, N);
Eigen::MatrixXd a = m * m.transpose() + N * Eigen::MatrixXd::Identity(N, N);

stan::math::matrix_cl<double> a_cl(a);
stan::math::opencl::cholesky_decompose(a_cl);

Eigen::MatrixXd diff
    = stan::math::from_matrix_cl(a_cl) - stan::math::cholesky_decompose(a);
std::cout << diff.cwiseAbs().maxCoeff() << std::endl;

Expected Output

Expected: a difference at rounding level.

Current, on the GPU above, in five runs of this computation as a unit test (MinGW g++ 15.2): 13,852 of the 40,000 entries differ by more than 1e-8. The largest difference is 2.9 to 4.3, on a diagonal entry whose correct value is about 16.

Activity

  1. jachymb commented on Oct 4, 2026

    @jachymb
    ContributorAuthor

    I'm working on this, there are probably more issues of this kind in the codebase, some of which may be invisible due to basically being lucky with memory layout.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions