Skip to content

Commit fa67477

Browse files
author
peng.li24
committed
feat: add NUMPYCPP_STD_ONLY backend; dual DEB packaging; dual CI jobs
Two interchangeable math backends, selected at cmake configure time: cmake -DNUMPYCPP_STD_ONLY=OFF (default) → bitexact backend • dlsym → npy_exp/npy_log/... + __svml_exp8/... from numpy .so • OpenBLAS ILP64 cblas_sgemm/sdot via dlsym • IEEE 754 bit-identical to numpy on all architectures • DEB: numpycpp-dev-bitexact-<ver>-Linux.deb cmake -DNUMPYCPP_STD_ONLY=ON → std backend • std::exp/log/sin/... from <cmath> • Pure C++ loops for dot/gemm (compiler auto-vectorises with -O3) • 0-2 ULP from numpy, NOT bit-exact; no external dependencies • DEB: numpycpp-dev-std-<ver>-Linux.deb All public APIs (numpy::exp, numpy::dot, numpy::einsum, ...) are identical in both backends. The split is fully encapsulated in detail/: New files: numpy/detail/math_backend.h — #ifdef selector (svml vs std) numpy/detail/std_math_backend.h — std::exp/log/sin/... same API numpy/detail/linalg_backend.h — #ifdef selector (blas vs std) numpy/detail/std_linalg_backend.h — C++ loop dot/gemm same API Modified: numpy/elementwise.h — use math_backend.h; guard avx512_loops.h numpy/linalg.h — use linalg_backend.h CMakeLists.txt — option(NUMPYCPP_STD_ONLY); conditional DEB name tests/CMakeLists.txt — option + conditional compile flags .github/workflows/ci.yml — 3 jobs: test-bitexact, test-std, package README.md — backend comparison table; compiler flag tables Compiler flag differences: bitexact: -ffp-contract=off -mavx512f -mprefer-vector-width=256 -ldl (all required) std: -O3 -march=native (no -ffp-contract=off, no -ldl needed) 900 bitexact tests still pass.
1 parent 4ee900e commit fa67477

10 files changed

Lines changed: 584 additions & 120 deletions

File tree

.github/workflows/ci.yml

Lines changed: 91 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
# Bit-exact CI: every push/PR must pass all 792 IEEE-754 tests.
1+
# CI: build and test both backends on every push/PR.
22
#
3-
# Requirements driving the setup:
4-
# - numpy must be pip-installed (its bundled OpenBLAS exposes sdot_64_ /
5-
# cblas_sgemm64_ / __svml_exp8 etc. that numpycpp resolves via dlsym)
6-
# - AVX-512 hardware is required at runtime (-mavx512f compile flag +
7-
# __attribute__((target)) guards; GitHub ubuntu-latest uses Intel Xeon
8-
# which supports AVX-512)
9-
# - pybind11, Eigen3, OpenMP needed to build the test extension module
3+
# Two jobs run in parallel:
4+
# test-bitexact — NUMPYCPP_STD_ONLY=OFF (default)
5+
# All 900 tests verify IEEE-754 bit-identical results vs numpy.
6+
# Requires: numpy .so loaded (OpenBLAS + SVML dlsym), AVX-512 capable CPU.
7+
#
8+
# test-std — NUMPYCPP_STD_ONLY=ON
9+
# Same API, pure <cmath> + C++ loops. Tests run with atol/rtol tolerance.
10+
# No external math dependencies.
11+
#
12+
# DEB packaging:
13+
# cmake -DNUMPYCPP_STD_ONLY=OFF → numpycpp-dev-bitexact-*.deb
14+
# cmake -DNUMPYCPP_STD_ONLY=ON → numpycpp-dev-std-*.deb
1015

1116
name: CI
1217

@@ -17,20 +22,15 @@ on:
1722
branches: [ master ]
1823

1924
jobs:
20-
test:
21-
name: bit-exact tests — Python ${{ matrix.python }} / ${{ matrix.compiler }}
25+
# ── Job 1: bit-exact backend ─────────────────────────────────────────────────
26+
test-bitexact:
27+
name: bitexact / Python ${{ matrix.python }}
2228
runs-on: ubuntu-latest
2329

2430
strategy:
2531
fail-fast: false
2632
matrix:
27-
include:
28-
- python: "3.10"
29-
compiler: g++
30-
- python: "3.11"
31-
compiler: g++
32-
- python: "3.12"
33-
compiler: g++
33+
python: ["3.10", "3.11", "3.12"]
3434

3535
steps:
3636
- uses: actions/checkout@v4
@@ -47,24 +47,91 @@ jobs:
4747
run: |
4848
sudo apt-get update -qq
4949
sudo apt-get install -y libeigen3-dev
50-
# libgomp1 (OpenMP runtime) and omp.h (OpenMP headers) are bundled
51-
# with GCC on Ubuntu 24.04 — no separate libgomp1-dev package exists
5250
5351
- name: Diagnose numpy symbols
5452
run: |
5553
python diagnose_numpy.py
5654
grep -q avx512f /proc/cpuinfo && echo "AVX-512: present in cpuinfo" || echo "AVX-512: NOT in cpuinfo"
5755
working-directory: tests
5856

59-
- name: Configure
60-
run: cmake -S tests -B tests/build -DCMAKE_BUILD_TYPE=Release
61-
env:
62-
CXX: ${{ matrix.compiler }}
57+
- name: Configure (bitexact)
58+
run: cmake -S tests -B tests/build -DCMAKE_BUILD_TYPE=Release -DNUMPYCPP_STD_ONLY=OFF
6359

6460
- name: Build
6561
run: cmake --build tests/build -j$(nproc)
6662

67-
- name: Run bit-exact tests
63+
- name: Run 900 bit-exact tests
6864
run: |
6965
cd tests
7066
python -m pytest test_all.py -q --tb=short --no-header
67+
68+
# ── Job 2: std / performance-first backend ───────────────────────────────────
69+
test-std:
70+
name: std / Python ${{ matrix.python }}
71+
runs-on: ubuntu-latest
72+
73+
strategy:
74+
fail-fast: false
75+
matrix:
76+
python: ["3.10", "3.11", "3.12"]
77+
78+
steps:
79+
- uses: actions/checkout@v4
80+
81+
- name: Set up Python ${{ matrix.python }}
82+
uses: actions/setup-python@v5
83+
with:
84+
python-version: ${{ matrix.python }}
85+
86+
- name: Install Python dependencies
87+
run: pip install "numpy>=1.23,<2.0" pybind11 pytest cmake
88+
89+
- name: Install system dependencies
90+
run: |
91+
sudo apt-get update -qq
92+
sudo apt-get install -y libeigen3-dev
93+
94+
- name: Configure (std)
95+
run: cmake -S tests -B tests/build_std -DCMAKE_BUILD_TYPE=Release -DNUMPYCPP_STD_ONLY=ON
96+
97+
- name: Build
98+
run: cmake --build tests/build_std -j$(nproc)
99+
100+
- name: Run API-compatibility tests (std mode)
101+
run: |
102+
cd tests
103+
# Use the std .so (placed alongside test_all.py by CMake)
104+
python -m pytest test_all.py -q --tb=short --no-header \
105+
-k "not test_nan_passthrough and not test_domain and not test_svml"
106+
# Note: transcendental domain-error and NaN bit-pattern tests are
107+
# skipped in std mode (std::exp/sin are not bit-identical to npy_*).
108+
# All structural, reduction, manipulation, and linalg tests still run.
109+
110+
# ── Job 3: package both DEBs ─────────────────────────────────────────────────
111+
package:
112+
name: Package DEBs
113+
runs-on: ubuntu-latest
114+
needs: [test-bitexact, test-std]
115+
116+
steps:
117+
- uses: actions/checkout@v4
118+
119+
- name: Build bitexact DEB
120+
run: |
121+
cmake -S . -B build_bitexact -DNUMPYCPP_STD_ONLY=OFF
122+
cmake --build build_bitexact --target deb
123+
ls build_bitexact/*.deb
124+
125+
- name: Build std DEB
126+
run: |
127+
cmake -S . -B build_std_deb -DNUMPYCPP_STD_ONLY=ON
128+
cmake --build build_std_deb --target deb
129+
ls build_std_deb/*.deb
130+
131+
- name: Upload DEBs as artifacts
132+
uses: actions/upload-artifact@v4
133+
with:
134+
name: numpycpp-debs
135+
path: |
136+
build_bitexact/*.deb
137+
build_std_deb/*.deb

CMakeLists.txt

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ set(CMAKE_CXX_STANDARD 17)
66
set(CMAKE_CXX_STANDARD_REQUIRED ON)
77
set(CMAKE_CXX_EXTENSIONS OFF)
88

9+
# ---- Math backend selection --------------------------------------------------
10+
# NUMPYCPP_STD_ONLY=OFF (default): bit-exact backend
11+
# • dlsym resolves npy_exp/npy_log/... and __svml_exp8/... from numpy's .so
12+
# • OpenBLAS ILP64 for dot/matmul — same kernels as numpy
13+
# • Requires: -ldl at link time
14+
# • Results: IEEE 754 bit-identical to numpy on all architectures
15+
#
16+
# NUMPYCPP_STD_ONLY=ON: pure C++ standard-library backend
17+
# • std::exp, std::log, std::sin, ... from <cmath>
18+
# • Naive C++ loops for dot/gemm — compiler auto-vectorises with -O3
19+
# • No external dependencies (no dlopen, no numpy .so)
20+
# • Results: 0–2 ULP from numpy, NOT bit-exact
21+
option(NUMPYCPP_STD_ONLY
22+
"Use only C++ standard library (no SVML/BLAS/dlopen). Performance-first, not bit-exact."
23+
OFF)
24+
925
# ---- INTERFACE target for find_package consumers ----------------------------
1026
add_library(numpycpp INTERFACE)
1127
target_include_directories(numpycpp INTERFACE
@@ -14,6 +30,16 @@ target_include_directories(numpycpp INTERFACE
1430
)
1531
target_compile_features(numpycpp INTERFACE cxx_std_17)
1632

33+
if(NUMPYCPP_STD_ONLY)
34+
# Propagate the flag so every consumer gets the right backend automatically
35+
target_compile_definitions(numpycpp INTERFACE NUMPYCPP_STD_ONLY)
36+
message(STATUS "numpycpp backend: std (pure <cmath> + C++ loops — no bit-exact guarantee)")
37+
else()
38+
# dlsym required for SVML bridge + BLAS bridge
39+
target_link_libraries(numpycpp INTERFACE dl)
40+
message(STATUS "numpycpp backend: bitexact (SVML + OpenBLAS ILP64 via dlsym)")
41+
endif()
42+
1743
# ---- Install — header-only C++ library --------------------------------------
1844
install(DIRECTORY numpy
1945
DESTINATION include/numpycpp
@@ -53,17 +79,29 @@ install(DIRECTORY example/
5379
)
5480

5581
# ---- CPack DEB packaging ----------------------------------------------------
56-
set(CPACK_PACKAGE_NAME "numpycpp-dev")
82+
# Package name and description differ by backend:
83+
# bitexact: numpycpp-dev-bitexact-<ver>-Linux.deb
84+
# std: numpycpp-dev-std-<ver>-Linux.deb
85+
if(NUMPYCPP_STD_ONLY)
86+
set(_NUMPYCPP_DEB_FLAVOR "std")
87+
set(_NUMPYCPP_DEB_SUMMARY "C++ numpy-API header-only library — pure std backend (perf-first)")
88+
else()
89+
set(_NUMPYCPP_DEB_FLAVOR "bitexact")
90+
set(_NUMPYCPP_DEB_SUMMARY "C++ numpy-API header-only library — bit-exact backend (SVML + BLAS)")
91+
endif()
92+
93+
set(CPACK_PACKAGE_NAME "numpycpp-dev-${_NUMPYCPP_DEB_FLAVOR}")
5794
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
5895
set(CPACK_PACKAGE_VENDOR "miaobyte")
5996
set(CPACK_PACKAGE_CONTACT "miaobyte")
60-
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "C++ pixel-level alignment of Python numpy — header-only library")
97+
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${_NUMPYCPP_DEB_SUMMARY}")
6198
set(CPACK_GENERATOR "DEB")
6299
set(CPACK_DEB_COMPONENT_INSTALL OFF)
63100
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "miaobyte")
64101
set(CPACK_DEBIAN_PACKAGE_SECTION "libdevel")
65102
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
66-
set(CPACK_DEBIAN_FILE_NAME "numpycpp-dev-${CPACK_PACKAGE_VERSION}-Linux.deb")
103+
set(CPACK_DEBIAN_FILE_NAME
104+
"numpycpp-dev-${_NUMPYCPP_DEB_FLAVOR}-${CPACK_PACKAGE_VERSION}-Linux.deb")
67105
set(CPACK_PACKAGING_INSTALL_PREFIX "/usr")
68106
# Wipe the old include tree on install/upgrade so stale headers cannot accumulate.
69107
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/cmake/preinst")
@@ -73,7 +111,7 @@ include(CPack)
73111
# Convenience target
74112
add_custom_target(deb
75113
COMMAND cpack -G DEB
76-
COMMENT "Packaging numpycpp-dev-${CPACK_PACKAGE_VERSION}-Linux.deb"
114+
COMMENT "Packaging numpycpp-dev-${_NUMPYCPP_DEB_FLAVOR}-${CPACK_PACKAGE_VERSION}-Linux.deb"
77115
)
78116

79117
message(STATUS "numpycpp v${PROJECT_VERSION} (header-only C++ library)")

0 commit comments

Comments
 (0)