Skip to content

Commit 134defc

Browse files
tohtskyTomoki Ohtsuki
andauthored
Use Nanobind && Numpy >=2 (#112)
* test using cibuildwheel manually * remove invalid env * os -> runs-on * resurrect original * Omit windows-arm for now (eigen related error) * downaload-artifact v4 * use trusted publisher of pypi * working on nanobind * fixing nanobind * stub shell scripts * build system * debug statement * use pip nanobind instead * run-test.yml * pydantic as dependency * pre-commit * debugging build * fix sdist * setuptools * pip install build * pyproject * remove classfiers * scm configured * remove version file * pre-commit * remove local version * docstring * environment-pass option * update pandas requirements for numpy >= 2 * add cxxflags * .gitignore * branches: main --------- Co-authored-by: Tomoki Ohtsuki <tomoki_ohtsuki@libinc.co.jp>
1 parent 4a46669 commit 134defc

28 files changed

Lines changed: 527 additions & 732 deletions

.github/workflows/pre-commit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ jobs:
88
env:
99
SKIP: no-commit-to-branch
1010
steps:
11-
- uses: actions/checkout@v3
11+
- uses: actions/checkout@v4
1212
- uses: actions/setup-python@v3
1313
- uses: pre-commit/action@v3.0.0

.github/workflows/run-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ jobs:
88
steps:
99
- uses: actions/checkout@v4
1010
with:
11+
submodule: recursive
1112
fetch-depth: 0
1213
- name: Setup Python
1314
uses: actions/setup-python@v5
@@ -17,7 +18,6 @@ jobs:
1718
run: |
1819
pip install --upgrade pip setuptools wheel
1920
sudo apt-get install lcov
20-
pip install pybind11 numpy
2121
FLAGS="-fprofile-arcs -ftest-coverage"
2222
export CFLAGS="$FLAGS"
2323
export CXXFLAGS="$FLAGS"

.github/workflows/wheels.yml

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,19 @@ jobs:
1313
name: Build source distribution
1414
runs-on: ubuntu-latest
1515
steps:
16-
- uses: actions/checkout@v3
17-
with:
18-
fetch-depth: 0
16+
- uses: actions/checkout@v4
1917
- uses: actions/setup-python@v3
2018
name: Install Python
2119
with:
2220
python-version: '3.13'
2321
- name: Build sdist
24-
run: pip install pybind11 setuptools_scm && python setup.py sdist
22+
run: pip install build && python -m build --sdist
2523
- uses: actions/upload-artifact@v4
2624
with:
2725
path: dist/*.tar.gz
2826
build_wheels:
2927
name: Build wheels on ${{ matrix.os }}
30-
runs-on: ${{ matrix.os }}
31-
env:
32-
CIBW_ENVIRONMENT: "${{ matrix.cibw.env || '' }}"
28+
runs-on: ${{ matrix.runs-on }}
3329
strategy:
3430
matrix:
3531
include:
@@ -41,56 +37,55 @@ jobs:
4137
runs-on: ubuntu-24.04-arm
4238
cibw:
4339
cflags: ''
44-
# - os: windows-intel
45-
# runs-on: windows-latest
46-
# cibw:
47-
# cflags: ''
48-
# - os: windows-arm
49-
# runs-on: windows-11-arm
50-
# cibw:
51-
# cflags: ''
52-
# - os: macos-intel
53-
# runs-on: macos-13
54-
# cibw:
55-
# cflags: ''
56-
# - os: macos-arm
57-
# runs-on: macos-latest
58-
# cibw:
59-
# cflags: ''
40+
- os: windows-intel
41+
runs-on: windows-latest
42+
cibw:
43+
cflags: ''
44+
- os: macos-intel
45+
runs-on: macos-13
46+
cibw:
47+
cflags: ''
48+
- os: macos-arm
49+
runs-on: macos-latest
50+
cibw:
51+
cflags: ''
6052
steps:
6153
- uses: actions/checkout@v4
54+
with:
55+
submodule: recursive
56+
fetch-depth: 0
6257
- name: Build wheels
6358
uses: pypa/cibuildwheel@v3.0.1
6459
env:
6560
CIBW_PLATFORM: ${{ matrix.platform || 'auto' }}
6661
CFLAGS: ${{ matrix.cibw.cflags }}
62+
CXXFLAGS: ${{ matrix.cibw.cflags }}
6763
- uses: actions/upload-artifact@v4
6864
with:
6965
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
7066
path: ./wheelhouse/*.whl
7167
upload_pypi:
7268
needs: [build_wheels, build_sdist]
7369
runs-on: ubuntu-latest
70+
permissions:
71+
id-token: write
7472
steps:
75-
- uses: actions/download-artifact@v2
73+
- uses: actions/download-artifact@v4
7674
with:
77-
name: artifact
75+
pattern: cibw-*
7876
path: dist
77+
merge-multiple: true
7978
- name: Publish package to TestPyPI
80-
uses: pypa/gh-action-pypi-publish@master
79+
uses: pypa/gh-action-pypi-publish@release/v1
8180
with:
82-
user: __token__
83-
password: ${{ secrets.TEST_PYPI_APITOKEN }}
8481
packages_dir: dist/
8582
repository_url: https://test.pypi.org/legacy/
8683
verbose: true
8784
skip_existing: true
8885
- name: Publish package to PyPI
8986
if: github.event_name == 'release'
90-
uses: pypa/gh-action-pypi-publish@master
87+
uses: pypa/gh-action-pypi-publish@release/v1
9188
with:
92-
user: __token__
93-
password: ${{ secrets.PYPI_APITOKEN }}
9489
packages_dir: dist/
9590
verbose: true
9691
skip_existing: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ docs/build/
2828
docs/source/api_reference/
2929
.cache/
3030
.coverage
31+
src/irspack/_version.py

.gitmodules

Whitespace-only changes.

CMakeLists.txt

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,46 @@
1-
cmake_minimum_required(VERSION 3.0.0)
2-
project(rs_evaluation VERSION 0.1.0)
3-
set(CMAKE_BUILD_TYPE DEBUG)
4-
set(CMAKE_CXX_FLAGS "-std=c++11 -march=native -fPIC -O0 -g")
1+
cmake_minimum_required(VERSION 3.15...3.27)
2+
project(irspack VERSION 0.1.0)
3+
4+
if (CMAKE_VERSION VERSION_LESS 3.18)
5+
set(DEV_MODULE Development)
6+
else()
7+
set(DEV_MODULE Development.Module)
8+
endif()
9+
10+
11+
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
12+
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
13+
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
14+
endif()
15+
16+
if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/eigen-3.4.0")
17+
file(DOWNLOAD https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.zip "${CMAKE_CURRENT_BINARY_DIR}/eigen-3.4.0.zip")
18+
file(ARCHIVE_EXTRACT INPUT "${CMAKE_CURRENT_BINARY_DIR}/eigen-3.4.0.zip")
19+
endif()
520

621
find_package(Threads REQUIRED)
722

8-
include_directories(eigen-3.3.7 cpp_source)
9-
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
1023

24+
include_directories("${CMAKE_BINARY_DIR}/eigen-3.4.0" cpp_source)
25+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
1126
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
1227
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
1328
include(CPack)
1429

15-
add_subdirectory(pybind11)
16-
pybind11_add_module(_ials cpp_source/als/wrapper.cpp)
17-
#pybind11_add_module(irspack.recommenders._knn cpp_source/knn/wrapper.cpp)
18-
#pybind11_add_module(_util_cpp cpp_source/util.cpp)
19-
#pybind11_add_module(irspack._evapuator cpp_source/evaluator.cpp)
20-
#pybind11_add_module(irspack._rwr cpp_source/rws.cpp)
30+
31+
# Detect the installed nanobind package and import it into CMake
32+
find_package(Python 3.8 COMPONENTS Interpreter ${DEV_MODULE} REQUIRED)
33+
execute_process(
34+
COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
35+
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT)
36+
find_package(nanobind CONFIG REQUIRED)
37+
38+
nanobind_add_module(_ials_core cpp_source/als/wrapper.cpp)
39+
nanobind_add_module(_knn cpp_source/knn/wrapper.cpp)
40+
nanobind_add_module(_util_cpp cpp_source/util.cpp)
41+
nanobind_add_module(_core_evaluator cpp_source/evaluator.cpp)
42+
43+
install(TARGETS _ials_core LIBRARY DESTINATION irspack/recommenders)
44+
install(TARGETS _core_evaluator LIBRARY DESTINATION irspack/evaluation)
45+
install(TARGETS _util_cpp LIBRARY DESTINATION irspack/utils)
46+
install(TARGETS _knn LIBRARY DESTINATION irspack/recommenders)

cpp_source/als/definitions.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#include <Eigen/Core>
22
#include <Eigen/Sparse>
3-
#include <vector>
43

54
namespace irspack {
65
namespace ials {

cpp_source/als/wrapper.cpp

Lines changed: 63 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,57 @@
11
#include "IALSLearningConfig.hpp"
22
#include "IALSTrainer.hpp"
3-
#include "pybind11/cast.h"
43
#include <Eigen/Sparse>
54
#include <cstddef>
6-
#include <pybind11/eigen.h>
7-
#include <pybind11/pybind11.h>
8-
#include <pybind11/stl.h>
9-
#include <pybind11/stl_bind.h>
10-
#include <sstream>
11-
#include <stdexcept>
12-
#include <vector>
5+
#include <nanobind/nanobind.h>
6+
#include <nanobind/nb_defs.h>
7+
#include <nanobind/eigen/dense.h>
8+
#include <nanobind/eigen/sparse.h>
9+
#include <nanobind/stl/tuple.h>
10+
#include <nanobind/stl/string.h>
11+
#include <tuple>
1312

14-
namespace py = pybind11;
1513
using namespace irspack::ials;
16-
using std::vector;
14+
using namespace nanobind;
1715

18-
PYBIND11_MODULE(_ials, m) {
16+
NB_MODULE(_ials_core, m) {
1917
std::stringstream doc_stream;
2018
doc_stream << "irspack's core module for \"IALSRecommender\"." << std::endl
2119
<< "Built to use" << std::endl
2220
<< "\t" << Eigen::SimdInstructionSetsInUse();
2321

2422
m.doc() = doc_stream.str();
2523

26-
py::enum_<LossType>(m, "LossType")
24+
nanobind::enum_<LossType>(m, "LossType")
2725
.value("ORIGINAL", LossType::ORIGINAL)
2826
.value("IALSPP", LossType::IALSPP)
2927
.export_values();
3028

31-
py::enum_<SolverType>(m, "SolverType")
29+
nanobind::enum_<SolverType>(m, "SolverType")
3230
.value("CHOLESKY", SolverType::Cholesky)
3331
.value("CG", SolverType::CG)
3432
.value("IALSPP", SolverType::IALSPP)
3533
.export_values();
3634

3735
auto model_config =
38-
py::class_<IALSModelConfig>(m, "IALSModelConfig")
39-
.def(py::init<size_t, Real, Real, Real, Real, int, LossType>())
40-
.def(py::pickle(
41-
[](const IALSModelConfig &config) {
42-
return py::make_tuple(config.K, config.alpha0, config.reg,
43-
config.nu, config.init_stdev,
44-
config.random_seed, config.loss_type);
45-
},
46-
[](py::tuple t) {
47-
if (t.size() != 7)
48-
throw std::runtime_error("invalid state");
49-
50-
size_t K = t[0].cast<size_t>();
51-
Real alpha0 = t[1].cast<Real>();
52-
Real reg = t[2].cast<Real>();
53-
Real nu = t[3].cast<Real>();
54-
Real init_stdev = t[4].cast<Real>();
55-
int random_seed = t[5].cast<int>();
56-
LossType loss_type = t[6].cast<LossType>();
57-
return IALSModelConfig(K, alpha0, reg, nu, init_stdev,
58-
random_seed, loss_type);
59-
}));
60-
py::class_<IALSModelConfig::Builder>(m, "IALSModelConfigBuilder")
61-
.def(py::init<>())
36+
nanobind::class_<IALSModelConfig>(m, "IALSModelConfig")
37+
.def(nanobind::init<size_t, Real, Real, Real, Real, int, LossType>())
38+
.def("__getstate__",
39+
[](const IALSModelConfig &config) {
40+
return nanobind::make_tuple(
41+
config.K, config.alpha0, config.reg, config.nu,
42+
config.init_stdev, config.random_seed, config.loss_type);
43+
})
44+
.def("__setstate__",
45+
[](IALSModelConfig &ials_model_config,
46+
const std::tuple<size_t, Real, Real, Real, Real, int,
47+
LossType> &state) {
48+
new (&ials_model_config) IALSModelConfig(
49+
std::get<0>(state), std::get<1>(state), std::get<2>(state),
50+
std::get<3>(state), std::get<4>(state), std::get<5>(state),
51+
std::get<6>(state));
52+
});
53+
nanobind::class_<IALSModelConfig::Builder>(m, "IALSModelConfigBuilder")
54+
.def(nanobind::init<>())
6255
.def("build", &IALSModelConfig::Builder::build)
6356
.def("set_K", &IALSModelConfig::Builder::set_K)
6457
.def("set_alpha0", &IALSModelConfig::Builder::set_alpha0)
@@ -69,30 +62,28 @@ PYBIND11_MODULE(_ials, m) {
6962
.def("set_loss_type", &IALSModelConfig::Builder::set_loss_type);
7063

7164
auto solver_config =
72-
py::class_<SolverConfig>(m, "IALSSolverConfig")
73-
.def(py::init<size_t, SolverType, size_t, size_t, size_t>())
74-
.def(py::pickle(
75-
[](const SolverConfig &config) {
76-
return py::make_tuple(
65+
nanobind::class_<SolverConfig>(m, "IALSSolverConfig")
66+
.def(nanobind::init<size_t, SolverType, size_t, size_t, size_t>())
67+
.def(
68+
"__getstate__", [](const SolverConfig &config) {
69+
return std::make_tuple(
7770
config.n_threads, config.solver_type, config.max_cg_steps,
7871
config.ialspp_subspace_dimension, config.ialspp_iteration);
79-
},
80-
[](py::tuple t) {
81-
if (t.size() != 5)
82-
throw std::runtime_error("invalid state");
83-
84-
size_t n_threads = t[0].cast<size_t>();
85-
SolverType solver_type = t[1].cast<SolverType>();
86-
size_t max_cg_steps = t[2].cast<size_t>();
87-
size_t ialspp_subspace_dimension = t[3].cast<size_t>();
88-
size_t ialspp_iteration = t[4].cast<size_t>();
89-
return SolverConfig(n_threads, solver_type, max_cg_steps,
90-
ialspp_subspace_dimension,
91-
ialspp_iteration);
92-
}));
72+
}
73+
)
74+
.def(
75+
"__setstate__", [](SolverConfig &config, const std::tuple<size_t, SolverType, size_t, size_t, size_t> &state) {
76+
new (&config) SolverConfig(
77+
std::get<0>(state),
78+
std::get<1>(state),
79+
std::get<2>(state),
80+
std::get<3>(state),
81+
std::get<4>(state));
82+
}
83+
);
9384

94-
py::class_<SolverConfig::Builder>(m, "IALSSolverConfigBuilder")
95-
.def(py::init<>())
85+
nanobind::class_<SolverConfig::Builder>(m, "IALSSolverConfigBuilder")
86+
.def(nanobind::init<>())
9687
.def("build", &SolverConfig::Builder::build)
9788
.def("set_n_threads", &SolverConfig::Builder::set_n_threads)
9889
.def("set_solver_type", &SolverConfig::Builder::set_solver_type)
@@ -102,25 +93,23 @@ PYBIND11_MODULE(_ials, m) {
10293
.def("set_ialspp_iteration",
10394
&SolverConfig::Builder::set_ialspp_iteration);
10495

105-
py::class_<IALSTrainer>(m, "IALSTrainer")
106-
.def(py::init<IALSModelConfig, const SparseMatrix &>())
96+
nanobind::class_<IALSTrainer>(m, "IALSTrainer")
97+
.def(nanobind::init<IALSModelConfig, const SparseMatrix &>())
10798
.def("step", &IALSTrainer::step)
10899
.def("user_scores", &IALSTrainer::user_scores)
109100
.def("transform_user", &IALSTrainer::transform_user)
110101
.def("transform_item", &IALSTrainer::transform_item)
111102
.def("compute_loss", &IALSTrainer::compute_loss)
112-
.def_readwrite("user", &IALSTrainer::user)
113-
.def_readwrite("item", &IALSTrainer::item)
114-
.def(py::pickle(
115-
[](const IALSTrainer &trainer) {
116-
return py::make_tuple(trainer.config_, trainer.user, trainer.item);
117-
},
118-
[](py::tuple t) {
119-
if (t.size() != 3)
120-
throw std::runtime_error("Invalid state!");
121-
IALSTrainer trainer(t[0].cast<IALSModelConfig>(),
122-
t[1].cast<DenseMatrix>(),
123-
t[2].cast<DenseMatrix>());
124-
return trainer;
125-
}));
103+
.def_rw("user", &IALSTrainer::user)
104+
.def_rw("item", &IALSTrainer::item)
105+
.def("__getstate__", [](const IALSTrainer & trainer) {
106+
return std::make_tuple(trainer.config_, trainer.user, trainer.item);
107+
})
108+
.def("__setstate__", [](IALSTrainer & trainer, const std::tuple<IALSModelConfig, DenseMatrix, DenseMatrix> & state) {
109+
new (&trainer) IALSTrainer(
110+
std::get<0>(state), std::get<1>(state),
111+
std::get<2>(state)
112+
113+
);
114+
});
126115
}

0 commit comments

Comments
 (0)