3-15X snellere Docker image builds met TrueFoundry op Kubernetes

Gebouwd voor snelheid: ~10ms latentie, zelfs onder belasting
De razendsnelle manier om je modellen te bouwen, volgen en implementeren!
- Verwerkt 350+ RPS op slechts 1 vCPU — geen tuning nodig
- Productieklaar met volledige enterprise-ondersteuning
In de wereld van Machine Learning (ML) is het efficiënt bouwen van Docker images geen luxe, maar een noodzaak. De meeste bedrijven hebben een bestaande DevOps-pipeline voor het bouwen en pushen van Docker images op een lokale laptop of via CI/CD-pipelines. Naarmate ML-projecten echter complexer worden, met grotere afhankelijkheden en frequentere iteraties, kan het traditionele Docker-buildproces een aanzienlijke bottleneck worden.
.png)
Dit artikel belicht hoe we de buildtijd in TrueFoundry met 5-15X hebben verkort vergeleken met standaard CI-pipelines.
Waarom snelle Docker-builds belangrijk zijn voor ML
Grote afhankelijkheden
ML-projecten omvatten doorgaans tal van zware afhankelijkheden: deep learning-frameworks (PyTorch, TensorFlow), wetenschappelijke bibliotheken (NumPy, SciPy), GPU-drivers en CUDA-toolkits. Deze afhankelijkheden kunnen Docker images meerdere gigabytes groot maken, wat leidt tot lange buildtijden.
Snelle iteratiecycli
ML-ontwikkeling vereist frequente code-wijzigingen die moeten worden geployd om ze te testen. Data scientists beschikken vaak niet over de hardware om hun code lokaal op hun laptopt uit te voeren. Dit betekent dat de code op een remote cluster moet draaien, wat vaak het bouwen van images omvat.
- Frequente code-wijzigingen voor modelverbeteringen
- Regelmatige updates van afhankelijkheden
- Continu experimenteren met verschillende modelarchitecturen - A/B-testen met diverse configuraties
Bij TrueFoundry is ons doel om ontwikkelaars op een snel iteratietempo te laten werken. Daarvoor wilden we onze Docker-builds echt razendsnel maken. Om te begrijpen wat we hebben gedaan om de buildtijden te optimaliseren, kijken we eerst naar hoe we voorheen images bouwden op TrueFoundry.

Telkens wanneer een ontwikkelaar een image wilde bouwen, werd de code geüpload naar het control plane, waar een nieuwe pod startte om de image te bouwen met BuildKit draaiend in een sidecar. De doel-Docker registry diende als cachinglaag en de definitieve image werd naar de Docker registry gepusht.
Deze opzet is identiek aan de meeste CI-builders en kent dezelfde voor- en nadelen als huidige CI-setups
Dit had de volgende voordelen:
- Er kan een onbeperkt aantal parallelle builds tegelijk worden ondersteund, omdat het systeem oneindig kan schalen en alle builds als individuele pods draaien.
- De BuildKit van elke afzonderlijke build is geïsoleerd en beïnvloedt dus geen andere builds.
- BuildKit biedt geavanceerde parallelle verwerking en geavanceerde cachingfunctionaliteiten.
Er waren echter een paar nadelen aan deze aanpak:
1. De BuildKit-pod vereist een grote hoeveelheid resources, wat leidt tot een lange opstarttijd voor de build runner.
2. Het kost veel tijd om de cache te downloaden van de Docker registry, wat leidt tot trage buildtijden.
3. De cache wordt niet hergebruikt tussen builds van verschillende workloads.
We wilden dezelfde (en wellicht betere) ervaring bieden bij het op afstand bouwen van images vergeleken met lokale builds
Het Docker image buildproces sneller maken
We hebben besloten om de BuildKit-pod eerst te hosten als een service op Kubernetes die gedeeld kan worden door meerdere builders en lokale schijfcaching kan bieden, zodat Docker-builds echt razendsnel worden.
Er zijn echter een paar beperkingen bij deze aanpak:
1. BuildKit heeft de fundamentele beperking dat het cache-bestanden-systeem slechts door één instantie van BuildKit gebruikt kan worden. Dit betekent dat als we meerdere instanties van BuildKit uitvoeren om meerdere builds parallel te verwerken, elke instantie een eigen cache heeft die niet gedeeld kan worden.
2. Als we meerdere instanties van BuildKit uitvoeren die elk hun eigen cache hebben, moet dezelfde workload naar dezelfde machine worden gerouteerd zodat de cache effectief gebruikt kan worden. Dit vereist een aangepaste routinglogica.
3. Automatisch schalen van de BuildKit-pods op basis van het aantal actieve builds is niet eenvoudig. We kunnen het CPU-gebruik van de BuildKit-pods niet gebruiken als een autoscaling-metric, omdat Kubernetes mogelijk een kleine actieve build beëindigt in de veronderstelling dat er niets op die machine draait.
Het hebben van een dynamisch aantal BuildKit-pods waarbij de workloads naar dezelfde cache-instantie worden gerouteerd, is een complex probleem. Het koppelen en ontkoppelen van volumes tussen pods is vrij traag in Kubernetes, wat leidt tot zeer lange tijden voor het starten van builds.
Om bovenstaande beperkingen te overwinnen, hebben we een hybride aanpak ontwikkeld: de meeste builds worden razendsnel voltooid, terwijl we in zeldzame gevallen van hoge concurrentie bij parallelle builds terugvallen op onze eerdere workflow waarin BuildKit in een sidecar draait.
In de architectuur die hieronder is geïllustreerd, configureren we een bepaalde build-concurrency waaronder alle builds naar de BuildKit-service gaan. Ter illustratie: stel dat we één machine met 4 CPU en 16 GB RAM toewijzen aan de BuildKit-service. Uit historische build-data kunnen we afleiden dat deze machine 2 gelijktijdige builds aan kan. Dus als er al één build draait en er komt een nieuwe binnen, wordt deze naar de BuildKit-service gerouteerd. Als er echter nog een build binnenkomt, routen we deze naar het eerdere model dat de layer-cache gebruikt die is opgeslagen in de Docker registry en BuildKit als sidecar uitvoert.

Dit stelt ons in staat om ultrasnelle builds te leveren voor 99% van de workloads, terwijl de build in een zeer klein aantal gevallen de tijd neemt die gewoonlijk in standaard CI-pipelines nodig is.
Verdere verbeteringen in build-snelheid
We hebben nog een paar andere verbeteringen in het buildproces doorgevoerd om het sneller te maken, waaronder:
- pip vervangen door uv: uv maakt een veel snellere afhankelijkheidsresolutie en pakketinstallatie voor Python mogelijk vergeleken met pip. Het vervangen van pip door uv zorgde voor een reductie van ongeveer 40% in de buildtijd.
- Gebruikmaken van NVMe-schijven in plaats van EBS: NVMe-schijven bieden snelle lees- en schrijftoegang, wat helpt bij het verbeteren van de buildsnelheid door het schrijven en lezen van de cache sneller te maken.
Snelheidsverbeteringen benchmarken
Om onze experimenten te benchmarken, hebben we één voorbeeld-Dockerfile genomen die het meest voorkomende scenario voor ML-workloads vertegenwoordigt.
FROM tfy.jfrog.io/tfy-mirror/python:3.10.2-slim
WORKDIR /app
RUN echo "Starting the build"
COPY ./requirements.txt /app/requirements.txt
RUN pip install -r requirements.txt
COPY . /app/
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
De requirements.txt ziet er als volgt uit:
fastapi[standard]==0.109.1
huggingface-hub==0.24.6
vllm==0.5.4
transformers==4.43.3
We hebben de build gebenchmarkt voor 3 scenario's:
- Eerste build (geen cache)
- Tweede build na het wijzigen van Python-code, maar zonder wijziging in afhankelijkheden.
- Tweede build na het wijzigen van afhankelijkheden in requirements.txt.
De tijden zijn inclusief de tijd voor zowel het bouwen als het pushen van de image naar de registry, uitgedrukt in seconden.

Het tweede scenario met alleen een codewijziging is het meest voorkomende scenario voor ontwikkelaars. Zoals we kunnen zien, is er sprake van een verbetering van bijna 15X in de buildtijden.
We hebben ook een scenario gebenchmarkt met een Dockerfile die Triton als base image bevat, wat een aanzienlijk grotere base image is.
FROM nvcr.io/nvidia/tritonserver:24.09-py3
WORKDIR /app
RUN echo "Starting the build"
COPY ./requirements2.txt /app/requirements.txt
RUN pip install -r requirements.txt
RUN echo "Finished the build"
De resultaten zijn als volgt:
.png)
In dit geval zien we een 3X verbetering in buildtijd voor de eerste build en een 9X verbetering voor opeenvolgende builds.
De bovengenoemde wijzigingen hebben de ontwikkelaarservaring enorm verbeterd. Het stelt hen in staat om heel snel te itereren op hun ideeën, terwijl de pariteit behouden blijft met de wijze waarop zaken uiteindelijk in productie worden geployd.
TrueFoundry AI Gateway levert een latentie van ~3–4 ms, verwerkt 350+ RPS op 1 vCPU, schaalt moeiteloos horizontaal en is klaar voor productie, terwijl LiteLLM last heeft van hoge latentie, moeite heeft boven een gemiddeld aantal RPS, geen ingebouwde schaling heeft en het meest geschikt is voor lichte workloads of prototypes.



















.png)

.webp)



.webp)







.webp)






