TensorFlow GPU Slim Images - Repository Skeleton¶
This skeleton provides a complete, production-ready example for building slim TensorFlow GPU containers. It includes both approaches (CUDA runtime base and tensorflow[and-cuda] wheels) with a working FastAPI application.
Project Structure¶
tf-gpu-slim/
├── Dockerfile.approach-a # CUDA runtime base approach
├── Dockerfile.approach-b # tensorflow[and-cuda] wheels approach
├── requirements.txt # Python dependencies
├── docker-compose.yml # Multi-service setup
├── .dockerignore # Build exclusions
├── README.md # Project documentation
└── app/
├── main.py # FastAPI application
├── models/
│ ├── create_model.py # Model creation script
│ └── model.h5 # Saved Keras model
└── utils/
└── preprocessing.py # Data preprocessing utilities
Dockerfile.approach-a (CUDA Runtime Base)¶
# syntax=docker/dockerfile:1.9
############################
# 1) Builder: resolve wheels
############################
FROM nvidia/cuda:12.4.1-devel-ubuntu22.04 AS builder
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
python3.12 python3.12-venv python3-pip ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
RUN python3.12 -m venv /opt/venv
ENV PATH=/opt/venv/bin:$PATH PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_NO_CACHE_DIR=1
# Cache pip, lock versions
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --upgrade pip \
&& pip install \
tensorflow==2.16.1 \
fastapi==0.111.* uvicorn[standard]==0.30.* \
numpy==2.* pillow==10.* protobuf~=4.25
# Trim common cruft inside site-packages
RUN find /opt/venv/lib -type d -regex ".*\(tests\|__pycache__\|\.dist-info/.*-nspkg\).*" -prune -exec rm -rf {} + || true
############################
# 2) Runtime: CUDA runtime only
############################
FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04 AS runtime
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
python3.12 ca-certificates libgomp1 \
&& rm -rf /var/lib/apt/lists/*
# Bring the venv only
COPY --from=builder /opt/venv /opt/venv
ENV PATH=/opt/venv/bin:$PATH \
PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 \
NVIDIA_VISIBLE_DEVICES=all NVIDIA_DRIVER_CAPABILITIES=compute,utility
WORKDIR /app
COPY app/ /app/
# Last cleanup sweep
RUN rm -rf /root/.cache/*
EXPOSE 8000
CMD ["uvicorn","app.main:app","--host","0.0.0.0","--port","8000"]
Dockerfile.approach-b (tensorflow[and-cuda] Wheels)¶
# syntax=docker/dockerfile:1.9
FROM python:3.12-slim AS base
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_NO_CACHE_DIR=1 \
TF_CPP_MIN_LOG_LEVEL=1
# System libs TensorFlow uses at runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
libgomp1 libgl1 libglib2.0-0 libsm6 libxext6 libxrender1 ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# BuildKit pip cache
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --upgrade pip \
&& pip install "tensorflow[and-cuda]==2.16.1" \
fastapi==0.111.* uvicorn[standard]==0.30.* numpy==2.* pillow==10.* protobuf~=4.25
# Optional trims (validate after!)
RUN python - <<'PY'
import os, shutil, sys
base = next(p for p in sys.path if p.endswith('site-packages'))
trash = ['tensorflow/include','tensorflow/python/_pywrap_tensorflow_internal_d.so.debug']
for t in trash:
p=os.path.join(base,'tensorflow',*t.split('/'))
if os.path.exists(p):
try: os.remove(p)
except IsADirectoryError: shutil.rmtree(p, ignore_errors=True)
PY
WORKDIR /app
COPY app/ /app/
ENV NVIDIA_VISIBLE_DEVICES=all NVIDIA_DRIVER_CAPABILITIES=compute,utility
EXPOSE 8000
CMD ["uvicorn","app.main:app","--host","0.0.0.0","--port","8000"]
requirements.txt¶
tensorflow[and-cuda]==2.16.1
fastapi==0.111.0
uvicorn[standard]==0.30.0
numpy==2.0.0
pillow==10.1.0
protobuf~=4.25
docker-compose.yml¶
version: '3.8'
services:
tf-app-a:
build:
context: .
dockerfile: Dockerfile.approach-a
ports:
- "8000:8000"
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]
volumes:
- ./app/models:/app/models
tf-app-b:
build:
context: .
dockerfile: Dockerfile.approach-b
ports:
- "8001:8000"
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]
volumes:
- ./app/models:/app/models
.dockerignore¶
# Python
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
.venv/
# Git
.git/
.gitignore
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Documentation
*.md
docs/
# Development
tests/
.pytest_cache/
.coverage
app/main.py (FastAPI Application)¶
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import tensorflow as tf
import numpy as np
from typing import List, Dict, Any
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(
title="TensorFlow GPU Inference API",
description="Slim TensorFlow GPU container for inference",
version="1.0.0"
)
# Global model variable
model = None
class PredictionRequest(BaseModel):
data: List[float]
batch_size: int = 1
class PredictionResponse(BaseModel):
predictions: List[float]
model_version: str
gpu_available: bool
@app.on_event("startup")
async def load_model():
"""Load the TensorFlow model on startup"""
global model
try:
model = tf.keras.models.load_model("/app/models/model.h5")
logger.info(f"Model loaded successfully. Version: {tf.__version__}")
# Log GPU information
gpus = tf.config.list_physical_devices('GPU')
if gpus:
logger.info(f"GPU available: {[gpu.name for gpu in gpus]}")
else:
logger.warning("No GPU detected, running on CPU")
except Exception as e:
logger.error(f"Failed to load model: {e}")
raise
@app.get("/health")
async def health_check():
"""Health check endpoint"""
gpus = tf.config.list_physical_devices('GPU')
return {
"status": "healthy",
"tensorflow_version": tf.__version__,
"gpu_available": len(gpus) > 0,
"gpu_devices": [gpu.name for gpu in gpus] if gpus else [],
"model_loaded": model is not None
}
@app.get("/model/info")
async def model_info():
"""Get model information"""
if model is None:
raise HTTPException(status_code=503, detail="Model not loaded")
return {
"model_summary": str(model.summary()),
"input_shape": model.input_shape,
"output_shape": model.output_shape,
"total_params": model.count_params()
}
@app.post("/predict", response_model=PredictionResponse)
async def predict(request: PredictionRequest):
"""Make predictions using the loaded model"""
if model is None:
raise HTTPException(status_code=503, detail="Model not loaded")
try:
# Convert input to numpy array
input_data = np.array(request.data).reshape(1, -1)
# Make prediction
prediction = model.predict(input_data, batch_size=request.batch_size)
# Convert to list for JSON serialization
predictions = prediction.tolist()[0] if len(prediction.shape) > 1 else prediction.tolist()
return PredictionResponse(
predictions=predictions,
model_version=tf.__version__,
gpu_available=len(tf.config.list_physical_devices('GPU')) > 0
)
except Exception as e:
logger.error(f"Prediction failed: {e}")
raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")
@app.get("/gpu/info")
async def gpu_info():
"""Get detailed GPU information"""
gpus = tf.config.list_physical_devices('GPU')
if not gpus:
return {"gpu_available": False, "message": "No GPU detected"}
gpu_info = []
for gpu in gpus:
gpu_info.append({
"name": gpu.name,
"device_type": gpu.device_type,
"memory_limit": tf.config.experimental.get_memory_info(gpu.name) if hasattr(tf.config.experimental, 'get_memory_info') else None
})
return {
"gpu_available": True,
"gpu_count": len(gpus),
"gpus": gpu_info
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
app/models/create_model.py (Model Creation Script)¶
"""
Create a simple Keras model for demonstration purposes.
This script creates a small neural network and saves it as model.h5
"""
import tensorflow as tf
import numpy as np
from pathlib import Path
def create_simple_model():
"""Create a simple neural network for demonstration"""
model = tf.keras.Sequential([
tf.keras.layers.Dense(64, activation='relu', input_shape=(10,)),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(32, activation='relu'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(1, activation='sigmoid')
])
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy']
)
return model
def generate_training_data(n_samples=1000):
"""Generate synthetic training data"""
np.random.seed(42)
X = np.random.randn(n_samples, 10)
y = (X.sum(axis=1) > 0).astype(int)
return X, y
def main():
"""Create and train the model"""
print("Creating model...")
model = create_simple_model()
print("Generating training data...")
X, y = generate_training_data()
print("Training model...")
model.fit(X, y, epochs=10, batch_size=32, validation_split=0.2, verbose=1)
# Create models directory
models_dir = Path("models")
models_dir.mkdir(exist_ok=True)
# Save model
model_path = models_dir / "model.h5"
model.save(model_path)
print(f"Model saved to {model_path}")
# Test the model
test_data = np.random.randn(1, 10)
prediction = model.predict(test_data)
print(f"Test prediction: {prediction[0][0]:.4f}")
if __name__ == "__main__":
main()
app/utils/preprocessing.py (Data Preprocessing Utilities)¶
"""
Data preprocessing utilities for the TensorFlow application
"""
import numpy as np
from typing import List, Union
def normalize_data(data: Union[List[float], np.ndarray]) -> np.ndarray:
"""Normalize input data to [0, 1] range"""
data = np.array(data)
return (data - data.min()) / (data.max() - data.min() + 1e-8)
def standardize_data(data: Union[List[float], np.ndarray]) -> np.ndarray:
"""Standardize input data to zero mean and unit variance"""
data = np.array(data)
return (data - data.mean()) / (data.std() + 1e-8)
def validate_input_shape(data: Union[List[float], np.ndarray], expected_shape: int = 10) -> bool:
"""Validate that input data has the expected shape"""
data = np.array(data)
return len(data) == expected_shape
def preprocess_for_prediction(data: List[float]) -> np.ndarray:
"""Complete preprocessing pipeline for prediction"""
if not validate_input_shape(data):
raise ValueError(f"Expected input shape of 10, got {len(data)}")
# Normalize the data
processed_data = normalize_data(data)
# Reshape for model input
return processed_data.reshape(1, -1)
README.md¶
# TensorFlow GPU Slim Images
This repository demonstrates how to build slim TensorFlow GPU containers using two different approaches:
1. **Approach A**: CUDA runtime base with multi-stage builds
2. **Approach B**: tensorflow[and-cuda] wheels with minimal base image
## Quick Start
### Prerequisites
- Docker with BuildKit enabled
- NVIDIA Container Toolkit
- GPU with CUDA support
### Build and Run
```bash
# Build Approach A
docker build -f Dockerfile.approach-a -t tf-gpu:a .
# Build Approach B
docker build -f Dockerfile.approach-b -t tf-gpu:b .
# Run with GPU support
docker run --gpus all -p 8000:8000 tf-gpu:a
# Or use docker-compose
docker-compose up tf-app-a
Test the API¶
# Health check
curl http://localhost:8000/health
# Make a prediction
curl -X POST "http://localhost:8000/predict" \
-H "Content-Type: application/json" \
-d '{"data": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}'
# Get GPU info
curl http://localhost:8000/gpu/info
Create Model¶
Size Comparison¶
- Approach A: ~800MB (CUDA runtime base)
- Approach B: ~600MB (tensorflow[and-cuda] wheels)
Features¶
- FastAPI application with health checks
- GPU detection and monitoring
- Model loading and inference
- Comprehensive error handling
- Production-ready logging
Development¶
# Install dependencies
pip install -r requirements.txt
# Run locally
python app/main.py
# Test with curl
curl http://localhost:8000/health
Production Deployment¶
This skeleton is designed for production deployment with:
- Health checks
- GPU monitoring
- Error handling
- Logging
- Model versioning ```
Usage Instructions¶
- Copy the skeleton to a new directory
- Create the model by running
python app/models/create_model.py - Build the images using either Dockerfile approach
- Test the API with the provided curl commands
- Deploy to production using docker-compose
This skeleton provides a complete, production-ready example for building slim TensorFlow GPU containers with both approaches demonstrated.