openblas_set_num_threads() is silently overridden when built with USE_OPENMP #5806
Description
Summary
When OpenBLAS is built with USE_OPENMP, calling openblas_set_num_threads(1) before a BLAS operation has no lasting effect.
The thread count is unconditionally overridden back to omp_get_max_threads() on every BLAS call, making the API a silent no-op.
When a user uses openblas_set_num_threads(1) in the hope to get deterministic BLAS results, they will eventually find that this does not work, and results are still nondeterministic, because of this.
The workaround is to run with the OMP_NUM_THREADS=1 environment variable set. But it seems very wrong that this cannot be done with code, and that a function that is explicitly named to set the number of threads, does not actually do that.
Call chain demonstrating the problem
- User calls
openblas_set_num_threads(1)which callsgoto_set_num_threads(1), settingblas_cpu_number = 1. - User then calls
cblas_sgemm(); code is invoid CNAME()ingemm.c. - Inside,
args.nthreads = get_gemm_optimal_nthreads(MNK)callsnum_cpu_avail(3). num_cpu_avail()callsomp_get_max_threads(), which returns the OpenMP default (all CPUs, sinceOMP_NUM_THREADSis unset).if (blas_cpu_number != openmp_nthreads)is true (1 != N).goto_set_num_threads(openmp_nthreads)overridesblas_cpu_numberback to N.num_cpu_avail()returnsblas_cpu_number(now N), which flows toGEMM_THREAD(..., args.nthreads)then toexec_blas(num_cpu, queue)then to#pragma omp parallel for.
Root cause
goto_set_num_threads() does NOT call omp_set_num_threads() -- confirmed by searching the entire driver/ directory (zero results). So the OpenMP runtime's idea of the thread count is never updated, and num_cpu_avail() always re-syncs blas_cpu_number from omp_get_max_threads().
Expected behavior
openblas_set_num_threads(1) should durably limit OpenBLAS to 1 thread, even when built with USE_OPENMP. The only current workaround is setting OMP_NUM_THREADS=1 in the environment, which is a process-global side effect affecting all OpenMP consumers.
Suggested fix
When USE_OPENMP is defined, goto_set_num_threads() should also call omp_set_num_threads(num_threads) so that subsequent calls to omp_get_max_threads() in num_cpu_avail() return the value the user requested.
I have not tested this yet, but it seems the most sensical to me that this should work.
Practical impact
This breaks deterministic computation for any library that links OpenBLAS and tries to force single-threaded BLAS via the documented API. Multi-threaded floating-point reductions in exec_blas() produce nondeterministic results due to different summation orders across runs.
I found it when trying to make llama-cpp deterministic with its --threads 1 option and found that it didn't work.
Version
Tested on commit 8cecf899e (v0.3.32).
Activity
nh2 commentedon May 12, 2026
I found that this does work indeed:
But I don't think anymore that it's a good idea to do that, because it sets the global OpenMP thread limit to that, and maybe the user wants to limit only the thread limit of OpenBLAS to get it deterministic, but continue to use OpenMP in their own code that may be parallel and still deterministic.
So it would be better to just make that when the user calls
openblas_set_num_threads(), only OpenBLAS's own OpenMP loops are limited to that number of threads.nh2 commentedon May 12, 2026
I am trying to make sense of this whole code but it is really quite difficult.
Consider:
There are so many questions:
What is the purpose of this function? There are no comments, no documentaiton, no intent.
The formatting makes it impossible to read.
int num_cpu_avail()suggests that it returns numbers of threads to use, a read-only, pure computation.Then why does it also have a side effect of calling
goto_set_num_threads()?Above it are 3 variables containing state. What is their purpose? Unclear.
What is the concrete difference in purpose between
blas_cpu_numberandblas_num_threads, in comparison to those 3 state variables mentioned above such asblas_omp_number_max?The USAGE.md it says
But it doesn't explain anywhere why this is made a compile-time constant, given that different computers clearly have different numbers of cores.
Further
OK, that's confusing, "and what does "can be relevant" mean concretely?
That sounds very bad.
But again nowhere does it say: Why?
If this hardcoding of
NUM_THREADSmakes all these problems, why is it done?Why not just do what every normal program does, and determine the number of threads to use at runtime?
This makes it very difficult to work on even the most trivial fixes, such as making
openblas_set_num_threads(1)make OpenBLAS use 1 thread.nh2 commentedon May 12, 2026
The
README.mdalso saysIt says
yet in every call to
num_cpu_avail(),goto_set_num_threads()is called, so clearly not only once.It says
yet, as this issue describes, they don't really have an effect in that case.
nh2 commentedon May 12, 2026
PR that should fix it properly: #5808
Make OpenBLAS's usage of OpenMP respect openblas_set_num_threads(). F…