CI/CD für KI-Systeme: GitHub Actions, ECR und GPU-Deployments auf AWS

Dein RAG-System läuft lokal einwandfrei. Die Antworten sind präzise, die Retrieval-Qualität stimmt, die Inferenz ist schnell genug. Dann kommt das Deployment. Der Container braucht zwei Minuten zum Starten, weil das Embedding-Modell in den GPU-Speicher geladen werden muss. Der Health Check schlägt fehl, weil er nach 30 Sekunden aufgibt. Der Load Balancer routet Traffic an einen Container, der noch nicht bereit ist. Rollback.
GPU-basierte KI-Systeme brechen die Annahmen, auf denen Standard-CI/CD-Pipelines aufgebaut sind. Container starten nicht in Sekunden, sondern in Minuten. Images sind nicht 200 MB groß, sondern 2 bis 8 GB. Ein fehlgeschlagenes Deployment bedeutet nicht nur Downtime, sondern potenziell verlorene GPU-Rechenzeit, die pro Stunde abgerechnet wird.
In diesem Artikel zeige ich eine produktionsreife Pipeline für GPU-basierte KI-Systeme auf AWS. Von OIDC-Authentifizierung über ECR Lifecycle-Policies bis zu ECS Rolling Deployments mit Circuit Breaker. Jede Entscheidung wird begründet, jeder Trade-off benannt. Falls du bereits Erfahrung mit RAG-Systemen in Produktion hast, schließt dieser Artikel die Lücke zwischen funktionierendem System und zuverlässigem Deployment.
Warum CI/CD für KI-Systeme anders ist
Bevor wir in die Implementierung einsteigen, lohnt sich ein Blick auf die fundamentalen Unterschiede. Diese Tabelle fasst zusammen, warum Standard-Pipelines bei GPU-Workloads scheitern:
| Aspekt | Standard-App | GPU/KI-System |
|---|---|---|
| Container-Start | 2 bis 5 Sekunden | 60 bis 120 Sekunden |
| Health Check | HTTP 200 nach 3s | GPU + Modell geladen nach 90s |
| Image-Größe | 100 bis 300 MB | 2 bis 8 GB |
| Worker-Skalierung | Horizontal (N Worker) | 1 Worker pro GPU (VRAM-Limit) |
| Rollback-Risiko | Sekunden Downtime | Minuten GPU-Kosten |
| Skalierung | Mehr Worker im Container | Mehr Container (horizontal) |
Diese Unterschiede haben direkte Auswirkungen auf jede Komponente der Pipeline: Health-Check-Timeouts müssen angepasst werden, der Load Balancer braucht eine Aufwärmphase, und der Circuit Breaker muss wissen, dass ein langer Start kein Fehler ist.
OIDC: Keine gespeicherten Credentials
Die erste Entscheidung betrifft die Authentifizierung. Viele Teams nutzen langlebige AWS Access Keys in GitHub Secrets. Das funktioniert, ist aber ein Sicherheitsrisiko: Die Keys rotieren nicht automatisch, haben oft zu breite Berechtigungen und müssen manuell verwaltet werden.
Die Alternative ist OIDC (OpenID Connect). GitHub Actions authentifiziert sich direkt bei AWS mit kurzlebigen JWT-Tokens. Keine gespeicherten Credentials, keine Rotation, keine manuelle Verwaltung. Der Token ist nur für die Dauer des Workflow-Runs gültig.
Terraform-Setup
# OIDC Provider: GitHub als Identity Provider registrieren
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [
"6938fd4d98bab03faadb97b34396831e3780aea1",
"1c58a3a8518e8759bf075b76b750d4f2df264fcd"
]
}
# IAM-Rolle, die GitHub Actions annehmen darf
resource "aws_iam_role" "github_actions" {
name = "github-actions-deploy"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.github.arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:your-org/your-repo:ref:refs/heads/main"
}
}
}
]
})
}Least-Privilege-Berechtigungen
Die Condition auf den main-Branch ist entscheidend. Ohne diese Einschränkung könnte jeder Branch, jeder Pull Request und jeder Fork in dein AWS-Konto deployen. In einem Team mit mehreren Entwicklern ist das ein Angriffsvektor, der leicht übersehen wird.
# Nur die minimal nötigen Berechtigungen
resource "aws_iam_role_policy" "deploy" {
name = "deploy-policy"
role = aws_iam_role.github_actions.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "ECRAuth"
Effect = "Allow"
Action = ["ecr:GetAuthorizationToken"]
Resource = "*"
},
{
Sid = "ECRPush"
Effect = "Allow"
Action = [
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
]
Resource = aws_ecr_repository.ki_api.arn
},
{
Sid = "ECSUpdate"
Effect = "Allow"
Action = [
"ecs:UpdateService",
"ecs:DescribeServices",
"ecs:RegisterTaskDefinition",
"ecs:DescribeTaskDefinition"
]
Resource = "*"
Condition = {
StringEquals = {
"ecs:cluster" = aws_ecs_cluster.ki_cluster.arn
}
}
},
{
Sid = "PassRole"
Effect = "Allow"
Action = ["iam:PassRole"]
Resource = [
aws_iam_role.ecs_task_role.arn,
aws_iam_role.ecs_execution_role.arn
]
}
]
})
}Der Unterschied zu einem Action = ["ecr:*", "ecs:*"] ist erheblich. Die Policy oben erlaubt exakt vier Aktionen: ECR-Login, Image-Push, ECS-Service-Update und das Weiterreichen der Task-Rollen. Kein Löschen von Repositories, kein Zugriff auf andere Cluster, keine Manipulation von IAM-Rollen.
Dockerfile für GPU-Workloads
FROM python:3.11-slim
WORKDIR /app
# System-Dependencies für ML-Libraries
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Dependencies zuerst (Docker Layer Cache)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Application Code
COPY . .
# Gunicorn mit GPU-optimierten Parametern
CMD ["gunicorn", "app:create_app()", \
"--bind", "0.0.0.0:8000", \
"--workers", "1", \
"--timeout", "120", \
"--max-requests", "1000", \
"--max-requests-jitter", "50"]Warum diese Parameter?
| Parameter | Wert | Begründung |
|---|---|---|
| workers | 1 | Ein GPU-Modell belegt den gesamten VRAM. Zwei Worker würden doppelten Speicher brauchen, den eine einzelne GPU nicht hat. Horizontale Skalierung erfolgt über ECS (mehr Container), nicht über mehr Worker. |
| timeout | 120 | RAG-Queries mit Retrieval, Reranking und Generation brauchen bei komplexen Dokumenten bis zu 30 Sekunden. Mit Sicherheitspuffer: 120 Sekunden. |
| max-requests | 1000 | Nach 1000 Requests wird der Worker neugestartet. Das verhindert Memory-Fragmentierung, die bei langlebigen Python-Prozessen mit ML-Libraries auftritt. PyTorch und Transformers allokieren und deallokieren GPU-Speicher nicht immer sauber. |
| max-requests-jitter | 50 | Verhindert, dass alle Worker gleichzeitig neustarten, wenn mehrere Container laufen. |
Warum python:3.11-slim statt eines CUDA-Base-Images? CUDA-Images sind 4 bis 6 GB groß. Wenn die GPU-Treiber auf dem Host installiert sind (was bei ECS GPU-Instances der Fall ist), reicht ein schlankes Python-Image. Die CUDA-Runtime wird über den --gpus-Flag beim Container-Start vom Host durchgereicht. Das reduziert die Image-Größe erheblich und beschleunigt Build und Push.
ECR: Image-Management mit Lifecycle-Policies
Repository mit Terraform
resource "aws_ecr_repository" "ki_api" {
name = "ki-rag-api"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
encryption_configuration {
encryption_type = "AES256"
}
}MUTABLE Tags erlauben es, das latest-Tag bei jedem Push zu überschreiben. Für On-Demand Tasks (Batch-Verarbeitung, Ingestion) ist das wichtig: Der Task referenziert immer latest und bekommt automatisch die neueste Version.
Lifecycle-Policy
GPU-Images sind groß. Ohne Lifecycle-Policy sammeln sich schnell Hunderte Gigabyte an veralteten Images an:
resource "aws_ecr_lifecycle_policy" "ki_api" {
repository = aws_ecr_repository.ki_api.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Untagged Images nach 7 Tagen entfernen"
selection = {
tagStatus = "untagged"
countType = "sinceImagePushed"
countUnit = "days"
countNumber = 7
}
action = {
type = "expire"
}
},
{
rulePriority = 2
description = "Maximal 10 tagged Images behalten"
selection = {
tagStatus = "tagged"
tagPatternList = ["*"]
countType = "imageCountMoreThan"
countNumber = 10
}
action = {
type = "expire"
}
}
]
})
}Bei einem 4 GB Image und täglichen Deployments spart diese Policy ca. 120 GB pro Monat. Das klingt wenig, aber ECR berechnet $0.10 pro GB pro Monat. Ohne Policy wächst der Speicher linear und kostet nach einem Jahr über $500 pro Repository.
ECS Rolling Deployment mit Circuit Breaker
Service-Definition
resource "aws_ecs_service" "ki_api" {
name = "ki-rag-api"
cluster = aws_ecs_cluster.ki_cluster.id
task_definition = aws_ecs_task_definition.ki_api.arn
desired_count = 2
launch_type = "EC2"
deployment_minimum_healthy_percent = 100
deployment_maximum_percent = 200
deployment_circuit_breaker {
enable = true
rollback = true
}
load_balancer {
target_group_arn = aws_lb_target_group.ki_api.arn
container_name = "ki-rag-api"
container_port = 8000
}
ordered_placement_strategy {
type = "spread"
field = "attribute:ecs.availability-zone"
}
}Was bei einem Deployment passiert
- Neue Task-Definition registrieren: ECS erstellt eine neue Revision mit dem aktualisierten Image-Tag
- Neue Tasks starten: ECS startet neue Tasks parallel zu den bestehenden (
maximum_percent = 200erlaubt doppelte Kapazität) - Health Check abwarten: Die neuen Tasks durchlaufen den Health Check (bis zu 120 Sekunden)
- Traffic umleiten: Der ALB routet Traffic erst nach erfolgreichem Health Check an die neuen Tasks
- Alte Tasks stoppen: Die alten Tasks werden erst gestoppt, nachdem die neuen gesund sind
deployment_minimum_healthy_percent = 100 bedeutet: Es gibt zu keinem Zeitpunkt weniger gesunde Tasks als die gewünschte Anzahl. Kein Benutzer erlebt einen Ausfall während des Deployments.
Circuit Breaker: Wenn drei aufeinanderfolgende Tasks den Health Check nicht bestehen, stoppt ECS das Deployment und rollt automatisch auf die vorherige Task-Definition zurück. Ohne Circuit Breaker würde ECS endlos neue Tasks starten und terminieren, was GPU-Kosten verursacht, ohne Fortschritt zu machen.
Health Checks für GPU-Services
Der Health Check ist die kritischste Komponente bei GPU-Deployments. Ein Standard-Health-Check (GET /health mit 30s Timeout) versagt hier, weil er nicht unterscheiden kann zwischen "Container startet noch" und "Container ist kaputt".
Application-Level Health Check
from fastapi import FastAPI
import torch
app = FastAPI()
models_loaded = False
@app.on_event("startup")
async def load_models():
global models_loaded
# Modelle in GPU-Speicher laden
# Embedding-Modell, Reranker, etc.
models_loaded = True
@app.get("/health")
async def health_check():
checks = {
"gpu_available": torch.cuda.is_available(),
"gpu_memory_allocated": torch.cuda.memory_allocated() > 0,
"models_loaded": models_loaded,
}
if all(checks.values()):
return {"status": "healthy", "checks": checks}
return {"status": "unhealthy", "checks": checks}, 503Dieser Endpoint prüft drei Dinge: Ist eine GPU verfügbar? Wird GPU-Speicher genutzt (Modelle geladen)? Hat der Startup-Prozess alle Modelle erfolgreich geladen? Erst wenn alle drei Bedingungen erfüllt sind, meldet der Container "healthy".
ECS Task Definition mit startPeriod
resource "aws_ecs_task_definition" "ki_api" {
family = "ki-rag-api"
requires_compatibilities = ["EC2"]
network_mode = "bridge"
execution_role_arn = aws_iam_role.ecs_execution_role.arn
task_role_arn = aws_iam_role.ecs_task_role.arn
container_definitions = jsonencode([
{
name = "ki-rag-api"
image = "${aws_ecr_repository.ki_api.repository_url}:latest"
cpu = 2048
memory = 8192
essential = true
portMappings = [
{
containerPort = 8000
hostPort = 0
protocol = "tcp"
}
]
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]
interval = 15
timeout = 10
retries = 8
startPeriod = 120
}
resourceRequirements = [
{
type = "GPU"
value = "1"
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/ki-rag-api"
"awslogs-region" = var.aws_region
"awslogs-stream-prefix" = "ecs"
}
}
secrets = [
{
name = "OPENAI_API_KEY"
valueFrom = "${aws_secretsmanager_secret.openai_key.arn}"
}
]
}
])
}startPeriod = 120 gibt dem Container 120 Sekunden Zeit, bevor der erste Health Check zählt. Während dieser Grace Period werden fehlgeschlagene Health Checks ignoriert. Das ist essentiell für GPU-Container, die Modelle laden müssen. Ohne startPeriod würde ECS den Container nach wenigen fehlgeschlagenen Checks als ungesund markieren und terminieren, obwohl er noch startet.
ALB Slow Start
resource "aws_lb_target_group" "ki_api" {
name = "ki-rag-api"
port = 8000
protocol = "HTTP"
vpc_id = var.vpc_id
health_check {
path = "/health"
healthy_threshold = 2
unhealthy_threshold = 5
timeout = 10
interval = 15
matcher = "200"
}
slow_start = 60
stickiness {
type = "lb_cookie"
enabled = false
}
}slow_start = 60 bedeutet: Nachdem ein Target als gesund registriert wird, erhält es 60 Sekunden lang linear steigenden Traffic. Statt sofort 50% der Last zu bekommen, startet es bei nahezu 0% und wird langsam hochgefahren. Das gibt dem GPU-Container Zeit, CUDA-Kernels zu kompilieren und Caches aufzuwärmen. Ohne Slow Start kann der erste Schwung an Requests zu Timeouts führen, weil die initiale GPU-Inferenz langsamer ist als nachfolgende.
Doppelte Absicherung: ECS Health Checks schützen auf Container-Ebene (startet der Container überhaupt?), ALB Slow Start schützt auf Traffic-Ebene (bekommt der Container zu viel Last, bevor er bereit ist?). Beide Mechanismen sind unabhängig und ergänzen sich.
Zwei Deployment-Patterns: Service vs. Task
Nicht jeder GPU-Workload braucht ein Rolling Deployment. Die Wahl des Patterns hängt vom Anwendungsfall ab:
| Aspekt | ECS Service (API) | ECS RunTask (Batch) |
|---|---|---|
| Verfügbarkeit | Always-On (24/7) | On-Demand |
| Deployment | Rolling Update + Circuit Breaker | Kein Deployment nötig |
| Image-Tag | Commit-SHA (reproduzierbar) | latest (immer aktuell) |
| Trigger | ecs:UpdateService | EventBridge Schedule / Webhook |
| Skalierung | desired_count anpassen | Parallele Tasks starten |
| Kosten | Kontinuierlich (GPU reserviert) | Nur bei Ausführung |
API-Service: Für die RAG-API, die Anfragen in Echtzeit beantwortet. Rolling Update mit Circuit Breaker stellt sicher, dass kein Benutzer einen Ausfall erlebt. Das Image wird über den Commit-SHA referenziert, damit exakt nachvollziehbar ist, welcher Code läuft.
Batch-Task: Für die Ingestion-Pipeline, die Dokumente verarbeitet und in die Vektordatenbank schreibt. Der Task referenziert latest und bekommt bei jedem Start automatisch die neueste Version. Kein explizites Deployment nötig. Der Task wird per EventBridge-Schedule (z.B. täglich um 2:00 Uhr) oder per Webhook (neues Dokument hochgeladen) gestartet.
Die komplette GitHub Actions Pipeline
name: Deploy KI-API
on:
push:
branches: [main]
permissions:
id-token: write # OIDC Token anfordern
contents: read # Repository auschecken
env:
AWS_REGION: eu-central-1
ECR_REPOSITORY: ki-rag-api
ECS_CLUSTER: ki-cluster
ECS_SERVICE: ki-rag-api
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Build, Tag, Push
env:
ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
$ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
- name: Update ECS Task Definition
id: task-def
env:
ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
TASK_DEF=$(aws ecs describe-task-definition \
--task-definition ki-rag-api \
--query 'taskDefinition' \
--output json)
NEW_TASK_DEF=$(echo $TASK_DEF | jq \
--arg IMAGE "$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" \
'.containerDefinitions[0].image = $IMAGE |
del(.taskDefinitionArn, .revision, .status,
.requiresAttributes, .compatibilities,
.registeredAt, .registeredBy)')
NEW_ARN=$(aws ecs register-task-definition \
--cli-input-json "$NEW_TASK_DEF" \
--query 'taskDefinition.taskDefinitionArn' \
--output text)
echo "task_def_arn=$NEW_ARN" >> $GITHUB_OUTPUT
- name: Deploy to ECS
run: |
aws ecs update-service \
--cluster $ECS_CLUSTER \
--service $ECS_SERVICE \
--task-definition ${{ steps.task-def.outputs.task_def_arn }} \
--force-new-deployment
- name: Wait for Deployment
run: |
aws ecs wait services-stable \
--cluster $ECS_CLUSTER \
--services $ECS_SERVICEDie zwei Zeilen unter permissions sind das Herzstück der OIDC-Integration. id-token: write erlaubt GitHub Actions, einen JWT-Token beim OIDC-Provider anzufordern. contents: read wird für den Checkout benötigt. Ohne diese Permissions-Deklaration schlägt die OIDC-Authentifizierung fehl.
Der Workflow baut das Image, taggt es mit Commit-SHA und latest, pusht beide Tags, registriert eine neue Task-Definition mit dem SHA-Tag und aktualisiert den Service. aws ecs wait services-stable wartet, bis das Rolling Deployment abgeschlossen ist. Wenn der Circuit Breaker auslöst, schlägt dieser Schritt fehl und der Workflow wird als fehlgeschlagen markiert.
Secrets-Management
Secrets gehören nicht in Umgebungsvariablen und schon gar nicht in den Terraform-State. AWS Secrets Manager bietet eine saubere Lösung:
# Secret erstellen (Wert wird manuell oder per CLI gesetzt)
resource "aws_secretsmanager_secret" "openai_key" {
name = "ki-rag-api/openai-api-key"
}
# ECS Execution Role darf Secrets lesen
resource "aws_iam_role_policy" "execution_secrets" {
name = "secrets-access"
role = aws_iam_role.ecs_execution_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = aws_secretsmanager_secret.openai_key.arn
}
]
})
}Der Ablauf ist: ECS startet den Container. Die Execution Role holt den Secret-Wert aus Secrets Manager und injiziert ihn als Umgebungsvariable in den Container. Der Secret-Wert erscheint weder im Terraform-State noch in den Container-Logs noch im GitHub Actions Log. Die einzige Stelle, an der der Wert existiert, ist im laufenden Container-Prozess.
Rollback-Strategien
Drei Ebenen von Rollback, je nach Situation:
Automatisch (Circuit Breaker): ECS erkennt, dass die neuen Tasks nicht gesund werden, und rollt auf die vorherige Task-Definition zurück. Kein manuelles Eingreifen nötig. Das passiert typischerweise bei fehlerhaftem Code oder inkompatiblen Model-Versionen.
Manuell (Task-Definition-Revision): Jedes Deployment erstellt eine neue Revision. Wenn du auf eine bestimmte Version zurückrollen willst:
# Aktuelle Revisionen anzeigen
aws ecs list-task-definitions \
--family-prefix ki-rag-api \
--sort DESC \
--max-items 5
# Auf eine bestimmte Revision zurückrollen
aws ecs update-service \
--cluster ki-cluster \
--service ki-rag-api \
--task-definition ki-rag-api:42 \
--force-new-deploymentImage-Level (Commit-SHA): Da jedes Image mit dem Commit-SHA getaggt wird, kannst du exakt nachvollziehen, welcher Code in welcher Revision läuft. Das macht Debugging bei Produktionsproblemen deutlich einfacher als ein generisches latest-Tag.
| Rollback-Typ | Trigger | Dauer | Eingriff |
|---|---|---|---|
| Circuit Breaker | Automatisch bei 3x Failure | 2 bis 5 Minuten | Keiner |
| Task-Definition | Manuell per CLI | 3 bis 5 Minuten | Ein Befehl |
| Commit-SHA | Manuell per Pipeline | 5 bis 10 Minuten | Git revert + Push |
Fazit
GPU-basierte KI-Systeme erfordern drei grundlegende Anpassungen gegenüber klassischen CI/CD-Pipelines:
-
Zeitliche Anpassung: Health Checks, Grace Periods und Slow Start müssen auf GPU Cold Starts ausgelegt sein. 120 Sekunden statt 30, 60 Sekunden Slow Start statt sofortige Last.
-
Sicherheitsnetz: Der Circuit Breaker fängt fehlgeschlagene Deployments automatisch ab. In Kombination mit ALB Slow Start entsteht ein doppeltes Sicherheitsnetz, das GPU-Kosten bei fehlerhaften Releases minimiert.
-
Image-Management: Lifecycle-Policies und eine klare Tagging-Strategie (Commit-SHA + latest) verhindern Kostenwachstum und ermöglichen präzises Rollback.
Die OIDC-Authentifizierung und Least-Privilege-Policies sind keine GPU-spezifischen Themen, aber sie bilden das Fundament für eine sichere Pipeline. Wer Serverless-Deployments auf AWS kennt, wird die Parallelen bei IAM-Rollen und Secrets-Management erkennen.
Im nächsten Artikel gehen wir einen Schritt weiter: die komplette RAG-Infrastruktur auf AWS mit GPU-Clustern, Auto-Scaling und Terraform-Modulen. Wenn du bereits an RAG-Evaluation und Testing arbeitest, schließt die Infrastruktur den Kreis von der Entwicklung über das Testing bis zum produktionsreifen Betrieb.
Sie deployen GPU-basierte KI-Systeme auf AWS und brauchen Unterstützung bei Architektur oder CI/CD? Kontaktieren Sie mich für eine unverbindliche Beratung.