top of page

Så kör du en egen AI-gateway på Kubernetes för self-hosted LLM:er

Just nu pågår en allt tydligare diskussion i Europa om AI-suveränitet och det finns goda skäl till det. När du skickar prompts till en hostad AI-tjänst lämnar din data ditt nätverk, passerar genom någon annans infrastruktur och behandlas enligt villkor du kanske inte har läst tillräckligt noggrant. I många situationer är det helt okej. I andra är det inte det.



Det här inlägget kommer inte att säga att du ska skrota moln-AI helt. Hostade modeller är smidiga, snabba och, ärligt talat, imponerande. Men om du jobbar med känslig information, verkar i en reglerad bransch eller helt enkelt vill ha full kontroll över vad som körs i din egen miljö, är det värt att förstå hur en egen AI-gateway fungerar.


I praktiken handlar det om att bygga en privat AI-infrastruktur, där språkmodeller körs i din egen miljö i stället för att anropas som externa tjänster. Vi går igenom exakt hur du gör det, från en lokal uppsättning till en produktionsdriftsättning på Kubernetes.


Vad vi ska sätta upp

Kärnan i lösningen är llama.cpp, närmare bestämt dess serverbinär llama-server. Den kan ladda kvantiserade språkmodeller och exponera ett OpenAI-kompatibelt API på valfri port.


Det är viktigt: allt som redan fungerar mot OpenAI-API:et fungerar här också, utan att du behöver ändra en enda rad kod. Du pekar bara klienten mot en annan base URL.

Med andra ord kör du i praktiken ditt eget self-hosted OpenAI-kompatibla API i din egen infrastruktur.


Dessutom kommer llama-server med ett inbyggt webbgränssnitt på samma port. Du får ett chattgränssnitt direkt, utan extra tjänster.


En endpoint, flera modeller, noll leverantörsberoende.


En notering om hårdvara och förväntningar

Var realistisk. Att köra stora språkmodeller lokalt kräver rejäl hårdvara. De modeller som ger verkligt bra resultat ligger ofta i spannet 14B till 35B parametrar och de kräver antingen en kapabel GPU, mycket RAM eller båda.


Samtidigt har kvantiserade modeller blivit riktigt bra. En Q4-kvantiserad 35B-modell kan fungera fint på ett konsument-GPU med 24 GB VRAM och den kan även köras på CPU med tillräckligt mycket RAM, bara långsammare. Poängen är inte att alla ska göra detta, utan att det är mer tillgängligt än det var tidigare.


För organisationer som tittar på on-prem LLM-drift eller säker AI-infrastruktur gör det här upplägget det möjligt att köra kraftfulla modeller utan att data behöver lämna det egna nätverket.


När det gäller kvalitet: lokala modeller är bra. Inte perfekta, och inte alltid i nivå med de allra senaste hostade modellerna, men absolut användbara när du matchar rätt modell mot rätt uppgift. En kodassistent, en sammanfattare eller en pipeline för strukturerad dataextraktion fungerar bra med rätt modell och rätt konfiguration.


Modellrekommendationer

Vilken modell du ska välja beror på vad du vill göra och vilken hårdvara du har. Här är en praktisk genomgång.


En ärlig startpunkt: Qwen3.5

Om du är osäker på var du ska börja: börja med Qwen3.5. Det är en modellfamilj som täcker det mesta – resonemang, vision och tool calling – i storlekar från 0.8B upp till 397B.


MoE-varianterna (Mixture of Experts) aktiverar bara en del av parametrarna per inferens, vilket gör dem snabbare och mer minneseffektiva än parameterantalet antyder. Fullständiga detaljer och rekommenderade inställningar finns i Unsloth Qwen3.5-dokumentationen, som är värd att läsa innan du börjar ladda ner modeller.


Här är vad du faktiskt behöver i minne beroende på storlek och kvantisering (minnet avser totalen: VRAM + systemminne, eller unified memory på Apple Silicon):

Model size

4-bit

6-bit

8-bit

0.8B + 2B

3.5 GB

5 GB

7.5 GB

4B

5.5 GB

7 GB

10 GB

9B

6.5 GB

9 GB

13 GB

27B

17 GB

24 GB

30 GB

35B-A3B

22 GB

30 GB

38 GB

122B-A10B

70 GB

106 GB

132 GB


35B-A3B är en bra sweet spot för de flesta mer krävande användningsområden. I 4-bitarsformat kräver den omkring 22 GB totalt minne, får plats på ett enda GPU med 24 GB VRAM och levererar riktigt starka resultat.


Den täta 27B-modellen är något mer exakt men också långsammare. 9B- och 4B-modellerna är bra alternativ när hårdvaran är den begränsande faktorn.


Thinking mode

Qwen3.5 är en hybridmodell: den kan antingen “tänka igenom” ett problem steg för steg innan den svarar, eller svara direkt. För större storlekar (27B och uppåt) är thinking-läget på som standard. För mindre (0.8B, 2B, 4B, 9B) är det av som standard. Du styr detta per request eller globalt med en flagga:


--chat-template-kwargs '{"enable_thinking":true}'   # enable

--chat-template-kwargs '{"enable_thinking":false}'  # disable


Thinking-läge höjer kvaliteten på svårare uppgifter, men ökar tokenförbrukningen och svarstiden tydligt. För exempelvis en kodassistent eller en pipeline för strukturerad extraktion är det ofta rimligt att ha det av om du inte faktiskt behöver resonemanget.


Rekommenderade parameterinställningar


De rätta parametrarna beror både på modellstorleken och på om thinking-läget är aktiverat. För thinking-läge rekommenderar Unsloth:


Parameter

General tasks

Precise coding

temperature

1.0

0.6

top_p

0.95

0.95

top_k

20

20

min_p

0.0

0.0

presence_penalty

1.5

0.0

repeat_penalty

1.0

1.0


För non-thinking (instruct) mode:

 

Parameter

General tasks

Reasoning tasks

temperature

0.7

1.0

top_p

0.8

0.95

top_k

20

20

min_p

0.0

0.0

presence_penalty

1.5

1.5

repeat_penalty

1.0

1.0

Det maximala kontextfönstret är 262 144 tokens och kan vid behov utökas ytterligare via YaRN. För de flesta arbetslaster räcker 32 768 tokens i outputlängd mer än väl.


Vision-uppgifter

Modeller som stöder bildinmatning levereras med en separat mmproj-fil tillsammans med huvudfilen i GGUF-format. Båda monteras i din konfiguration. Qwen3.5 35B A3B stöder vision på detta sätt.


Om kvantisering

Q4_K_XL är ett stabilt standardval. Det ger en bra balans mellan storlek, minnesanvändning och kvalitet på modellens svar.


Går du under Q4 börjar kvaliteten försämras märkbart. Q8 ligger nära full precision men använder ungefär dubbelt så mycket minne.


Var du hittar modeller

Hugging Face är den huvudsakliga källan. Du ska använda filer i GGUF-format, vilket är det format llama.cpp laddar direkt.


Unsloth producerar särskilt väloptimerade GGUF-kvantiseringar som är mindre, snabbare och noggrant justerade. Det är värt att titta där innan du laddar ner en slumpmässig modell.


Metoden med konfigurationsfil

I stället för att köra en container per modell eller hantera flera olika kommandon stöder llama-server en konfigurationsfil där alla modeller definieras på ett ställe. Servern laddar dem och exponerar varje modell som en namngiven endpoint på samma port.


En sak som är bra att känna till: du kan definiera samma modell flera gånger under olika namn med olika parameterinställningar. Det gör det möjligt att exponera samma modell både i thinking-läge och icke-thinking-läge, eller med olika temperaturprofiler för olika användningsområden, utan att ladda modellen två gånger.


Här är ett exempel på en konfiguration som täcker några praktiska scenarier:


; Qwen3.5 35B A3B: thinking mode, general tasks

[qwen3.5-35b-thinking]

model = /models/qwen3.5-35b-a3b/Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf

mmproj = /models/qwen3.5-35b-a3b/mmproj-F16.gguf

ngl = 99

ctx-size = 131072

temp = 1.0

top-p = 0.95

top-k = 20

min-p = 0.0

presence-penalty = 1.5

repeat-penalty = 1.0

flash-attn = on

chat-template-kwargs = {"enable_thinking":true}

 

; Qwen3.5 35B A3B: non-thinking mode, precise coding

[qwen3.5-35b-coding]

model = /models/qwen3.5-35b-a3b/Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf

mmproj = /models/qwen3.5-35b-a3b/mmproj-F16.gguf

ngl = 99

ctx-size = 131072

temp = 0.6

top-p = 0.95

top-k = 20

min-p = 0.0

presence-penalty = 0.0

repeat-penalty = 1.0

flash-attn = on

chat-template-kwargs = {"enable_thinking":false}

 

; Qwen3.5 9B: lightweight, non-thinking, general use

[qwen3.5-9b]

model = /models/qwen3.5-9b/Qwen3.5-9B-UD-Q4_K_XL.gguf

ngl = 99

ctx-size = 32768

temp = 0.7

top-p = 0.8

top-k = 20

min-p = 0.0

presence-penalty = 1.5

repeat-penalty = 1.0

flash-attn = on

chat-template-kwargs = {"enable_thinking":false}


Några saker som är bra att känna till

  • ngl är antalet lager som flyttas över till GPU:n. Sätt värdet till 99 för att flytta över allt som får plats.

  • ctx-size är kontextfönstret i tokens. Ett större värde gör att modellen kan hantera längre konversationer eller dokument, men kräver mer minne.

  • flash-attn = on aktiverar Flash Attention, vilket minskar minnesanvändningen och snabbar upp inferensen.

  • mmproj behövs bara för modeller som stöder bildinmatning. Den pekar på den multimodala projektorfilen som levereras tillsammans med modellen.

  • chat-template-kwargs används för att skicka inställningar för thinking-läge och andra mallrelaterade parametrar för varje modell.


Var modellfilerna ligger

llama-server läser modellfiler från disk vid uppstart och laddar dem i minnet. Det innebär att filerna måste vara tillgängliga i filsystemet som containern ser, som en monterad sökväg.


De behöver dock inte ligga på samma fysiska maskin som noden. Så länge volymen är monterad i podden och är läsbar behandlar llama-server den som en lokal sökväg.


I praktiken betyder det att du kan lagra modellfiler på en dedikerad lagringsserver eller en nätverksdelning och montera dem i podden via NFS, en distribuerad lagringslösning eller någon annan nätverksbaserad volym.


Podden märker ingen skillnad och det gör inte llama-server heller. Detta beskrivs mer i detalj i Kubernetes-delen nedan.


Kör lokalt först

Innan du deployar något är det värt att köra servern lokalt för att få en känsla för hur den fungerar.


Installationen beror helt på vilken hårdvara du har. CPU-only, NVIDIA CUDA, AMD ROCm eller Apple Silicon med Metal har alla sina egna byggsteg.


I stället för att duplicera dokumentation som redan finns är det bättre att gå till llama.cpp build-dokumentationen och följa instruktionerna för din specifika miljö. Det är värt att läsa ordentligt i stället för att bara skumma igenom rätt build för din hårdvara är skillnaden mellan snabb inferens och mycket långsam inferens.


När du har byggt eller installerat llama-server pekar du den mot din konfigurationsfil:


llama-server --port 8080 --models-preset ./config.ini


När servern startar öppnar du http://localhost:8080 i webbläsaren. Där ser du det inbyggda chattgränssnittet där du kan välja vilken modell som ska användas och börja direkt.


Samma port exponerar också det OpenAI-kompatibla API:et på:

/v1/chat/completions

Docker

De officiella llama.cpp-Docker images publiceras på ghcr.io/ggml-org/llama.cpp. För vårt syfte är server image den vi vill ha. Det finns varianter för CUDA (NVIDIA), ROCm (AMD), Vulkan och Intel SYCL beroende på din hårdvara.


En enkel Docker Compose-setup kan se ut så här:


services:

  llama-server:

    ports:

      - "8080:8080"

    volumes:

      - /path/to/models:/models

      - ./config.ini:/config/config.ini

    command:

      - --port

      - "8080"

      - --host

      - "0.0.0.0"

      - --models-preset

      - /config/config.ini

    deploy:

      resources:

        reservations:

          devices:

            - driver: nvidia

              count: all

              capabilities: [gpu]


Om du kör CPU-only, använd den vanliga server image och ta bort deploy.resources-blocket. Flaggan --host 0.0.0.0 är viktig om du vill att servern ska gå att nå utanför containern.


Deploying on Kubernetes

För en produktionsdrift följer här råa Kubernetes manifests för att få gatewayen att köra.


Att köra LLM inference on Kubernetes på det här sättet blir ett allt vanligare mönster för organisationer som bygger interna AI-plattformar eller private AI infrastructure.

De är tillräckligt raka för att förstå och anpassa, och fungerar som en stabil startpunkt om du senare vill paketera dem i ett Helm chart eller en Kustomize overlay.


Du behöver ett fungerande kluster och kubectl konfigurerat. För GPU-stöd behöver du också NVIDIA device plugininstallerat i klustret. Det är det som gör nvidia.com/gpu till en schemaläggningsbar resurs i Kubernetes.


I praktiken fungerar den här setupen som en intern AI platform endpoint som applikationer kan anropa via ett self-hosted OpenAI-compatible API.


Utan pluginet kommer GPU resource request i Deployment inte att matcha och podden kommer inte att schemaläggas. Om du kör CPU-only kan du ta bort resursblocket helt.


Namespace

Börja med ett dedikerat namespace för att hålla det snyggt:

kubectl create namespace ai


ConfigMap

Model config monteras i podden som en ConfigMap. Det håller den separat från container image och gör den enkel att uppdatera utan att bygga om något:


# configmap.yaml

apiVersion: v1

kind: ConfigMap

metadata:

  name: llama-config

  namespace: ai

data:

  config.ini: |

    ; Qwen3.5 35B A3B: thinking mode, general tasks

    [qwen3.5-35b-thinking]

    model = /models/qwen3.5-35b-a3b/Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf

    mmproj = /models/qwen3.5-35b-a3b/mmproj-F16.gguf

    ngl = 99

    ctx-size = 131072

    temp = 1.0

    top-p = 0.95

    top-k = 20

    min-p = 0.0

    presence-penalty = 1.5

    flash-attn = on

    chat-template-kwargs = {"enable_thinking":true}

 

    ; Qwen3.5 35B A3B: non-thinking mode, precise coding

    [qwen3.5-35b-coding]

    model = /models/qwen3.5-35b-a3b/Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf

    mmproj = /models/qwen3.5-35b-a3b/mmproj-F16.gguf

    ngl = 99

    ctx-size = 131072

    temp = 0.6

    top-p = 0.95

    top-k = 20

    min-p = 0.0

    presence-penalty = 0.0

    flash-attn = on

    chat-template-kwargs = {"enable_thinking":false}


Deployment

# deployment.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

  name: llama-gateway

  namespace: ai

spec:

  replicas: 1

  selector:

    matchLabels:

      app: llama-gateway

  template:

    metadata:

      labels:

        app: llama-gateway

    spec:

      containers:

        - name: llama-server

          image: ghcr.io/ggml-org/llama.cpp:server-cuda

          args:

            - --port

            - "8080"

            - --host

            - "0.0.0.0"

            - --models-preset

            - /config/config.ini

          ports:

            - containerPort: 8080

          resources:

            requests:

              nvidia.com/gpu: 1

            limits:

              nvidia.com/gpu: 1

          volumeMounts:

            - name: config

              mountPath: /config

            - name: models

              mountPath: /models

      volumes:

        - name: config

          configMap:

            name: llama-config

        - name: models

          # hostPath is the simplest option for single-node setups.

          # Replace with a PVC backed by your StorageClass for anything beyond that.

          # (NFS provisioner, Longhorn, Rook/Ceph, cloud block storage, etc.)

          hostPath:

            path: /data/models

            type: Directory

      # Adjust this label to match what your cluster actually uses.

      # Remove entirely if running CPU only.

      nodeSelector:

        nvidia.com/gpu: "true"


hostPath-volymen är den enklaste startpunkten, men för riktiga driftsättningar vill du normalt ha en PersistentVolumeClaim backad av en riktig StorageClass: en NFS provisioner, Longhorn, Rook/Ceph eller det som klustret redan använder.


Modellfiler är stora men read-only vid körning, vilket gör dem lämpliga för ReadOnlyMany om din storage backend stödjer det.


nodeSelector ser till att podden bara hamnar på en nod som faktiskt har en GPU.

Justera labeln så den matchar det ni använder i klustret.


Service


# service.yaml

apiVersion: v1

kind: Service

metadata:

  name: llama-gateway

  namespace: ai

spec:

  selector:

    app: llama-gateway

  ports:

    - port: 8080

      targetPort: 8080

  type: ClusterIP


Ingress (optional)

Om du vill kunna nå gatewayen internt i nätverket utan port-forwarding kan du lägga till en Ingress.


Detta förutsätter att du har en ingress controller, till exempel ingress-nginx, som kör:

# ingress.yaml

kind: Ingress

metadata:

  name: llama-gateway

  namespace: ai

spec:

  ingressClassName: nginx

  rules:

    - host: llm.your-domain.internal

      http:

        paths:

          - path: /

            pathType: Prefix

            backend:

              service:

                name: llama-gateway

                port:

                  number: 8080


Applicera konfigurationen


kubectl apply -f configmap.yaml

kubectl apply -f deployment.yaml

kubectl apply -f service.yaml

kubectl apply -f ingress.yaml  # if using ingress


Kontrollera att allt kör

kubectl get pods -n ai

kubectl logs -n ai deployment/llama-gateway


Följ loggarna när servern startar. När du ser att den lyssnar är gatewayen uppe.


Du kan då antingen nå den via Ingress host eller port-forwarda för att testa lokalt:

kubectl port-forward -n ai svc/llama-gateway 8080:8080


Öppna sedan http://localhost:8080 för chat UI, eller använd http://localhost:8080/v1/chat/completions som din API endpoint.


Härifrån är de här manifesten en naturlig startpunkt för ett Helm chart eller en Kustomize overlay om du vill hantera flera miljöer eller göra konfigurationen mer återanvändbar.


Vad du kan använda det till

När gatewayen kör kan allt som använder OpenAI API format prata med den. Du ändrar bara base URL och kan (om du vill) sätta en dummy API key (llama-server kräver ingen som standard, men vissa klienter insisterar på att skicka en).


Agentic coding

Verktyg som OpenCode, Cursor och Continue stödjer custom OpenAI-compatible endpoints. Peka dem mot din gateway, välj din coding model, och du har en helt lokal coding assistant utan token costs och utan att data lämnar ditt nätverk.


Chat interfaces

Open WebUI ansluter direkt till din endpoint och ger ett polerat chat interface med conversation history, model switching och mer, om du vill ha något utöver built-in UI.


Applications

Om du bygger något internt, oavsett om det är en document processor, en support bot eller en data extraction pipeline, kan du använda OpenAI SDK och peka den mot din egen endpoint. Bytet är en rad i konfigurationen.


Automation and agents

Frameworks som LangChain, LlamaIndex och AutoGen stödjer alla custom base URLs. Dina interna agents kan köras helt i din egen infrastruktur.


Wrapping Up

Den här setupen är inte för alla. Du behöver hårdvara, du behöver hantera model files och du behöver hålla saker uppdaterade. Modellerna är bra, men de är inte magi, och att matcha rätt modell mot rätt uppgift kräver en del experimenterande.


Men om du vill ha kontroll, om du hanterar data som inte bör lämna din miljö, eller om du bara är nyfiken på hur det faktiskt ser ut att köra en egen AI gateway i praktiken, är det här ett stabilt, produktionsredo upplägg.


För team som bygger enterprise AI platforms eller secure AI infrastructure ger en egen gateway ett praktiskt sätt att deploya LLMs on Kubernetes utan att introducera externa beroenden.


OpenAI compatibility betyder att du inte låser in dig i något nytt – du ändrar bara var anropen går.


Modellerna utvecklas snabbt och tooling runt llama.cpp har mognat rejält. Om hårdvaran finns och behovet passar är det här ett produktionsklart alternativ redan idag, inte ett science project.


Om du tittar på liknande upplägg och behöver hjälp att designa eller deploya en private AI infrastructure arbetar våra konsulter med Kubernetes platforms, LLM deployments och secure AI environments.


Hör av dig om du vill diskutera hur det skulle kunna se ut i er miljö.



Kommentarer


bottom of page