Posted on
Artificial Intelligence

Artificial Intelligence Python Packaging

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Artificial Intelligence Python Packaging on Linux: Ship Models Without the Headaches

If you’ve ever spent an afternoon watching pip compile NumPy or PyTorch from source, you know the pain: hours lost, cryptic compiler errors, and an end user who just wants your model to run. Good news—you can avoid most of that. With the right packaging approach, AI apps install cleanly, upgrade predictably, and deploy anywhere your Linux shells go.

This guide explains why AI packaging is tricky, then gives you a practical path: isolate environments, prefer binary wheels, pin for reproducibility, and package your own tools the way pros do. Everything is Linux-first, Bash-friendly, and comes with apt, dnf, and zypper commands.


Why AI packaging is different (and what that means for you)

  • Native code everywhere: NumPy, SciPy, PyTorch, TensorFlow and friends ship C/C++/Fortran/CUDA code. Building them from source needs compilers, headers, and time—you don’t want this on your users’ machines.

  • Platform compatibility: Wheels are tagged for specific platforms (manylinux, musllinux) and Python ABIs. Pick the wrong wheel and you’re back to source builds.

  • GPU stacks: CUDA/ROCm compatibility is sensitive to driver and runtime versions. PyTorch often ships CUDA-enabled wheels; TensorFlow expects you to install CUDA/cuDNN separately on Linux.

  • Reproducibility and security: Unpinned deps and mutable package indexes turn today’s working environment into tomorrow’s mystery. Hash-pinning and constraints files help a lot.

The upshot: Prefer prebuilt binary wheels, manage isolated environments, and lock your dependencies.


0) System prep (compilers, Python, headers)

Even if you’re avoiding source builds, some tooling needs common dev packages. Install these once on your machine or builder.

Debian/Ubuntu (apt):

sudo apt update
sudo apt install -y \
  python3 python3-venv python3-pip python3-dev \
  build-essential gcc g++ make pkg-config \
  git cmake

Fedora/RHEL (dnf):

sudo dnf -y groupinstall "Development Tools"
sudo dnf -y install \
  python3 python3-pip python3-devel \
  git cmake pkgconfig

openSUSE (zypper):

sudo zypper refresh
sudo zypper install -y \
  python3 python3-pip python3-devel \
  git gcc gcc-c++ make cmake pkg-config

Note:

  • On Debian/Ubuntu you need python3-venv to use python -m venv.

  • If you plan GPU work, install NVIDIA drivers and CUDA per vendor docs; don’t mix system CUDA with wheels unless the package supports it.


1) Use isolated environments (no sudo pip; no system pollution)

Create a per-project virtual environment so you can freely install and remove AI stacks.

mkdir -p ~/code/ai-demo
cd ~/code/ai-demo

python3 -m venv .venv
. .venv/bin/activate

python -m pip install --upgrade pip

Confirm isolation:

which python
python -c "import sys; print(sys.prefix)"

Why it matters:

  • Keeps system Python clean.

  • Reproducible across machines/CI.

  • Lets you test CPU vs GPU stacks side-by-side.


2) Prefer binary wheels and match platform/GPU

Install prebuilt wheels to avoid compiling. Use dedicated indexes when frameworks provide CPU/GPU-specific wheels.

Fast, CPU-only installs:

# Force binaries where possible
pip install --only-binary=:all: numpy scipy

# TensorFlow CPU (ensure Python and glibc versions are supported)
pip install --only-binary=:all: tensorflow

PyTorch examples:

# CPU wheels
pip install --index-url https://download.pytorch.org/whl/cpu torch torchvision torchaudio

# CUDA 12.1 wheels (large download)
pip install --index-url https://download.pytorch.org/whl/cu121 torch torchvision torchaudio

Tips:

  • Manylinux vs musllinux: Alpine Linux uses musl, not glibc. Prefer musllinux wheels on Alpine or use a glibc-based image.

  • pip install --only-binary=:all: helps avoid source builds. If a package has no wheel for your platform, remove the flag and expect compilers to kick in.

  • Read framework compatibility matrices (CUDA/ROCm, Python versions) to avoid runtime mismatch errors.


3) Pin, lock, and verify (reproducible installs)

Use constraints or lock files to keep environments stable across machines. Hash-pinning adds supply-chain safety.

Quick-start with pip-tools:

pip install pip-tools

# requirements.in is human-edited
cat > requirements.in <<'EOF'
numpy>=1.26
# Lock PyTorch to a minor release train on Linux
torch==2.3.* ; platform_system == "Linux"
EOF

# Compile a fully-resolved, hash-pinned requirements.txt
pip-compile --generate-hashes -o requirements.txt requirements.in

# Install exactly what was compiled
pip-sync requirements.txt

Or use plain pip with hashes:

pip install --require-hashes -r requirements.txt

Extra hardening:

  • Use a private index or an allowlist proxy.

  • Avoid --trusted-host unless necessary.

  • Record pip freeze > requirements.lock for debugging diffs.


4) Package your own AI tool the right way (pyproject.toml + wheels)

Package your code so others can pip install it—CLI and all. Keep large model files out of your wheel; download them at first run or make them optional extras.

Create a minimal project:

mkdir -p ai_pkg/src/ai_pkg
cd ai_pkg

cat > src/ai_pkg/__init__.py <<'EOF'
__version__ = "0.1.0"
EOF

cat > src/ai_pkg/cli.py <<'EOF'
def main():
    import numpy as np
    try:
        import torch
        x = torch.tensor([1.0, 2.0, 3.0])
        print("mean (torch):", x.mean().item())
    except Exception as e:
        a = np.array([1.0, 2.0, 3.0])
        print("mean (numpy fallback):", a.mean(), f"(torch unavailable: {e})")
EOF

Declare modern packaging metadata (PEP 517/518/621) in pyproject.toml:

cat > pyproject.toml <<'EOF'
[build-system]
requires = ["hatchling>=1.24.2"]
build-backend = "hatchling.build"

[project]
name = "ai-pkg-example"
version = "0.1.0"
description = "Tiny packaged AI demo with CPU fallback"
readme = "README.md"
requires-python = ">=3.9"
license = {text = "MIT"}
authors = [{name = "Your Name"}]

dependencies = [
  "numpy>=1.26",
  # Torch on Linux x86_64; skip on aarch64 or non-Linux to avoid broken installs
  "torch>=2.3 ; platform_system == 'Linux' and platform_machine == 'x86_64'",
]

[project.optional-dependencies]
# Let users opt into GPU-specific deps via: pip install ai-pkg-example[gpu]
gpu = [
  "torch==2.3.* ; platform_system == 'Linux' and platform_machine == 'x86_64'"
]

[project.scripts]
ai-demo = "ai_pkg.cli:main"
EOF

Add a simple README.md:

echo "# ai-pkg-example" > README.md

Build a wheel and sdist:

# Install build tooling
pip install build

# Build distributions
python -m build

# Your files appear in ./dist/
ls -1 dist

Optional: publish via TestPyPI first.

Install pipx for isolated tooling:

apt:

sudo apt update
sudo apt install -y pipx
pipx ensurepath

dnf:

sudo dnf install -y pipx
pipx ensurepath

zypper:

sudo zypper install -y pipx
pipx ensurepath

Then:

pipx install twine
twine check dist/*
twine upload --repository-url https://test.pypi.org/legacy/ dist/*

Test installation from TestPyPI:

python -m venv /tmp/testenv && . /tmp/testenv/bin/activate
pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple ai-pkg-example
ai-demo

Notes:

  • Put large models on a CDN or Hugging Face and download at first run, caching under ~/.cache/yourapp.

  • If you need native extensions, produce manylinux wheels using the official manylinux containers and auditwheel. Alpine users should target musllinux.


5) Keep installs small, fast, and secure

  • Minimize heavy deps: do you need the full torch or just ONNX Runtime or tflite-runtime?

  • Optional extras: GPU packages as [gpu] extras avoid bloating CPU-only installs.

  • Lazy imports: import heavy frameworks only when needed to speed CLI startup.

  • Scan for known vulns:

    pipx install pip-audit
    pip-audit -r requirements.txt
    
  • Verify integrity with hash-pinning and immutable artifact storage (e.g., internal PyPI mirror).


Real-world patterns that work

  • CLI microtools: Package a small pre/post-processing tool with console_scripts. Users pip install yourtool and run yourtool from Bash.

  • Server deploys: Build a wheel in CI, publish to an internal index, and pip install yourpkg==X.Y.Z --only-binary=:all: in Docker images.

  • CPU/GPU split: Publish a base package mlapp (CPU) plus mlapp[gpu] extras or a sibling mlapp-gpu that declares the right CUDA wheel index.


Common pitfalls (and how to avoid them)

  • Using sudo pip: this corrupts system Python and fights with your package manager. Always use venv or containers.

  • Alpine + manylinux wheels: glibc vs musl mismatch. Use musllinux wheels or a glibc base image.

  • TensorFlow GPU on Linux: the pip wheel expects system CUDA/cuDNN. Install the exact versions from TensorFlow’s compatibility table.

  • Mixing CUDA versions: PyTorch CUDA 12.1 wheels expect matching drivers. Read the install page and choose the correct --index-url.


Conclusion and next steps

Packaging is as critical to AI success as your model’s accuracy. On Linux, a disciplined approach—isolated environments, binary wheels, locked dependencies, and a clean pyproject.toml—turns “it works on my machine” into “it works everywhere.”

Try this now: 1) Create a new venv and install NumPy or PyTorch with binary wheels.
2) Lock your environment with pip-compile and hashes.
3) Package a tiny CLI with pyproject.toml and publish to TestPyPI.

Have questions about your specific stack (CUDA, Alpine, CI/CD, private indexes)? Bring your Bash logs and we’ll help you make it bulletproof.