Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 191 additions & 55 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,57 +1,193 @@
# syntax=docker/dockerfile:1
FROM python:3.10-bookworm

SHELL ["/bin/bash", "-c"]
WORKDIR /src/temp

#Install Featomic
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > installRust.sh \
&& chmod 700 installRust.sh \
&& ./installRust.sh -y \
&& source $HOME/.cargo/env \
&& pip install git+https://github.com/metatensor/featomic.git

#Install OpenMP
RUN wget https://download.open-mpi.org/release/open-mpi/v5.0/openmpi-5.0.2.tar.bz2 \
&& tar -xf openmpi-5.0.2.tar.bz2 \
&& cd openmpi-5.0.2 \
&& ./configure --prefix=/usr/local/openmpi5/lib \
&& make all install \
&& cd .. \
&& rm -R openmpi-5.0.2

ENV PATH=/usr/local/openmpi5/lib/bin:$PATH
ENV LD_LIBRARY_PATH=/usr/local/openmpi5/lib
ENV HDF5_DIR=/usr/local/hdf5

#Install HDF5
RUN wget https://hdf-wordpress-1.s3.amazonaws.com/wp-content/uploads/manual/HDF5/HDF5_1_14_3/src/hdf5-1.14.3.tar.gz \
&& tar -xf hdf5-1.14.3.tar.gz \
&& cd hdf5-1.14.3 \
&& HDF5_MPI="ON" CC=mpicc ./configure --enable-shared --enable-parallel --prefix=/usr/local/hdf5 \
&& HDF5_DIR=/usr/local/hdf5 make \
&& HDF5_DIR=/usr/local/hdf5 make install \
&& cd .. \
&& rm -R hdf5-1.14.3

#Install h5py
RUN HDF5_DIR=/usr/local/hdf5 HDF5_MPI="ON" CC=mpicc pip install --no-cache-dir --no-binary=h5py h5py

RUN apt-get update && apt-get install -y \
ninja-build \
gfortran \
&& rm -rf /var/lib/apt/lists/*

RUN pip install meson cython \
&& pip install --prefer-binary pyscf

#Install SALTED
COPY . /src/temp/SALTED-master
RUN cd /src/temp/SALTED-master \
&& make \
&& pip install .

RUN rm -R /src/temp
# syntax=docker/dockerfile:1.7

ARG PYTHON_IMAGE=python:3.10-slim-bookworm
ARG OPENMPI_VERSION=4.1.8
ARG HDF5_VERSION=1.14.3


# -----------------------------------------------------------------------------
# Build MPI and parallel HDF5
# -----------------------------------------------------------------------------
FROM ${PYTHON_IMAGE} AS native-builder

ARG OPENMPI_VERSION
ARG HDF5_VERSION

ENV DEBIAN_FRONTEND=noninteractive

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
apt-get update && apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
curl \
gfortran \
libevent-dev \
libhwloc-dev \
libmunge-dev \
libpmi2-0-dev \
zlib1g-dev

WORKDIR /tmp/build

# Open MPI
#
# Use the PMIx version bundled with Open MPI instead of independently
# combining Open MPI 4.1 with PMIx 6.
RUN curl -fsSL \
"https://download.open-mpi.org/release/open-mpi/v4.1/openmpi-${OPENMPI_VERSION}.tar.gz" \
| tar -xz \
&& cd "openmpi-${OPENMPI_VERSION}" \
&& ./configure \
--prefix=/opt/mpi \
--enable-shared \
--disable-static \
--disable-debug \
--enable-builtin-atomics \
--disable-mpi-fortran \
--disable-oshmem \
--with-slurm \
--with-pmix=internal \
--with-hwloc=/usr \
--with-libevent=/usr \
--with-zlib=/usr \
--without-psm \
--without-psm2 \
&& make -j"$(nproc)" \
&& make install-strip \
&& cd /tmp/build \
&& rm -rf "openmpi-${OPENMPI_VERSION}"

ENV PATH=/opt/mpi/bin:${PATH}
ENV LD_LIBRARY_PATH=/opt/mpi/lib

# Parallel HDF5
RUN curl -fsSL \
"https://hdf-wordpress-1.s3.amazonaws.com/wp-content/uploads/manual/HDF5/HDF5_1_14_3/src/hdf5-${HDF5_VERSION}.tar.gz" \
| tar -xz \
&& cd "hdf5-${HDF5_VERSION}" \
&& CC=/opt/mpi/bin/mpicc ./configure \
--prefix=/opt/hdf5 \
--enable-shared \
--disable-static \
--enable-parallel \
&& make -j"$(nproc)" \
&& make install-strip \
&& cd /tmp/build \
&& rm -rf "hdf5-${HDF5_VERSION}"

# Create a runtime-only copy without headers, pkg-config files, static
# archives, documentation, or HDF5 developer tools.
RUN mkdir -p /opt/runtime \
&& cp -a /opt/mpi /opt/runtime/mpi \
&& cp -a /opt/hdf5 /opt/runtime/hdf5 \
&& rm -rf \
/opt/runtime/mpi/include \
/opt/runtime/mpi/share/man \
/opt/runtime/mpi/share/doc \
/opt/runtime/mpi/lib/pkgconfig \
/opt/runtime/hdf5/include \
/opt/runtime/hdf5/share \
/opt/runtime/hdf5/bin \
/opt/runtime/hdf5/lib/pkgconfig \
&& find /opt/runtime -type f \
\( -name '*.a' -o -name '*.la' \) \
-delete


# -----------------------------------------------------------------------------
# Build the Python environment
# -----------------------------------------------------------------------------
FROM native-builder AS python-builder

ENV HDF5_DIR=/opt/hdf5
ENV PATH=/opt/venv/bin:/opt/mpi/bin:${PATH}
ENV LD_LIBRARY_PATH=/opt/mpi/lib:/opt/hdf5/lib
ENV PIP_DISABLE_PIP_VERSION_CHECK=1

RUN python -m venv /opt/venv

RUN --mount=type=cache,target=/root/.cache/pip \
python -m pip install --upgrade \
pip \
setuptools \
wheel \
build \
&& python -m pip install \
featomic \
numpy \
cython \
pkgconfig \
&& MPICC=/opt/mpi/bin/mpicc \
python -m pip install \
--no-binary=mpi4py \
mpi4py \
&& HDF5_DIR=/opt/hdf5 \
HDF5_MPI=ON \
CC=/opt/mpi/bin/mpicc \
python -m pip install \
--no-build-isolation \
--no-binary=h5py \
h5py \
&& python -m pip install \
meson \
packaging \
numba \
ase \
scipy \
pyyaml \
sympy \
&& python -m pip install \
--prefer-binary \
pyscf

# Copy SALTED last so that source-code changes do not invalidate the expensive
# MPI, HDF5, and Python dependency layers.
WORKDIR /src/SALTED
COPY . .

RUN --mount=type=cache,target=/root/.cache/pip \
python -m pip install . \
&& python -m pip uninstall -y \
build \
cython \
meson \
pkgconfig \
wheel \
&& find /opt/venv -type d -name '__pycache__' \
-prune -exec rm -rf '{}' +


# -----------------------------------------------------------------------------
# Minimal runtime
# -----------------------------------------------------------------------------
FROM ${PYTHON_IMAGE} AS runtime

ENV DEBIAN_FRONTEND=noninteractive

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
libevent-2.1-7 \
libevent-pthreads-2.1-7 \
libgfortran5 \
libhwloc15 \
libmunge2 \
libpmi2-0 \
openssh-client \
zlib1g

COPY --from=native-builder /opt/runtime/mpi /opt/mpi
COPY --from=native-builder /opt/runtime/hdf5 /opt/hdf5
COPY --from=python-builder /opt/venv /opt/venv

ENV PATH=/opt/venv/bin:/opt/mpi/bin:${PATH}
ENV LD_LIBRARY_PATH=/opt/mpi/lib:/opt/hdf5/lib
ENV HDF5_DIR=/opt/hdf5
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /work
ENTRYPOINT ["/bin/bash"]

CMD ["/bin/bash"]
107 changes: 107 additions & 0 deletions docs/binary_models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Binary Salted Models
By running `salted_pack.py`, a single binary file is created that consolidates and serializes multiple data sources (NumPy arrays, HDF5 datasets, and model parameters) into one portable format.
This simplifies model sharing and enables easier deployment for prediction tasks.

## Usage
### Creating binary models
Only a single command is required to transform a newly trained and verified model into the binary `.salted` format.
The command must be executed from the main project directory:

salted_pack.py

This will generate a `.salted` file.

### Deploying binary models
Predictions using a binary model can be performed with:

predict_from_model.py

**Input arguments**
| Argument | Meaning |
| -------- | --------|
| model | Path to the `.salted` model file. |
| xyz | Path to the `.xyz` file containing the structure for which the prediction is performed. |
| -o | Output directory for the predicted coefficients (optional, default: `predictions/`) |

## Implementation details
### Basic design
All integers are little-endian signed 32-bit unless noted; all floating arrays are little-endian float64. All 5-char names are ASCII padded with NUL to 5 bytes.
General Arrays

In the following every array is encoded using the same scheme:

def encode_array(NDIMS:int, DIMS:list[int], data):
file.write(NDIMS)
for dim_size in DIMS:
file.write(dim_size)
file.write(data)

This results in a datastructure as follows:

NDIMS (int32)
DIMS (int32 * NDIMS)
DATA (TYPE OF ARRAY)

The Type of the given array is encoded using the numbers from 0 - 5:

int32=0
int64=1
float32=2
float64=3
str=4
bool=5

### Sections in the file
Now the different sections of the file are explained in more detail:
#### Container header

MAGIC (5 bytes): b"SALTD"
VERSION (int32)
N_BLOCKS (int32)
TOC: N_BLOCKS entries of:
BLOCK_NAME (5 bytes, NUL-padded)
BLOCK_OFFSET (int32): file offset (from start) where the block payload begins

#### AVERG, WIG, FPS, WEIGH

TYPE (int32) (datatype of the following block)
NFILES (int32) (number of arrays in the specific key)
FOR EACH FILE
encode_array(NDIMS, DIMS, data)

#### FEATS, PROJE

TYPE (int32): float64
NKEYS (int32) — top-level HDF5 group count (sorted)
For each top-level key:
KEY5 (5 bytes, NUL-padded)
NSUB (int32) — number of datasets under this key (sorted)
For each sub-key dataset:
encode_array(NDIMS, DIMS, data)

#### CONFG

For each entry in inputs (fixed order in code):
KEY5 (5 bytes) — e.g., b"averg", b"ncut\0", …
TYPE (int32)
VALUE encoded by VAL_TYPE:
bool: int32 (0 or 1)
int32: int32
float64: float64
str: SLEN (int32, byte length), then SLEN bytes UTF-8

#### BASIS (Only if pyscf is installed)

TYPE (int32): float64 (tag for numeric arrays in this block)
NELEM (int32): number of elements included
For each element:
ELEM_ID (int32): PySCF element index
Four arrays follow, each preceded by a shape header:
contractions_per_shell (int32[])
encode_array(NDIMS, DIMS, data)
angular_momenta_per_shell (int32[])
encode_array(NDIMS, DIMS, data)
exponents_per_shell (float64[])
encode_array(NDIMS, DIMS, data)
coeffs_per_shell (float64[])
encode_array(NDIMS, DIMS, data)
Loading