Skip to content

cbcbox: Add version 2.935 - #3260

Draft
luhenry wants to merge 13 commits into
mainfrom
cbcbox
Draft

luhenry wants to merge 13 commits into
mainfrom
cbcbox

Conversation

@luhenry

@luhenry luhenry commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Compiles OpenBLAS, SuiteSparse AMD, and the CoinUtils/Osi/Clp/Cgl/Cbc stack from source and bundles the resulting CBC MILP solver binaries and shared libraries. Upstream publishes no riscv64 wheel.

Mirrors upstream's wheel.yml Linux ARM64 jobs (compile generic + debug, then package with compilation skipped).

Differs from upstream

  • manylinux_2_39_riscv64 (dnf) instead of manylinux2014_aarch64/manylinux_2_28_aarch64 (yum) - manylinux2014 predates riscv64 entirely

Matrix: cp313 only, compiled once - final wheel is tagged py3-none, same as upstream's own aarch64/x86_64 jobs

Testing: same as upstream

License: EPL-2.0 (CoinUtils/Osi/Clp/Cgl/Cbc); wheel also bundles OpenBLAS and SuiteSparse AMD (BSD-3-Clause) and libgfortran (GPL, from the build image) with no licence text for the first two, same as upstream's existing x86_64/aarch64 wheels.

Patches

  • 0001-setup.py-map-riscv64-to-manylinux_2_39_riscv64-in-th.patch - Inappropriate [no riscv64 CI upstream to exercise it]. Without it the wheel is tagged plain linux_riscv64, which pip rejects; riscv64-only.

Not pre-validated locally (no docker/riscv64 available in this sandbox); CI runs cbcbox's own MIP-instance test suite.

luhenry added a commit that referenced this pull request Oct 15, 2026
@luhenry
luhenry marked this pull request as draft October 15, 2026 12:13
luhenry added a commit that referenced this pull request Oct 15, 2026
mip: the real compiled dependency is the separate cbcbox package
(already tracked here, PR #3260); mip itself is a pure cffi dlopen
wrapper with no native content of its own.

cyipopt: PyPI ships source-only by design across every platform;
upstream's own README recommends conda-forge instead.

pytidylib: pure ctypes wrapper that dlopen's the system libtidy,
already packaged for riscv64 on Debian/Ubuntu.

pycld3: parked as stale/unmaintained upstream (last release 2021,
wheels capped at cp39), not a fresh riscv64-specific gap.
cbcbox ships a prebuilt binary distribution of the CBC MILP solver
(COIN-OR Branch and Cut): a py3-none-manylinux*_{x86_64,aarch64} wheel
built by compiling OpenBLAS, SuiteSparse AMD, and the CoinUtils/Osi/
Clp/Cgl/Cbc stack from source and bundling the resulting binaries and
shared libraries. The architecture-specific code is cleanly gated
behind an x86_64 check (AVX2/Haswell variants only); the generic build
path already exercised on aarch64 compiles with a plain Fortran/C++
toolchain and has no riscv64-blocking assumptions.

Mirror the upstream wheel.yml's Linux ARM64 jobs (compile generic +
compile debug, then package with cibuildwheel, compilation skipped)
on manylinux_2_39_riscv64, since manylinux2014 (upstream's own tag for
x86_64/aarch64) predates riscv64 entirely. OpenBLAS's DYNAMIC_ARCH
build supports riscv64 natively (RISCV64_GENERIC/ZVL128B/ZVL256B
kernels, confirmed against the pinned v0.3.31 source), Rocky Linux
10's riscv64 AppStream repo carries gcc-gfortran, and PyPI's patchelf
package (used directly by cbcbox's own bundling logic, not auditwheel)
ships a manylinux_2_31_riscv64 wheel.

cbcbox bumps its version on master without ever pushing a git tag for
most releases (v2.931-v2.933 are tagged, 2.934/2.935 are not), so pin
the exact commit that carries version = "2.935": its timestamp sits
about 3h20m before the 2.935 wheels were uploaded to PyPI, with no
further pyproject.toml change in between.

setup.py's custom bdist_wheel only maps linux_x86_64/linux_aarch64 to
their manylinux2014 wheel tag; patch in a linux_riscv64 mapping to
manylinux_2_39_riscv64 (patches/cbcbox/2.935/), since upstream has no
riscv64 CI of its own to carry this.

Note for later review: cbcbox's own bundling logic does not copy a
LICENSE file for the vendored OpenBLAS/SuiteSparse AMD (BSD) next to
the COIN-OR (EPL-2.0) ones it does copy -- a pre-existing gap on the
already-published x86_64/aarch64 wheels, not introduced by or specific
to this riscv64 port.
package_linux_riscv64's cibuildwheel step builds and tests the wheel as a
single action, so its 61-case MILP-solve pytest suite inherits whatever is
left of the job's timeout-minutes: 300. On run 42301146079 that suite was
genuinely progressing (28/61 cases passed in ~4h52m, each taking minutes on
this fleet's riscv64 hardware, not hung) when the job hit its 5h ceiling and
GitHub Actions auto-cancelled it mid-test, losing the already-built wheel in
the process (gotcha 598).

Split testing into its own step, with the wheel uploaded first and its own
generous-but-bounded timeout, and give the job enough headroom overall for
the suite to actually finish.
_publish-wheel.yml now requires a matrix input (main's "publish:
Validate effective wheel targets"), but this PR branch forked before
that landed and cbcbox has never been merged to main, so
build-cbcbox.yml never picked up the matching one-line update every
already-published package's workflow got. The merge ref combines the
new required-input _publish-wheel.yml with this branch's old
build-cbcbox.yml, so any/every run has failed startup with "Input
matrix is required, but not provided".

Pass it the same way the mass-update commit did for every other
package's publish job.
Real test log (run 43082576749, job 130832260080) shows a genuine SIGABRT,
not just slowness: "Fatal glibc error: pthread_mutex_lock.c:94
(___pthread_mutex_lock): assertion failed: mutex->__data.__owner == 0",
with the crash's own stack trace going through
libopenblas.so!blas_memory_alloc -> pthread_mutex_lock while CBC was
mid-solve under its own -threads=3 branch-and-bound parallelism (CoinUtils/
Clp's dense-factorization update calling into OpenBLAS's dgetrs_ from one
of CBC's worker threads). This is the classic nested-parallelism hazard:
CBC already parallelizes the search itself, and OpenBLAS's own pthread
thread pool then additionally tries to parallelize the BLAS calls each
worker thread makes, so N CBC threads each spawn M OpenBLAS threads
fighting over OpenBLAS's shared memory-pool lock - evidently exposing a
real bug in OpenBLAS's pthread-backend locking on this architecture.

Re-examining the run's 5 reported failures with this in mind: the 2
"wrong objective value" assertions (mzzv11, neos-860300) and 2 of the 3
test_solve_parallel TimeoutExpired failures are downstream symptoms of
this same corruption/abort, not independent slowness or correctness bugs
in CBC's own solve logic - the previous diagnosis of "just needs a higher
time limit" undersold what's actually happening here.

OPENBLAS_NUM_THREADS=1 is this repo's existing, established fix for
exactly this class of problem (see build-statsmodels.yml, build-pyscf.yml,
build-torchft.yml, build-ligo-skymap.yml): forcing OpenBLAS's BLAS calls
to stay single-threaded lets CBC's own -threads=N remain the only level of
parallelism, removing the oversubscription/locking hazard rather than
papering over it with a longer timeout.

This branch has not been deployed

No deployments
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