Skip to content

Installer overhaul: pins, provenance, a run-not-just-link BLAS check, backend selection, and docs - #162

Merged
mmelnich merged 4 commits into
mainfrom
randlapack-install-sh
Aug 13, 2026
Merged

Installer overhaul: pins, provenance, a run-not-just-link BLAS check, backend selection, and docs#162
mmelnich merged 4 commits into
mainfrom
randlapack-install-sh

Conversation

@mmelnich

@mmelnich mmelnich commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Problem

RandLAPACK's two installers had drifted apart. install/install.ps1 was rebuilt in #156 and pins every dependency, checksums downloads, records provenance and link-and-run validates the BLAS before building anything. install/install.sh did none of that, and the documentation described a script that no longer existed in several respects.

Eleven defects, all verified against main before touching anything:

Where Defect
install.sh:336,339,342 three git clone with no ref — blaspp, lapackpp, random123 all tracked upstream default branches
no BLAS validation at all
/opt/homebrew hardcoded, 8 places — hard-fails on Intel macOS and custom HOMEBREW_PREFIX
install.sh:331 mv "$REPO_DIR" relocated the user's own clone, breaking git worktrees
install.sh:211 : > "$LOG" truncated the log, destroying the previous run's output
install.sh:247 libdir search omitted lib/aarch64-linux-gnu
no --prefix; extras and benchmarks unconditional with no way to skip
install.ps1 zero references to RANDNLA_PROJECT_DIR, no --modify-rc equivalent
INSTALL_SCRIPT.md documented the pre-backend flag list, and still told people the script would move their clone

The two things most worth reviewing

Integer width is read back, not assumed

BLAS++ probes int32 before int64, and blas_int only filters which library names to try. For MKL that is a real choice — mkl_intel_lp64 and mkl_intel_ilp64 are different libraries. For OpenBLAS there is only -lopenblas, so a successful blas_int=int64 configure proves nothing: an LP64 build passes the int32 probe and is accepted. The resolved width therefore comes from BLAS++'s generated blas/defines.h after the build. Trusting the request would stamp LP64 installs as ILP64 and have every later run reuse them believing otherwise.

Verification runs the program, and that is the point

The conftest compiles, links and runs against the finished install — through BLAS++ and LAPACK++ rather than raw dgemm_, since that is how RandLAPACK reaches them — checking a gemm result and a gesdd factorization numerically. gesdd is chosen because it is the routine Apple's legacy Accelerate computes incorrectly.

Running it rather than only linking matters for a reason specific to integer width. BLAS++ and LAPACK++ do guard the int64_t downcast — to_blas_int (blaspp/src/blas_internal.hh:16-22) throws rather than truncates — but that guard keys off sizeof(blas_int) as declared by the header. If the headers say 64-bit while the library actually loaded is LP64, the guard compiles out and 64-bit values reach routines reading 32 bits. That surfaces not as wrong numbers but as nonsense control values: a misread workspace query becomes an absurd lwork, and the run dies in allocation or never finishes. Nothing catches it by inspection.

A cross-project collision, found while testing

Dependency install directories now carry backend and GPU in their names (blaspp-mkl-cpu-install). That is not cosmetic.

RandBLAS's installer uses the same RandNLA-project layout and named its BLAS++ install identically (blaspp-<backend>-install), with a different stamp filename. With a shared RANDNLA_PROJECT_DIR, this script would rebuild over RandBLAS's BLAS++ — their stamp never matches ours — and RandBLAS's next run would reuse an artifact we had replaced underneath it, while its own stamp still described the original. Distinct names make it impossible; sharing is instead explicit via BLASPP_INSTALL_DIR, verified by the conftest.

Everything else

  • macOS keeps Homebrew OpenBLAS as the default. Apple's legacy Accelerate has a broken divide-and-conquer gesdd, and RandLAPACK calls gesdd in rl_rsvd.hh:146, rl_abrik.hh:692, rl_revd2.hh:208, rl_preconditioners.hh:355 and rl_util.hh:413. That is what TEMPORARY: quarantine the macOS Accelerate gesdd test (revert after Mark's BLAS++/LAPACK++ review) #157's quarantine is about. --blas=accelerate warns, citing Revert the macOS Accelerate quarantine (PR #157) once the upstream Accelerate PRs are approved #159. The hardcoded /opt/homebrew is fixed regardless.
  • Extras and benchmarks stay built by default (--no-extras / --no-benchmarks), unlike RandBLAS's examples — they need nothing this script has not already built.
  • The clone is never moved; a symlink gives the same layout and the path CI invokes still resolves.
  • RandBLAS is untouched — still a pinned submodule, still authoritative.
  • install.ps1 now honours RANDNLA_PROJECT_DIR with the same precedence as install.sh, gains -ModifyEnvironment (User-scope, the only thing that survives a new shell) and -Prefix, and its path-length warning now checks the resolved path rather than only an explicitly passed one.
  • Progress rendering in three tiers, determinate from the build tool's own output, with tier 0 byte-identical to the plain step list — plus a CI assertion that redirected output carries no escape sequence.
  • INSTALL_SCRIPT.md rewritten: current flags, corrected layout diagram, and a new section 6 with a tested-configuration table drawn from the CI lanes, the per-backend integer width, whether LP64 actually limits RandLAPACK, and why macOS is on OpenBLAS.

Verification

Local Linux (gcc 15.2, oneAPI MKL, NVIDIA GPU present):

Scenario Result
Fresh MKL build, --no-gpu 9/9 steps, ILP64 confirmed from blas/defines.h
Re-run in place all six dependency steps reused, 1s rebuild
Full default run (extras + benchmarks) 13/13 steps
--no-gpu--gpu BLAS++ provenance invalidated, rebuilt rather than reused
Dependency discovery all three reused, 3/3 steps
Run from a git worktree clone left in place, worktree still functional
Output redirected no ANSI escapes, no carriage returns
Full test suite 313/313 pass
RandBLAS then RandLAPACK, shared RANDNLA_PROJECT_DIR both dependency sets intact
RandLAPACK reusing RandBLAS's BLAS++ reused, only LAPACK++ built, 5/5 steps

Windows 11, Windows PowerShell 5.1, VS 2022 Build Tools: all three -ProjectDir precedence cases resolve as intended, and the User-scope environment write round-trips.

RandBLAS submodule bump

Also bumps the vendored RandBLAS from 04f2018 to 952251c (RandBLAS main), and RandLAPACK_RandBLAS_PIN with it.

The three commits in that range are #185 (vcpkg manifest fix), #186 (CMake hygiene) and #187 (installer scripts). None of them touch RandBLAS/ headers — the library code is byte-identical, and everything in the range is build machinery. Two pieces of it matter here:

  • RandBLAS's CMake floor moved 3.12 → 3.21. RandLAPACK already declares 3.21, so nothing to change; the submodule now simply enforces what RandLAPACK already required.
  • RandBLAS exports randblas_stage_runtime_dlls() from its installed package and prints a configuration summary. As a subproject under RandLAPACK, which configures it with BUILD_TESTS=OFF, that summary reports tests as deliberately skipped rather than warning.

CMakeLists.txt enforces that the pin variable and the submodule move together, and it means it: the cross-check compares the variable against git ls-tree HEAD RandBLAS, so a staged-but-uncommitted bump fails configure until both land in one commit. Worth knowing if you ever bump this iteratively — it is not satisfiable before committing.

Verified: clean build against the bumped submodule, and the full suite re-run.

Notes for reviewers

@mmelnich

Copy link
Copy Markdown
Contributor Author

Test suite result, as promised: 313/313 pass, 0 failed on the build this installer produced (Linux, gcc 15.2, oneAPI MKL, ILP64, --no-gpu). All CI checks green as well.

For the record, since the run took a while and it is the slow tests that matter here: TestCQRRT.CQRRT_large_full_rank alone is ~1080s and several ABRIK/REVD2 cases are 60-70s each, so a local full-suite run against an installer build is around 40 minutes wall clock, not something to expect in a quick loop.

@mmelnich mmelnich changed the title install.sh: pins, provenance, a run-not-just-link BLAS check, and backend selection Installer overhaul: pins, provenance, a run-not-just-link BLAS check, backend selection, and docs Aug 13, 2026
Base automatically changed from randlapack-ci-fixes to main August 13, 2026 20:35
…kend selection

Brings the Unix installer up to the standard install.ps1 reached in #156, and
in line with the RandBLAS installer. The Windows script already pinned every
dependency, recorded provenance and validated the BLAS before building; this one
did none of that. Eleven defects, all verified against main before changing
anything:

* Three `git clone` calls with no ref (:336,339,342) -- blaspp, lapackpp and
  random123 all tracked upstream default branches, so two runs of the same
  script could build different source. Now pinned to the refs this repository's
  own Windows provisioner already validated, fetched one commit deep, with a
  provenance stamp on each install and each source tree.

* No BLAS validation at all. The installer now compiles, links and *runs* a
  program against the finished install, through BLAS++ and LAPACK++ rather than
  raw dgemm_ (that is how RandLAPACK reaches them), checking a gemm result and
  a gesdd factorization numerically.

  The gesdd call is deliberate. It is the routine Apple's legacy Accelerate
  computes incorrectly, so a broken SVD surfaces at install time rather than in
  someone's RSVD output. And the run-it-not-just-link-it part matters for a
  reason peculiar to integer width: BLAS++/LAPACK++ guard the int64_t downcast
  and throw rather than truncate, but that guard keys off sizeof(blas_int) as
  declared by the *header*. If the headers say 64-bit while the loaded library
  is LP64, the guard compiles out and 64-bit values reach routines reading 32
  bits -- which shows up not as wrong numbers but as an absurd workspace size
  and a run that dies in allocation or never finishes. Nothing catches that by
  inspection.

* /opt/homebrew hardcoded in eight places, which hard-fails on Intel macOS and
  any custom HOMEBREW_PREFIX. Now `brew --prefix`.

* The script moved the user's own clone into <project>/lib/RandLAPACK, breaking
  git worktrees. It now works in place and puts a symlink there instead, so the
  layout still reads as complete and the path CI invokes still resolves.

* The log was truncated on every run (:211), destroying the previous run's
  output at exactly the moment you want it. Now appended with a run header.

* The libdir search omitted lib/aarch64-linux-gnu.

* No --prefix; extras and benchmarks had no way to be skipped. Both added,
  with extras and benchmarks still built by DEFAULT (--no-extras /
  --no-benchmarks to opt out): unlike RandBLAS's examples they need nothing this
  script has not already built, so default-on costs only time.

Backend selection is new: --blas=auto|openblas|mkl|accelerate|custom,
--blas-int, --blas-libraries. Integer width is requested per backend and then
READ BACK from BLAS++'s generated blas/defines.h rather than assumed, because
BLAS++ probes int32 before int64 while blas_int only filters library *names* --
so for MKL the request genuinely selects mkl_intel_ilp64, but for OpenBLAS
there is only -lopenblas and an LP64 build passes the int32 probe and is
accepted. Trusting the request would stamp LP64 installs as ILP64.

macOS deliberately keeps Homebrew OpenBLAS as the default rather than moving to
Accelerate. Apple's legacy Accelerate has a broken divide-and-conquer gesdd and
RandLAPACK calls gesdd in rl_rsvd, rl_abrik, rl_revd2, rl_preconditioners and
rl_util, so Accelerate would quietly return wrong singular values across most
of the SVD-based drivers -- which is what #157's quarantine of
TestQB.Polynomial_Decay_general1 is about. --blas=accelerate is allowed and
warns, citing #159.

The provenance stamp includes the GPU setting, and the dependency install
directories now carry the backend and GPU configuration in their names
(blaspp-mkl-cpu-install). Both matter, and the second one for a reason outside
this project: RandBLAS's installer uses the same RandNLA-project layout and
named its BLAS++ install identically. Sharing that name meant this script would
rebuild over RandBLAS's BLAS++ (their stamp is a different file, so it never
matched ours), and RandBLAS's next run would reuse an artifact we had replaced
underneath it while its own stamp still described the original. Distinct names
make that impossible; cross-project sharing is instead explicit, via
BLASPP_INSTALL_DIR, with the conftest confirming the result works.

Also adds the three progress tiers from the RandBLAS installer -- determinate,
parsed from Ninja's "[12/34]" and Make's "[ 42%]" -- with tier 0 (redirected
output) byte-identical to the plain step list, and a CI assertion that
redirected output contains no escape sequence or carriage return.

CI: the install-script lanes globbed rather than hardcoded for the new
dependency directory names, so they are not coupled to that naming, and the
discovery assertion updated to the text the new script prints.

Verified on Linux (gcc 15.2, oneAPI MKL): fresh MKL build; idempotent re-run;
full default run with extras and benchmarks (13/13 steps); switching --no-gpu to
--gpu correctly invalidating the BLAS++ provenance and rebuilding; dependency
discovery reducing to 3 steps; running from a git worktree with the clone left
in place; redirected output escape-free; and RandBLAS-then-RandLAPACK into one
shared RANDNLA_PROJECT_DIR leaving both projects' dependencies intact, plus
RandLAPACK reusing RandBLAS's BLAS++ when pointed at it explicitly.
…nvironment

Brings the Windows installer's location handling in line with install.sh, which
is what makes the shared RandNLA-project convention actually work on both
platforms.

RANDNLA_PROJECT_DIR was read nowhere in this script. Its precedence is now
identical to install.sh's -- the flag, then the environment variable, then a
sibling of the clone -- so a machine that has already installed one RandNLA
project does not scatter a second tree somewhere else.

-ModifyEnvironment persists that variable for the user with
[Environment]::SetEnvironmentVariable(..., "User"), the Windows equivalent of
install.sh's --modify-rc and the only mechanism that survives opening a new
shell; setting $env: alone would last for the current process. Opt-in, matching
install.sh: the default touches nothing and prints the setx command instead.

-Prefix installs RandLAPACK itself somewhere other than
<ProjectDir>\install\RandLAPACK-install, for a module tree or any prefix a site
wants to own. Dependencies stay in the project directory.

Also moved the project-directory length warning to after path resolution. It
was guarded on `$ProjectDir -ne ""`, so it only ever fired for an explicitly
passed -ProjectDir -- never for the default, which is the common case and is
derived from wherever the clone happens to sit, so the more likely one to be
long.

Verified on Windows 11 under Windows PowerShell 5.1 with VS 2022 Build Tools:
all three precedence cases resolve as intended (sibling default, environment
variable honoured, flag beating the variable), and the User-scope environment
write round-trips. The oneMKL discovery, BLAS link check and space-free import
library staging from #156 were all observed still working along the way.
…tions

The guide described a script that no longer exists in several respects. Four
changes.

The documented flag list predated backend selection entirely. It now covers
--blas, --blas-int, --blas-libraries, --prefix, --no-extras, --no-benchmarks
and --no-progress, and states that extras and benchmarks are built by default
with opt-outs -- deliberately unlike RandBLAS's examples, which are opt-in
because they pull in dependencies RandBLAS itself does not need.

The directory-structure diagram said the script would MOVE your clone into
lib/. It no longer does -- that broke git worktrees -- and lib/RandLAPACK is a
symlink. The diagram also now shows the install/ tree, whose dependency
directories carry the backend and GPU configuration in their names.

"What the Script Does" described cloning dependencies from their default
branches and moving the RandLAPACK directory. It now describes the pinned
fetches, the provenance stamps that make reuse configuration-aware, and the
verification step -- including why that step runs a program rather than only
linking one.

New section 6 covers what the earlier guide had nowhere: a tested-configuration
table drawn from the CI lanes, the per-backend integer width with the reason
OpenBLAS is the awkward case, whether LP64 actually limits RandLAPACK (rarely,
and loudly -- with the genuinely silent header/library mismatch called out
separately), and why macOS defaults to Homebrew OpenBLAS rather than Accelerate.

Section 6 is placed before the GPU-benchmark appendix so the numbered sections
stay contiguous.
RandBLAS main has moved three commits since 04f2018: the vcpkg manifest fix
(#185), the CMake hygiene pass (#186), and the installer scripts (#187).

None of them touch RandBLAS/ headers -- the library code is byte-identical.
Everything in the range is build machinery, and two pieces of it matter here:

  * RandBLAS's CMake floor moved from 3.12 to 3.21. RandLAPACK already declares
    3.21, so nothing to do, but the submodule now enforces what RandLAPACK
    already required.
  * RandBLAS now exports randblas_stage_runtime_dlls() from its installed
    package and prints a configuration summary. As a subproject under
    RandLAPACK, which configures it with BUILD_TESTS=OFF, that summary reports
    the tests as deliberately skipped rather than warning.

RandLAPACK_RandBLAS_PIN is updated in the same commit, which CMakeLists.txt
requires and enforces: the cross-check compares the variable against
`git ls-tree HEAD RandBLAS`, so a staged-but-uncommitted bump fails configure
until the two land together.
@mmelnich
mmelnich force-pushed the randlapack-install-sh branch from ecb3f47 to f455462 Compare August 13, 2026 20:40
@mmelnich
mmelnich merged commit ab05872 into main Aug 13, 2026
12 checks passed
@mmelnich
mmelnich deleted the randlapack-install-sh branch August 13, 2026 21:11
mmelnich added a commit that referenced this pull request Aug 29, 2026
Brings in the six commits main gained since the 2026-08-05 refresh (5da0674):

  1cbe9e0  Native Windows (MSVC) support (#154)
  4cc72a8  TEMPORARY: quarantine the macOS Accelerate gesdd test (#157)
  e4e8565  Windows installation overhaul (#156)
  26eaed6  CI: fix the broken oneMKL cache path and drop a dead ctest exclusion (#161)
  ab05872  Installer overhaul: pins, provenance, run-not-just-link BLAS check (#162)
  d4ee721  Include the GPU layer from RandLAPACK.hh, guarded on __CUDACC__ (#169)

Two conflicts, both in test/, both from main's MSVC work colliding with branch
test infrastructure. Neither is library code.

test/CMakeLists.txt: kept BOTH sides. Main adds /bigobj and
randlapack_stage_runtime_dlls(RandLAPACK_tests); the branch adds
PROPERTIES TIMEOUT 300 to gtest_discover_tests. The ordering is forced rather
than stylistic: the DLL staging must precede gtest_discover_tests, because that
command runs the test binary at build time to enumerate cases and on Windows
that run needs the DLLs already staged.

test/drivers/test_cqrrt.cc: both sides made the same code change, atol from
eps^0.7 to eps^0.65, and differed only in the justification comment. Merged the
comment to credit both backends. Two unrelated BLAS implementations independently
put norm_0/sqrt(n) over the tighter bound on this case, vcpkg oneMKL sequential
on Windows at 4.4e-11 and Apple Accelerate on macOS at ~1.7e-11. That it was
found twice independently is what says the bound was wrong, not the backends.

Three things worth recording.

1. 26eaed6 removed the dead ctest exclusion
   --exclude-regex "^TestABRIK\.ABRIK_catch_instability" that this branch was
   still carrying in six places across three workflow files, so the branch no
   longer needs to. One correction to that PR's description, which states there
   is no history of the test under that name: edab935^ contains four such tests
   (_prelim, _good, _bad, _worse) and the regex was unanchored on the right, so
   it matched all four until edab935 deleted them on 2026-02-02. The exclusion
   was live once, not always inert.

2. Main's 4cc72a8 quarantines TestQB.Polynomial_Decay_general1 on macOS, while
   this branch's 5a6b6d3 fixed that same test on Apple Silicon by switching its
   reference SVD from gesdd to gesvd. Both survive the merge, so the
   quarantine's own "if this passes, delete the suppression" warning will now
   fire on every macOS run. Left in place deliberately: 4cc72a8 is a temporary
   main-side commit pinned to two open upstream PRs, and reverting it here would
   fight main. Flagged for the PR thread instead.

3. install.sh shrinks by 484 lines because ab05872 turned it into a wrapper
   delegating to install/install.sh. Nothing was lost, and the branch's own
   cluster-install optimisation from 2ca557e survives there as -Dbuild_tests=OFF
   for both blaspp and lapackpp, using those projects' actual lowercase option
   name rather than the branch's BUILD_TESTING.

The RandBLAS submodule advances from 8417f4b (1.1.0-32) to 952251c (1.1.0-42).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant