Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions dev/adr/adr-0006-openmp-default-feature.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
sidebar_label: 0006 - OpenMP as default for the OpenBLAS device
sidebar_position: 6
rstsr_meta:
date: 2026-08-14
rstsr_version: "0.8.0"
ai_generated: true
---

# ADR 0006 - `openmp` as a default feature of `rstsr-openblas`

`rstsr-openblas` flips its `default` features from `["linalg"]` to `["linalg", "openmp"]`, and the integration crate `rstsr` enables it through its own default features (`"rstsr-openblas?/openmp"` in `default`, effective whenever the `openblas` backend feature is on) — there is deliberately no separate `openmp` feature at the `rstsr` level. This reverses the earlier position recorded in the crate readme ("we currently decided not make `openmp` as default feature") — that sentence is deleted along with the flip.

The `openmp` feature is a pure cfg gate: it compiles in the `omp_set_num_threads` / `omp_get_max_threads` FFI references in `threading.rs`, and thereby **requires an OpenMP runtime at link time** (`gomp` on Linux-GNU, `omp` on macOS/LLVM, `vcomp` on MSVC). It does not change any Rust API. `dynamic_loading` unlocks the same runtime code paths through `libloading` with no link-time requirement, but is not a general recommendation here: with LAPACK enabled the dynamic-loading table covers too many symbols to be the default answer.

Three forces drove the reversal. First, **performance**: in the project's testing, OpenBLAS built with OpenMP generally outperforms the pthread build. Second, the **footgun the non-default created**: a user with an OpenMP-built `libopenblas` and neither `openmp` nor `dynamic_loading` enabled hit a runtime panic on the threading API — and because the workspace pins the device dependency with `default-features = false`, users entering through the `rstsr` integration crate had *no* feature to escape with (the umbrella exposed no `openmp` passthrough at all). Third, the alternatives were rejected on the merits: always using `openblas_set_num_threads` is not a substitute, because the OpenMP and pthread builds behave differently and must be treated as distinct threading backends (and OpenBLAS's own getters have historically misbehaved on OpenMP builds, which is why `omp_get_max_threads` is used at all); and `dynamic_loading` for the reason above.

## Blast radius

- **Breaking (accepted, hence the 0.8.0 bump)**: direct users of `rstsr-openblas` default features gain a hard OpenMP-runtime link requirement. For pthread-OpenBLAS users the feature is *compatible* (runtime takes the `OPENBLAS_THREAD` path) but the extra runtime must still be linked, buying nothing; on macOS (Apple clang ships no libomp) and MSVC (`vcomp` is not auto-linked) this is a hard link error rather than an inconvenience. Opt-out: `default-features = false, features = ["linalg"]`. The same applies to users of the integration crate with default features plus `openblas` — the mainstream entry path deliberately behaves like the device crate.
- **Also affected, the other direction**: umbrella users building `rstsr` with `default-features = false` (enabling `openblas` manually) get no `openmp` at all; against an OpenMP-built OpenBLAS the threading API panics, and the escape is a direct `rstsr-openblas` dependency with `openmp` (feature unification applies it graph-wide).
- **Not affected**: docs.rs (compile-only). The book workspace (`rstsr-openblas >= 0.7.9` with explicit `features = ["linalg", "openmp"]`) picks the new version up transparently.
- **Caveat**: cargo feature unification is graph-global — opting out via `default-features = false` is not hermetic if any other crate in the build graph enables `rstsr-openblas` defaults.

## Consequences

The panic message in `threading.rs` (reachable only for `default-features = false` builds against OpenMP-built OpenBLAS) now points out that the integration crate enables `openmp` by default, and directs non-default `rstsr` users to a direct `rstsr-openblas` dependency. The verification matrix also exposed a pre-existing bug, fixed alongside: the *internal* caching `OpenBLASConfig::get_parallel` panicked whenever both features were off, without checking what `openblas_get_parallel()` actually reported — so the documented opt-out broke `set_num_threads`/`get_num_threads` even on pthread builds. It now mirrors the free `get_parallel()`: it only panics when the library really reports `OPENBLAS_OPENMP`. CI gains a pthread job running `cargo test -p rstsr-openblas --release` with default features (and `--test-threads=1`, per the BLAS test convention) against `libopenblas-pthread-dev`, so both runtime branches (`OPENBLAS_THREAD` and `OPENBLAS_OPENMP`) are exercised under the default feature set; the pre-existing OpenMP job keeps its explicit `--features="openmp linalg"` so intent survives any future re-flip. The compatibility matrix above (feature × OpenBLAS build × link setup) is verified against locally built OpenBLAS 0.3.34 (both variants) and recorded in the crate readme as user-facing guidance.
18 changes: 14 additions & 4 deletions docs/fundamentals/00-installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -134,23 +134,33 @@ Features exclusive to the integration library `rstsr` include:

The default backends supported by `rstsr-core` are implemented purely in Rust, so no special build.rs setup is required.

When the `openblas` backend feature is enabled, the default features of `rstsr` also turn on the `openmp` feature of `rstsr-openblas` (the same default the device crate ships), which adds the OpenMP-runtime link requirement described in the `rstsr-openblas` section below. If you build `rstsr` with `default-features = false` (for example enabling `openblas` manually), `openmp` is not enabled; an OpenMP-built OpenBLAS then makes the threading API panic, and you can re-enable it through a direct `rstsr-openblas` dependency with the `openmp` feature.

### rstsr-openblas

This library is not mandatory for users. It is only required if the user needs a BLAS backend.

- `linalg`: Whether to compile `rstsr-linalg-traits` to implement linear algebra interfaces.
- `linalg`: Whether to compile `rstsr-linalg-traits` to implement linear algebra interfaces. Enabled by default.
- `ilp64`: Whether the integer type in BLAS is `int32_t` or `int64_t`. For example, OpenBLAS compiled with default options on Linux systems typically uses `int32_t`; in such cases, this option does not need to be enabled.
- `openmp`: Whether to add support for OpenMP-compiled OpenBLAS. This compilation option requires users to include the OpenMP library in build.rs or `RUSTFLAGS`. Generally, this option is recommended. If the user is certain that the linked OpenBLAS uses pthread for parallelism, this option can be skipped; however, note that OpenBLAS generally performs better with OpenMP parallelism.
- `openmp`: Support for OpenMP-compiled OpenBLAS (threading control via the OpenMP runtime API). **Enabled by default** since v0.8.0. OpenBLAS generally performs better with OpenMP parallelism, and the feature is also compatible with pthread-built OpenBLAS: the runtime reports the pthread mode and the `openblas_*` API is used, with the linked OpenMP runtime simply unused. The cost is that an OpenMP runtime must always be linked (see the build.rs example below). If you do not want this requirement, disable default features and re-enable `linalg`.

To make the OpenBLAS backend work, users need to manually include the following lines in their project's build.rs:

```rust
// in build.rs
// if your library is named `libopenblas.so`
println!("cargo:rustc-link-lib=openblas");
// if your openblas is compiled with OpenMP (but not pthread)
// and your openmp is GNU's distribution
// required by the default `openmp` feature: the OpenMP runtime
// (`gomp` for GNU's distribution; `omp` on macOS/LLVM; `vcomp` on MSVC)
println!("cargo:rustc-link-lib=gomp");
```

Alternatively, include these libraries in the `RUSTFLAGS` environment variable.

If your OpenBLAS is built with pthread and you do not want to link any OpenMP runtime, disable the default features:

```toml
rstsr-openblas = { version = "0.8", default-features = false, features = ["linalg"] }
```

Note that cargo feature unification is graph-global: if any other crate in your build enables `rstsr-openblas` default features, `openmp` is enabled for the whole build. For the full compatibility matrix (OpenBLAS build × feature × link setup, verified against OpenBLAS 0.3.34 built both ways), see the readme of [`rstsr-openblas`](https://docs.rs/rstsr-openblas).
Original file line number Diff line number Diff line change
Expand Up @@ -133,21 +133,31 @@ rstsr-core 从设计上,预期需要实现 Python array API standard 中大部

rstsr-core 默认支持的后端,都是以纯 Rust 实现;这种情况下不需要特别设置 build.rs。

当启用 `openblas` 后端 feature 时,`rstsr` 的默认 feature 会一并开启 `rstsr-openblas` 的 `openmp` feature(与该设备库自身的默认设置一致),从而引入下文 `rstsr-openblas` 一节所述的 OpenMP 运行时链接要求。若以 `default-features = false` 构建 `rstsr`(例如手动开启 `openblas`),则 `openmp` 不会被启用;此时若链接以 OpenMP 编译的 OpenBLAS,线程数控制 API 将会 panic,可以通过直接依赖 `rstsr-openblas` 并开启其 `openmp` feature 的方式重新启用。

### rstsr-openblas

该库并不是用户必须选择的。当用户对 BLAS 后端有需求时,才需要将 rstsr-openblas 纳入 Cargo.toml 作为依赖。

- `linalg`:是否编译 `rstsr-linalg-traits` 以对线性代数的接口作实现。
- `linalg`:是否编译 `rstsr-linalg-traits` 以对线性代数的接口作实现。默认开启。
- `ilp64`:BLAS 中的整数类型是 `int32_t` 还是 `int64_t`。举例而言,在 Linux 系统下默认选项编译的 OpenBLAS 一般是 `int32_t`;在这种情况下不需要开启该选项。
- `openmp`:是否增加对 OpenMP 编译的 OpenBLAS 的支持。该编译选项要求用户在 build.rs 或 `RUSTFLAGS` 中引入 OpenMP 库。一般来说建议开启该选项。如果用户确定链接的 OpenBLAS 的并行模式是 pthread,那么该选项可以不用开启;但同时需要注意,OpenBLAS 一般来说在 OpenMP 并行模式下有更高的运行效率
- `openmp`:对以 OpenMP 编译的 OpenBLAS 的支持(通过 OpenMP 运行时 API 控制线程数)。**自 v0.8.0 起默认开启**。OpenBLAS 在 OpenMP 并行模式下一般有更高的运行效率;且该选项与 pthread 编译的 OpenBLAS **兼容**:运行时检测到 pthread 模式后会改用 `openblas_*` API,链接的 OpenMP 运行时不会被使用。其代价是无论 OpenBLAS 以何种方式编译,都必须链接 OpenMP 运行时(见下文的 build.rs 示例)。若不希望有该链接要求,可以关闭默认 feature 并按需重新开启 `linalg`

为了能让 OpenBLAS 后端可以工作,用户需要手动在自己项目中的 build.rs 中引入如下的语句:
```rust
// in build.rs
// if your library is named `libopenblas.so`
println!("cargo:rustc-link-lib=openblas");
// if your openblas is compiled with OpenMP (but not pthread)
// and your openmp is GNU's distribution
// required by the default `openmp` feature: the OpenMP runtime
// (`gomp` for GNU's distribution; `omp` on macOS/LLVM; `vcomp` on MSVC)
println!("cargo:rustc-link-lib=gomp");
```
或者编译的环境变量 `RUSTFLAGS` 中引入这两个库。

如果用户链接的 OpenBLAS 以 pthread 编译、且不希望链接任何 OpenMP 运行时,可以关闭默认 feature:

```toml
rstsr-openblas = { version = "0.8", default-features = false, features = ["linalg"] }
```

需要注意,cargo 的 feature 统一是全局性的:只要构建图中任一 crate 开启了 `rstsr-openblas` 的默认 feature,整个构建就会启用 `openmp`。完整的兼容性矩阵(OpenBLAS 编译方式 × feature × 链接配置,已针对两种方式编译的 OpenBLAS 0.3.34 验证)见 [`rstsr-openblas` 的文档](https://docs.rs/rstsr-openblas)。
Loading