Development#
For build requirements and platform support, see installation.
Local setup#
git clone https://github.com/RosettaCommons/tmol.git
cd tmol
python -m pip install "torch>=2.14" "scikit-build-core>=0.10" "cmake>=3.24,<4" "pybind11>=2.12" ninja packaging
python -m pip install --no-build-isolation -e ".[dev]"
Build with PyTorch 2.14+ headers. Production extensions target the stable C ABI in PyTorch 2.11+. Test-only and JIT extensions use the local PyTorch C++ API and must be rebuilt when changing that runtime.
TMol locates Torch libraries/headers and pybind11 from this Python interpreter. It does not use TorchConfig.cmake or the unstable Torch C++ ABI for production.
Requirements:
Python 3.11 or newer.
PyTorch 2.14+ build headers; PyTorch 2.11+ runtime.
A C++17 compiler (C++20 for test/JIT extensions with recent PyTorch).
CMake 3.24 or newer.
CUDA toolkit with
nvccfor CUDA builds.
CPU builds are the default. To set this explicitly:
python -m pip install --no-build-isolation -e . -Ccmake.define.TMOL_ENABLE_CUDA=OFF
Build extensions#
TMol builds extensions with CMake through scikit-build-core.
# Production extensions
python -m pip install --no-build-isolation -e .
# Include test-only C++/CUDA extensions
python -m pip install --no-build-isolation -e ".[dev]" \
-Ccmake.define.TMOL_BUILD_TESTS=ON
# CUDA development build, including GPU test helpers
python -m pip install --no-build-isolation -e ".[dev]" -Ccmake.define.TMOL_ENABLE_CUDA=ON \
-Ccmake.define.TMOL_BUILD_TESTS=ON -Ccmake.define.CMAKE_CUDA_ARCHITECTURES="80;90"
# Separate distribution containing only CUDA operators
python -m pip wheel --no-build-isolation --no-deps ./packages/cu13
# Control parallelism
MAX_JOBS=4 python -m pip install --no-build-isolation -e . -Ccmake.define.TMOL_NVCC_THREADS=2
Build settings:
Variable |
Default |
Meaning |
|---|---|---|
|
|
|
|
|
Build test-only extensions. |
|
|
Threads per |
|
|
Enable CUDA in a development build; CUDA operator packages enable it automatically. |
|
build-time Torch include directory |
Optional PyTorch 2.14+ headers when testing production modules against an older runtime; test helpers retain that runtime’s headers. |
|
auto |
Maximum parallel build jobs. |
Extension loading#
TMol can load kernels two ways:
AOT: pre-built shared libraries bundled in the installed wheel.
JIT: source files compiled on first use through
torch.utils.cpp_extension.
Environment variables:
Variable |
Effect |
|---|---|
|
Force JIT mode. |
|
Try AOT first, then JIT if AOT is unavailable. |
JIT mode requires a source checkout and build tools. Use it while editing
kernels; use AOT mode for installed packages.
On Linux, the JIT loader detects PyTorch’s OpenMP CPU backend and propagates
the corresponding compiler and linker flags, so at::parallel_for has the
same thread behavior as the CMake-built extension. OMP_NUM_THREADS and
torch.set_num_threads() therefore apply consistently in both modes.
Tests#
# All tests
pytest tmol/tests/ -v
# Specific file
pytest tmol/tests/score/test_score_function.py -v
# Skip CUDA-parametrized cases
pytest tmol/tests/ -v -k "not cuda"
# Coverage
pytest tmol/tests/ --cov=./tmol --junitxml=results.xml
# Benchmarks
pytest --benchmark-enable --benchmark-only --benchmark-max-time=.1
Ligand charge generation is intentionally strict. Partial charges come from the SMILES to OpenBabel MMFF94 mol2 step and are applied by atom index. There is no RDKit/Gasteiger fallback and no charge-mode switch.
Containers#
Docker:
docker build -t tmol-dev -f containers/docker/tmol-dev.Dockerfile .
docker run --gpus all -it -v "$(pwd):/tmol_host" -w /tmol_host tmol-dev bash
python -m pip install --no-build-isolation -e .
Apptainer:
apptainer build tmol-dev.sif containers/apptainer/tmol-dev.def
apptainer run --nv --bind "$(pwd):/tmol_host" tmol-dev.sif
CI#
GitHub Actions runs linting and the Linux x86-64, Linux aarch64, and Apple Silicon
CPU suites automatically on pushes to main and pull requests. Documentation
also builds on hosted CPU runners. Routine CI has six CPU/check jobs plus the
docs build and same-repository preview deployment. Coverage uploads are a step
in the Linux x86-64 job; a coverage-service outage does not fail tested code.
Closing a pull request does not start an empty CI run.
The Validate release wheels workflow is manual. It builds all 28 wheels once per Python/platform/backend, then tests the same artifacts against the supported Torch runtimes. The release workflow runs the complete matrix again on an immutable version tag before publishing. Publication-condition regressions run as fast unit tests in the packaging job; there is no simulated release workflow. The separate CUDA ARM64 dependency-repair probe is manual; real ARM64 wheel installation checks remain in the full candidate/release matrix.
Maintainers with write, maintain, or admin access can dispatch GPU CI from
main, selecting a pull request number or a branch, tag, or commit. It resolves
an immutable commit, checks the initiating and rerunning users’ current access,
and reports a separate GPU CI status on that commit. Rerun all jobs when
retrying so authorization runs again. CUDA tests use the existing Slurm and
Apptainer configuration; wheel and release validation use the same access gate.
Enable the GPU runner#
An organization owner (or delegated runner administrator) must configure the runner group before registering a runner for this public repository:
Open RosettaCommons → Settings → Actions → Runner groups and create
tmol-gpu. Under Repository access, select onlyRosettaCommons/tmoland allow public repositories.Under Workflow access, select Selected workflows and allow exactly:
RosettaCommons/tmol/.github/workflows/gpu-ci.yml@refs/heads/main RosettaCommons/tmol/.github/workflows/_test_gpu_wheel.yml@refs/heads/main
If selected-workflow restrictions are unavailable, keep the runner offline; repository access alone does not prevent a PR from proposing another job.
Under the organization’s Actions → Runners, choose New self-hosted runner → Linux x64. Install its verified archive in a separate directory on the existing Slurm/Apptainer CI host. Never reuse another registration’s credentials or work directory. Register using the short-lived token shown there (the command prompts for it):
./config.sh --url https://github.com/RosettaCommons \ --runnergroup tmol-gpu --name tmol-gpu-slurm --labels gpu --work _work
Start the runner with
./run.shin a persistent session, or have the host administrator install/start its service withsvc.sh. The account must have Slurm access and read access to the Apptainer image configured ingpu-ci.ymland_test_gpu_wheel.yml. Confirm the runner is Idle intmol-gpuand that the selected-workflow restriction is saved.In the repository’s Actions → GPU CI → Run workflow, select
mainand enter a reviewed PR number or ref. Leave both blank to test thismaincommit. A hosted job checks maintainer access before any GPU job starts.
The group restriction is the enforcement boundary; workflow checks alone cannot stop a pull request from adding another self-hosted job. See GitHub’s runner-group documentation.
Native interface#
Operators register directly with PyTorch’s stable dispatcher. Python autograd
owns differentiation and saved-variable version checks; saved tensors cross the
boundary in batches. Native allocations, dispatch, streams, and generator access
use the stable C API. PyTorch 2.14+ provides direct Python tensor interop; older
runtimes exchange real tensor handles through a private stable operator. Neither
path copies tensor storage. Whole-pose scoring groups eligible precomputed
coordinate derivatives into one autograd context and CUDA reduction. This path
checks that static tensors are frozen before skipping per-term gradient queries;
custom Python wrappers, hooks, trainable parameters, and weighted/parallel execution
retain their ordinary differentiation paths. The engine schedules remaining
term graphs directly, without a nested backward call. Saved derivatives stay
under PyTorch’s saved-variable ownership; CUDA reduction receives the entire
tensor list through one native dispatcher call on every supported runtime.
New native operations must preserve this boundary.
scripts/check_native_abi.py catches accidental C++ ABI imports and unversioned
Linux C++ runtime imports. Production uses C++17 to retain the manylinux floor.
CUDA packing draws phase seeds through Torch’s public dispatcher rather than
accessing CUDAGeneratorImpl; fixed seeds remain reproducible, but trajectories
can differ from releases before this refactor.
Releasing#
Versioned wheel and sdist publication happens from v* tags. The tag version
must match [project].version in pyproject.toml.
scripts/release_matrix.py defines 28 wheels: 12 CPU/API wheels and 16 CUDA
operator wheels across Python 3.11–3.14 and supported platforms. CUDA builds
use a toolkit floor of 12.8 or 13.0, independent of the build-time Torch minor.
All three project versions and the extras/dependencies must agree.
CI audits each wheel for unstable PyTorch C++ imports and tests the same
binary against PyTorch 2.11 and 2.14 where CUDA availability permits. It also
checks native scoring/gradients, actual pip resolution through staging indexes,
source metadata, wheel contents, and RECORD hashes before publication. All
three distributions go to PyPI without renaming wheels or adding local versions.
The existing PYPI_API_TOKEN repository secret must cover tmol-ops-cu12 and
tmol-ops-cu13 as well as tmol. For the first upload, an account-scoped token
can create both new projects; a token scoped only to tmol cannot. The workflow
uploads both CUDA distributions before the base release, so missing project
access stops publication before new base extras become visible. No separate
package index or user-side build is needed. Publication remains gated on the
complete wheel matrix and CPU/GPU installation checks.
Code style#
TMol uses Black for Python formatting, Flake8 for linting, and clang-format for C++/CUDA formatting.
black --check .
black .
flake8
Run pre-commit before opening a PR:
pre-commit run --all-files