FractalNet in production
FractalNet in production
Nachfolgend finden Sie eine sofort einsatzbereite, fehlertolerante Produktionslösung. Wir kombinieren einen asynchronen Webserver mit FastAPI mit einer Multithread-fähigen ONNX Runtime-Inferenz-Engine. Um maximale CPU-Leistung zu erzielen, konfigurieren wir explizit die Parameter `intra_op_num_threads` (Threads zur Parallelisierung einer einzelnen mathematischen Operation) und `inter_op_num_threads` (Threads zur parallelen Ausführung unabhängiger Graphknoten).
1. Proxy-Webserver, anschließend als proxy_pass an nginx. (server.py)
pip install fastapi uvicorn onnxruntime pillow numpy python-multipart requests
Dieses Skript initialisiert die ONNX-Sitzung beim Start, konfiguriert Prozessorthreads, empfängt Bilder über die API, verarbeitet sie asynchron und gibt das Ergebnis im JSON-Format zurück. Wir verwenden das zuvor erhaltene quantisierte Modell.
import io
import os
import asyncio
from concurrent.futures import ThreadPoolExecutor
from fastapi import FastAPI, File, UploadFile, HTTPException
import numpy as np
import onnxruntime as ort
from PIL import Image
app = FastAPI(title="Adaptiver Fractal-Server", description="Hochleistungsfähige API für das INT8-Modell mit Multithreading auf der CPU")
# Globale Variablen für Sitzung und Thread-Pool
ort_session = None
thread_pool = None
MODEL_PATH = "onnx_models/fractal_net_int8.onnx"
@app.on_event("startup")
async def startup_event():
global ort_session, thread_pool
if not os.path.exists(MODEL_PATH):
raise FileNotFoundError(f"Für den Pfad {MODEL_PATH} wurde kein quantisiertes Modell gefunden")
# ONNX Runtime Asynchrone Multithreading-Konfiguration für CPU
opts = ort.SessionOptions()
# 1. Wir legen die Anzahl der Threads für parallele Berechnungen innerhalb einer Schicht fest.
# Optimal: Stellen Sie den Wert gleich der Anzahl der physischen Kerne Ihres Prozessors ein.
opts.intra_op_num_threads = os.cpu_count()
# 2. Wir legen die Anzahl der Threads für die gleichzeitige Ausführung unabhängiger Schichten fest.
opts.inter_op_num_threads = 2
# Intensiver Graphoptimierungsmodus
opts.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
opts.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
# Die Sitzung wird ausschließlich auf der CPU initialisiert.
ort_session = ort.InferenceSession(
MODEL_PATH,
sess_options=opts,
providers=['CPUExecutionProvider']
)
# Erstellen Sie einen Thread-Pool, um synchrone CPU-Berechnungen aus der asynchronen FastAPI-Hauptschleife auszulagern.
thread_pool = ThreadPoolExecutor(max_workers=os.cpu_count())
print(f"Server gestartet. ONNX CPU-Thread-Konfiguration: {opts.intra_op_num_threads}")
def preprocess_image(image_bytes: bytes) -> np.ndarray:
"""Synchrone Bildvorverarbeitung mit Pillow und NumPy."""
try:
img = Image.open(io.BytesIO(image_bytes)).convert('RGB')
img = img.resize((128, 128))
# Wir wandeln es in ein Array um und normalisieren es (genau wie beim Training).
img_array = np.array(img).astype(np.float32) / 255.0
# ImageNet-Standardisierung: (X - mean) / std
mean = np.array([0.485, 0.456, 0.406], dtype=np.float32)
std = np.array([0.229, 0.224, 0.225], dtype=np.float32)
img_array = (img_array - mean) / std
# Ändern wir die Dimension von [H, W, C] in [C, H, W] und fügen Sie die Chargengröße [1, C, H, W] hinzu.
img_array = np.transpose(img_array, (2, 0, 1))
img_array = np.expand_dims(img_array, axis=0)
return img_array
except Exception as e:
raise ValueError(f"Fehler bei der Bilddekoration: {str(e)}")
def run_inference(input_tensor: np.ndarray):
"""Synchrone Ausführung von Berechnungen in ONNX Runtime."""
input_name = ort_session.get_inputs()[0].name
outputs = ort_session.run(None, {input_name: input_tensor})
return outputs
@app.post("/predict")
async def predict(file: UploadFile = File(...)):
"""Asynchroner Endpunkt zur Verarbeitung eingehender Dateien."""
# Dateiformatvalidierung
if file.content_type not in ["image/jpeg", "image/png"]:
raise HTTPException(status_code=400, detail="Zulässig sind nur die Dateiformate JPEG und PNG.")
# Asynchrones Lesen von Bytes aus dem Netzwerk
image_bytes = await file.read()
loop = asyncio.get_event_loop()
try:
# Wir verlagern aufwändige Vorverarbeitungsprozesse in einen separaten Thread, um die asynchrone Schleife nicht zu blockieren.
input_tensor = await loop.run_in_executor(thread_pool, preprocess_image, image_bytes)
# Wir verlagern die mathematische Inferenz des Modells in den Thread-Pool.
outputs = await loop.run_in_executor(thread_pool, run_inference, input_tensor)
logits, fractal_weights = outputs[0], outputs[1]
# Berechnung der endgültigen Klasse und Wahrscheinlichkeiten mit Softmax in NumPy
exp_logits = np.exp(logits - np.max(logits, axis=1, keepdims=True))
probabilities = exp_logits / np.sum(exp_logits, axis=1, keepdims=True)
predicted_class = int(np.argmax(probabilities, axis=1)[0])
# Eine strukturierte Antwort zurückgeben
return {
"status": "success",
"predicted_class": predicted_class,
"probabilities": probabilities[0].tolist(),
"fractal_router_weights": fractal_weights[0].tolist()
}
except ValueError as val_err:
raise HTTPException(status_code=400, detail=str(val_err))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Interner Serverfehler beim Ableiten: {str(e)}")
@app.on_event("shutdown")
async def shutdown_event():
# Den Thread-Pool beim Serverstopp ordnungsgemäß schließen.
global thread_pool
if thread_pool:
thread_pool.shutdown()
print("Der Server-Threadpool wurde erfolgreich geschlossen.")
Starten Sie den Server im Terminal aus dem Projektordner mit uvicorn:
uvicorn server:app --host 0.0.0.0 --port 8000 --workers 1
Es wird empfohlen, den Parameter `--workers 1` auf eins zu setzen, da die interne Parallelisierung innerhalb einer einzelnen ONNX-Runtime-Sitzung (`intra_op_num_threads`) die CPU-Kerne deutlich effizienter verteilt als das Starten mehrerer separater uvicorn-Prozesse. Öffnen Sie nach dem Start einen Browser unter http://localhost:8000/docs. Sie gelangen zur Swagger-UI. Suchen Sie die Methode `/predict`, klicken Sie auf die Schaltfläche „Ausprobieren“, fügen Sie ein beliebiges Bild hinzu und klicken Sie auf „Ausführen“. Der Server sendet umgehend eine JSON-Antwort.
2. Beispiel eines automatischen Client-Verifizierungsskripts (test_client.py)
Sie können den Server programmatisch testen, indem Sie eine HTTP-POST-Anfrage senden.
import requests
url = "http://localhost:8000/predict"
file_path = "dataset_split/test/class_0/any_image.jpg" # Pfad zum realen Testfoto
with open(file_path, "rb") as f:
files = {"file": (file_path, f, "image/jpeg")}
response = requests.post(url, files=files)
print("Serverantwort:", response.json())
Wichtig für die Implementierung in der Produktion:
- Wenn Sie die Konfiguration für den Produktivbetrieb beibehalten möchten, ändern Sie 0.0.0.0 in 127.0.0.1, damit der Port 8000 nicht von außen zugänglich ist.
Systemdienst für komfortables Management
Lasst uns also ein Skript für einen einfachen Start und die Fehlerverfolgung erstellen. Wir werden einen Socket verwenden, um die Datenübertragung zwischen dem nginx und service zu beschleunigen.
1. Superuser
Hierfür benötigen wir den Superuser-Modus.
sudo -s
Falls Sie www-data verwenden müssen, können Sie das Skript manuell in den Einstellungen ausführen. Bearbeiten Sie die Datei /etc/passwd so, dass beim Wechsel zu www-data dieser Benutzer die Bash-Shell ausführt.
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin --> www-data:x:33:33:www-data:/var/www:/bin/bash
su - www-data
2. Lasst uns die notwendige Umgebung für die Arbeit mit fractalnet in uvicorn einrichten.
Hier grenzen wir unseren Service von der allgemeinen Umgebung ab.
cd /var/www/fractalnet
python3 -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn onnxruntime pillow numpy python-multipart requests
deactivate
3. Erstellen einer /etc/systemd/system/fractalnet.service
Das Skript wird von www-data ausgeführt, und der Dienst startet, sobald das Netzwerk hergestellt ist.
vi /etc/systemd/system/fractalnet.service
[Unit]
Description=Uvicorn ASGI Server via Socket
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/fractalnet
# Starten über die Socket-Datei mit automatischer Berechtigung (umask)
UMask=0007
ExecStart=/var/www/fractalnet/.venv/bin/uvicorn main:app --uds /var/www/fractalnet/fractalnet.sock --workers 1
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl restart fractalnet
4. Wir überprüfen die Funktionalität unseres Dienstes.
Wir stellen sicher, dass der Service läuft und alles in Ordnung ist. Nachdem Sie den Service gestartet haben, prüfen Sie mit folgendem Befehl, ob die Socket-Datei die richtigen Rechte hat. Durch umask 0007 muss die Ausgabe so aussehen (Besitzer und Gruppe haben Vollzugriff, andere gar keinen) srwxrwx--- 1 www-data www-data ... fractalnet.sock
systemctl status fractalnet
ls -l /var/www/fractalnet/fractalnet.sock
5. Nginx erstellen
Installieren Sie die erforderlichen Pakete
apt update
apt install nginx certbot python3-certbot-nginx -y
6. Nginx-Konfiguration erstellen
Erstellen Sie eine neue Konfigurationsdatei für Ihre Webseite in Nginx:
vi /etc/nginx/sites-available/fractalnet
server {
listen 80;
server_name ihre-domain.de; # Ersetzen Sie dies durch Ihre Domain oder IP-Adresse
# Erlaubt große Datei-Uploads falls nötig
client_max_body_size 10M;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade; # Wichtig für WebSockets
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_buffering off;
# Verweis auf die exakte Socket-Datei aus der systemd-Datei
proxy_pass http://unix:/var/www/fractalnet/fractalnet.sock;
}
}
# Konfiguration aktivieren
ln -s /etc/nginx/sites-available/fractalnet /etc/nginx/sites-enabled/
# Nginx-Konfiguration auf Syntaxfehler prüfen
nginx -t
# Wenn der Test erfolgreich war, Nginx neu laden
systemctl restart nginx
7. SSL-Zertifikat anfordern
Führen Sie Certbot aus. Ersetzen Sie ihre-domain.de mit Ihrer echten, auf den Server zeigenden Domain
certbot certonly --nginx
8. Ihre Domain mit SSL
Ändern Sie die Konfiguration des virtuellen Hosts wie folgt:
# 1. Der neue HTTPS-Block (Port 443)
server {
listen 443 ssl;
server_name ihre-domain.de;
# Erlaubt große Datei-Uploads falls nötig
client_max_body_size 10M;
ssl_certificate /etc/letsencrypt/live/ihre-domain.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ihre-domain.de/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_buffering off;
proxy_pass http://unix:/var/www/fractalnet/fractalnet.sock;
}
}
# 2. Der alte HTTP-Block (Port 80) – leitet jetzt auf HTTPS weiter
server {
listen 80;
server_name ihre-domain.de;
if ($host = ihre-domain.de) {
return 301 https://$host$request_uri;
}
return 404;
}
nginx -t
systemctl restart nginx
9. Fehlerprotokolle auslesen
Live-Fehler-Log von Nginx and Uvicorn-Services ansehen
tail -50 /var/log/nginx/error.log
journalctl -u fractalnet.service -f -n 50
Deployment (Git - Basis)
Hier ist ein einfaches, aber effektives Deployment-Skript (Bash). Sie können es auf Ihrem Server hinterlegen (z. B. für Git-Push-Hooks, GitHub Actions oder GitLab CI/CD), um Ihre Anwendung mit einem einzigen Befehl ohne Ausfallzeit zu aktualisieren.
1. Erstellen requirements.txt in /var/www/fractalnet
Wir werden alle Python-Päckchen zu einer Datei zusammenfassen.
source .venv/bin/activate
pip freeze > requirements.txt
deactivate
2. Deployment-Skript erstellen /var/www/fractalnet/deploy.sh
Fügen Sie den folgenden Inhalt ein. Das Skript zieht den neuesten Code, aktualisiert die virtuellen Abhängigkeiten, prüft die Nginx-Konfiguration und startet Uvicorn sauber neu
#!/bin/bash
# Fehler sofort abfangen und Skript stoppen
set -e
PROJECT_DIR="/var/www/fractalnet"
VENV_DIR="$PROJECT_DIR/.venv"
echo "Starte Deployment..."
cd $PROJECT_DIR
# 1. Neuesten Code aus dem Git-Repository holen
echo "Ziehe aktuellen Code von Git..."
git pull origin main
# 2. Virtuelle Umgebung aktivieren & Abhängigkeiten aktualisieren
echo "Aktualisiere Python-Pakete..."
source $VENV_DIR/bin/activate
pip install -r requirements.txt
# 3. Uvicorn-Service neu starten
echo "Starte Uvicorn-Service neu..."
sudo systemctl restart fractalnet
echo "Deployment erfolgreich abgeschlossen!"
3. Skript ausführbar machen
Damit das Skript ausgeführt werden darf, müssen Sie die Rechte anpassen
chmod +x /var/www/fractalnet/deploy.sh
4. Check
Sie können das Deployment ab jetzt jederzeit manuell mit diesem Befehl starten
/var/www/fractalnet/deploy.sh
5. Berechtigungen für sudo einrichten (Wichtig für Automation)
Wenn dieses Skript vollautomatisiert (z. B. über einen CI/CD-Runner) ausgeführt wird, scheitert es an den sudo-Befehlen für Nginx und Systemd, da diese normalerweise ein Passwort verlangen.Sie können dem spezifischen Deployment-Nutzer (oder der Gruppe) erlauben
visudo
ihr_runner_user ALL=(ALL) NOPASSWD: /usr/sbin/nginx -t, /usr/bin/systemctl restart fractalnet
6. SSH-Sicherheitsdaten (Secrets) in GitHub hinterlegen
Damit GitHub auf Ihren Server zugreifen darf, ohne dass Ihr Passwort im Code steht, müssen Sie Repository Secrets anlegen (Settings -> Secrets and variables -> Actions)
cat ~/.ssh/id_rsa.pub
7. Workflow-Datei im Repository anlegen
Erstellen Sie in Ihrem lokalen Projektverzeichnis die Ordnerstruktur und die Datei unter folgendem Pfad: .github/workflows/deploy.yml
name: Production Deployment
on:
push:
branches:
- main # Der Workflow startet, sobald Code in den main-Branch gepusht wird
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Execute Deployment Script via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ... # Server IP-Adresse
username: ... # SSH-Nutzername (z. B. ihr_runner_user)
key: ... # Inhalt Ihres privaten SSH-Schlüssels
port: ... # SSH-Port (Standard: 22)
script: |
echo "Verbindung zum Server hergestellt."
# Führt das Skript auf dem Server aus
/var/www/fractalnet/deploy.sh
8. Workflow testen
Sobald Sie die Änderungen commiten und pushen, startet GitHub das Deployment
git add .github/workflows/deploy.yml
git commit -m "ci: add GitHub Actions deployment workflow"
git push origin main
FractalNet mit docker
Um unser gesamtes Projekt (FastAPI + Multithread-ONNX-Runtime INT8) in einem einzigen, isolierten Docker-Container zu packen, benötigen wir drei Dateien: eine Dockerfile zum Erstellen des Images, eine .dockerignore-Datei zum Entfernen unnötiger Dateien und eine docker-compose.yml-Datei für den einfachen Start mit einem einzigen Befehl. Wir verwenden das offizielle, schlanke Python 3.10-slim-Image als Basis-Image und setzen die entsprechenden Umgebungsvariablen, um CPU-Thread-Blockaden zu vermeiden.
1. Erstellen einer .dockerignore
Erstellen Sie eine .dockerignore-Datei im Stammverzeichnis, um zu verhindern, dass Docker Gigabytes an unnötigen Protokollen, Cache-Daten und Rohdatensätzen in den Container kopiert.
__pycache__/
*.pyc
*.pyo
*.pyd
.git
.gitignore
runs/
data_raw/
dataset_split/
.vscode/
.idea/
2. Erstellen einer Dockerfile
Erstellen Sie eine Datei namens Dockerfile (ohne Dateiendung). Wir trennen die Installation von Abhängigkeiten und das Kopieren von Code in verschiedene Ebenen. Dadurch können Sie den Container bei Codeänderungen sofort neu erstellen, ohne Bibliotheken erneut herunterladen zu müssen. Wir verwenden das offizielle optimierte Slim-Image.
FROM python:3.10-slim
# Installieren wir die Systemabhängigkeiten für die Arbeit mit Bildern (falls Pillow benötigt wird).
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Einrichten eines Arbeitsverzeichnisses innerhalb des Containers
WORKDIR /app
# Wir kopieren und installieren nur die Caching-Abhängigkeiten für diese Docker-Schicht.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Wir kopieren die Netzwerkarchitekturdatei, den Server und das kompilierte INT8-Modell selbst.
COPY model.py .
COPY server.py .
COPY onnx_models/fractal_net_int8.onnx ./onnx_models/fractal_net_int8.onnx
# Optimierung der Python-Performance in Docker (Deaktivierung der Protokollpufferung)
ENV PYTHONUNBUFFERED=1
# Wir öffnen den Port, auf dem unser Webserver laufen soll.
EXPOSE 8000
# Anleitung zum Starten des uvicorn-Servers
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
3. Datei requirements.txt
Vor dem Kompilieren stellen Sie sicher, dass Sie im Stammverzeichnis Ihres Projekts eine requirements.txt-Datei mit folgendem Inhalt erstellt haben
fastapi
uvicorn
onnxruntime
pillow
numpy
python-multipart
4. Erstellen einer docker-compose.yml
Für eine einfache Containerverwaltung (Portweiterleitung, Neustart im Fehlerfall) erstellen Sie eine docker-compose.yml-Datei im Projektverzeichnis.
services:
fractal-api:
build: .
container_name: adaptive_fractal_server
ports:
- "8000:8000"
restart: always
environment:
- OMP_NUM_THREADS=${OMP_NUM_THREADS:-2} # 2 Kerne, der Wert ist jedoch dynamisch und funktioniert automatisch.
5. Docker-Image
Führen Sie den folgenden Befehl im Terminal aus dem Projektordner aus. Dadurch wird automatisch das Docker-Image erstellt und der Server im Hintergrund gestartet.
docker compose up --build -d
6. Funktionsprüfung
Der Container läuft nun isoliert auf Ihrem PC. Sie können seine API auf die gleiche Weise über Ihren Browser testen.
http://localhost:8000/docs
7. Serverprotokolle in Echtzeit anzeigen
Wenn Sie Protokolle eingehender Anfragen oder Inferenzfehler innerhalb des Containers einsehen möchten.
docker compose logs -f