De AI in je fotobibliotheek, en waarom we die naar onze eigen GPU's hebben verhuisd

Als je je fotobibliotheek doorzoekt op “hond in de sneeuw” en de juiste foto komt bovendrijven, dan heeft iets die foto al voor je gelezen. Niemand heeft er een tag op geplakt. Een machine learning-model heeft naar de pixels gekeken, bepaald wat er in beeld was, en dat begrip opgeslagen zodat je het later kon terugvinden. We hebben dat werk onlangs verplaatst van CPU’s naar onze eigen GPU’s (graphics processing units). Deze post legt uit wat de AI precies doet, en waarom de hardware eronder ertoe doet.
De modellen die dit mogelijk maken zijn niet van ons. Ze komen uit het opensource-project Immich waar PixelUnion op gebouwd is. Wat wél van ons is, is waar ze draaien: op hardware die we zelf bezitten en beheren, op Europese bodem, niet in een externe AI-cloud. De modellen lezen je foto’s, en wij vinden het belangrijk dat ze dat in onze eigen racks doen en nergens anders.
Er is één onderscheid dat de moeite waard is om te maken voor we verdergaan, want dit is het onderscheid dat het meest telt voor je privacy. Een model gebruiken is niet hetzelfde als een model trainen. Trainen is het dure, eenmalige proces waarbij een model leert van miljoenen voorbeeldafbeeldingen, en dat wordt gedaan door de makers van het model, lang voordat het bij ons terechtkomt. Wat wij doen is inference: we nemen een afgerond, al getraind model en halen je foto er één keer doorheen om een resultaat te krijgen, zoals je een document door een scanner haalt. Je foto’s worden nooit gebruikt om de modellen iets te leren. Ze worden niet toegevoegd aan een trainingsset, ze verbeteren geen gedeeld model, en er lekt niets weg om een toekomstige versie slimmer te maken. Het model kijkt naar je foto, geeft zijn antwoord, en vergeet het weer.
Drie taken, één gedeelde truc: van pixels iets doorzoekbaars maken
Immich draait zijn AI in een aparte service, de immich-machine-learning-container, gedocumenteerd in de machine learning README van het project. Het doet drie taken, en elke taak lost een ander probleem op:
- Smart search zet een foto om in iets dat je met gewone taal kunt doorzoeken, met een CLIP-model.
- Gezichtsherkenning vindt gezichten en bepaalt welke bij dezelfde persoon horen, met een modelpakket zoals
buffalo_l. - OCR leest tekst die in je foto’s voorkomt, met een
PP-OCRv5-model.
Het is goed om hier precies te zijn, want “drie modellen” is een versimpeling. Twee van deze taken zijn eigenlijk een paar modellen die na elkaar werken, en elke taak kun je inwisselen voor een ander model. Gezichtsherkenning draait een detectiemodel gevolgd door een herkenningsmodel, en buffalo_l is een bundel die beide bevat. Immich levert ook alternatieven zoals antelopev2, buffalo_m en buffalo_s. OCR werkt op dezelfde manier: één model om tekst te vinden en een ander om die te lezen. En CLIP zelf is een paar encoders, één voor beelden en één voor tekst, met veel uitwisselbare varianten. Er is helemaal geen apart objectherkenningsmodel: dat een foto een hond of een strand bevat, wordt afgehandeld door CLIP, niet door een speciaal tagging-model.
Wat telt om ze te begrijpen, is dat alle drie de taken één onderliggende truc delen, dus die truc snappen we eerst. Daarna vallen de verschillen op hun plek.

CLIP: zoeken op betekenis, niet op tags
Oude fotozoekfunctie werkte op bestandsnamen en handmatige tags. Als je “strand” niet in een bijschrift had getypt, vond zoeken op “strand” niets. CLIP, een model dat oorspronkelijk door OpenAI is gepubliceerd (paper hier), haalt die beperking weg. Het kijkt naar de daadwerkelijke inhoud van een beeld.
Dit is het stuk waar mensen over struikelen, gewoon uitgelegd. CLIP slaat geen lijst met woorden op voor elke foto. Het zet elke foto om in een vector: een lange rij getallen, een paar honderd, die samen de betekenis van het beeld beschrijven. Een foto van een husky in verse sneeuw en een foto van een andere hond in de sneeuw krijgen vergelijkbare rijen getallen, omdat ze iets vergelijkbaars betekenen. Zo’n rij heet een embedding.
Wat vector search eigenlijk is
Zodra elke foto in je bibliotheek een vector is, slaan we al die vectoren op in een database. Zoeken gaat zo:
- Je typt “hond in de sneeuw”.
- Hetzelfde model zet jouw woorden om in een vector, met hetzelfde getallensysteem als de foto’s.
- De database vergelijkt jouw zoekvector met de opgeslagen fotovectoren en meet hoe dichtbij elk ligt.
- De dichtstbijzijnde treffers komen als eerste terug.
“Dichtbij” is hier wiskunde. Elke vector is een punt in de ruimte, en het systeem meet de afstand tussen jouw zoekopdracht en elke foto. In de simpelste vorm is dat brute force: check de afstand tot elke afzonderlijke foto, sorteer op wie het dichtst bij zit. Er komt nergens in dat proces tag-matching of trefwoordopzoeken aan te pas. Een foto zonder bijschrift, zonder bestandsnaam en zonder beschrijving komt alsnog boven, omdat de betekenis in de getallen zit.

Die brute-force-vergelijking is de eerlijke versie, en voor een normale bibliotheek werkt die prima. Ze benoemt ook de kosten. Eén zoekopdracht vergelijken met miljoenen vectoren, één voor één, wordt traag. Op schaal stap je over op slimmere indexen, approximate nearest neighbor search, die het merendeel van de vergelijkingen overslaan en toch de juiste treffers vinden. Welke aanpak je nodig hebt hangt af van de omvang van de bibliotheek, maar het principe blijft hetzelfde: zet alles om in vectoren, vind daarna de dichtstbijzijnde.
Nog iets dat er in Europa toe doet. Het standaard CLIP-model kan goed overweg met Engels en slecht met andere talen. Er zijn meertalige varianten, en Immich laat je ze inwisselen, zoals de gids voor modelkeuze beschrijft. De tradeoff is echt: de betere meertalige modellen zijn groter en hebben meer geheugen nodig om te draaien. Die geheugendruk is een van de redenen waarom de keuze van hardware begint te tellen, en daar komen we op.
buffalo_l: dezelfde persoon over al je foto’s terugvinden
Gezichtsherkenning in Immich gebruikt buffalo_l, een modelpakket uit het InsightFace-project, voor Immich gehost op Hugging Face. Het draait in twee fasen, en beide zijn de moeite waard om te begrijpen, want mensen nemen vaak aan dat het iets doet wat het niet doet.
Fase één is detectie. Het model scant het beeld en bepaalt waar de gezichten zitten. Denk aan het scherpstelvierkantje dat een camera om een gezicht tekent, alleen doet het model het achteraf, op elke foto. De uitkomst is een set kaders: hier is een gezicht, hier is er nog een.
Fase twee is herkenning. Voor elk gevonden gezicht maakt het model een vector, hetzelfde idee als CLIP maar afgestemd op gezichten. Je kunt het zien als een numerieke vingerafdruk van dat gezicht. Twee foto’s van dezelfde persoon, in ander licht en vanuit andere hoeken, leveren vectoren op die dicht bij elkaar liggen. Twee verschillende mensen liggen ver uit elkaar.
Dat is het hele mechanisme achter “mensen” in je bibliotheek. We zoeken je gezicht niet op in een externe database, en er hangt aan niets een naam totdat je zelf iemand labelt. Het model meet de geometrie van een gezicht en zet die om in getallen, en groepeert vervolgens de getallen die dicht bij elkaar liggen. Immichs eigen gids over gezichtsclusters beschrijft hoe die groepering bij te stellen is. De vingerafdrukken blijven in ons systeem, op onze hardware. Ze zijn nooit het product.
PP-OCRv5: de tekst in je foto’s lezen
De nieuwste van de drie is OCR, optical character recognition, toegevoegd in Immich versie 2.2. Het gebruikt PP-OCRv5, een model uit Baidu’s PaddleOCR-project. OCR is wat je laat zoeken naar woorden die in een foto voorkomen: een straatnaambord, een winkelpui, een whiteboard, het etiket op een fles.
Ook dit werkt in twee fasen, en de opsplitsing weerspiegelt gezichtsherkenning.
Eerst detecteert het tekst. Het model vindt de delen van het beeld die tekst bevatten en tekent er kaders omheen, net zoals het gezichtsmodel gezichten omkadert. Een foto van een straat kan op vijf plekken tekst hebben: een bord, een kenteken, een poster, een menukaart, een bus. Elk wordt een kader.
Daarna leest het. Voor elk kader bepaalt een tweede model welke tekens er werkelijk staan en zet die om in echte, machineleesbare tekst. Die tekst gaat de zoekindex in, zodat je later kunt zoeken op “pizzeria” en de foto van de restaurantgevel vindt, ook al heb je nooit een bijschrift getypt.
De tradeoff is zo’n typisch Marc-detail dat het benoemen waard is: OCR is goed in duidelijke gedrukte tekst in ondersteunde schriften, en veel zwakker bij handschrift of sterk gestileerde lettertypen. Immich ondersteunt officieel Engels, Chinees en Japans, waarbij talen met een Latijns schrift over het algemeen werken met een wat lagere nauwkeurigheid, zoals besproken in de OCR-taalthread van het project. Het is een echt bruikbare functie, geen toverstaf.
Waarom dit allemaal zoveel beter op een GPU draait
Elk model hierboven doet onderhuids dezelfde soort rekenkunde: enorme aantallen kleine vermenigvuldigingen en optellingen, geordend als matrixwiskunde. Eén foto omzetten in een vector zijn miljoenen van die bewerkingen. Dat ene feit verklaart het hele hardwareverhaal.
Een CPU heeft een klein aantal zeer capabele cores, een handvol tot een stuk of twintig. Elke core is flexibel en snel, en werkt taken grotendeels na elkaar af. Dat ontwerp is uitstekend voor het gevarieerde, vertakte werk dat een server de hele dag doet. Het past slecht bij dezelfde simpele vermenigvuldiging een paar miljoen keer achter elkaar doen.
Een GPU is het tegenovergestelde. Die heeft duizenden eenvoudiger cores, gebouwd om dezelfde bewerking tegelijk op enorme reeksen getallen uit te voeren, parallel. Grafische kaarten waren ontworpen om miljoenen pixels tegelijk in te kleuren, en het blijkt dat machine learning-inference hetzelfde soort probleem is: dezelfde wiskunde, herhaald over veel data. Dus in plaats van dat de miljoenen bewerkingen van een foto golf voor golf door een paar CPU-cores druppelen, worden ze samen over duizenden GPU-cores verdeeld.

Het resultaat is geen kleine verbetering, het is een andere orde van snelheid voor precies deze werklast. Immich biedt hier precies iets voor in zijn documentatie over hardwareversnelling, waarmee de machine learning-service op CUDA, ROCm of OpenVINO kan draaien in plaats van op de CPU. Meldingen uit de community geven een gevoel voor de schaal. Op CPU is smart search steevast de traagste van de machine learning-taken, waarbij gebruikers het als het knelpunt beschrijven bij het indexeren van een grote bibliotheek. Op bescheiden GPU-hardware documenteert één schrijver het embedden van ongeveer 300.000 foto’s in minder dan één nacht. Dat zijn externe cijfers uit andermans opstelling, niet onze eigen getallen, maar de richting is overal consistent: de GPU werkt de achterstand in een fractie van de tijd weg.
De kaarten die we hiervoor kozen zijn oudere consumentenkaarten, geen nieuwe datacenter-accelerators. Dat is een bewuste keuze, en hij komt met tradeoffs die het benoemen waard zijn. Zo’n kaart heeft nog steeds duizenden parallelle cores en genoeg intern geheugen om deze modellen comfortabel vast te houden, en meer heeft deze werklast niet nodig. De modellen zijn naar moderne maatstaven niet groot. CLIP is de zwaarste van de drie, terwijl de gezichts- en OCR-modellen relatief licht zijn. Capabele oudere hardware hergebruiken houdt goede chips in dienst in plaats van ze op een plank te zetten, en het houdt de hele pijplijn op apparatuur die volledig van onszelf is.
Er staat een echte kostenpost tegenover, en doen alsof dat niet zo is zou oneerlijk zijn. Een GPU trekt onder belasting meer stroom dan een inactieve CPU, en je eigen hardware draaien betekent dat wij het onderhoud, de drivers en de stroom dragen. Wij vinden dat de juiste afweging. Per verwerkte foto doet de GPU het werk met veel betere efficiëntie, en het betekent dat de AI die je herinneringen leest draait op machines die wij beheren, onder Europees recht, in plaats van weggestuurd te worden naar iemand anders zijn cloud om verwerkt te worden.
Voorlopig draaien we de standaardmodellen van Immich, dezelfde die elke Immich-gebruiker standaard krijgt. De reden dat deze hardwarestap verder gaat dan snelheid is ruimte. Grotere modellen hebben doorgaans meer rekenkracht en meer geheugen nodig om op een bruikbaar tempo te draaien, precies wat een CPU niet kon leveren. Met onze eigen GPU’s op hun plek wordt een grotere CLIP-variant voor scherper zoeken, of een zwaarder gezichtsmodel voor betere clustering, een praktische optie in plaats van een onmogelijkheid. We doen hier geen beloftes over specifieke modellen. Het punt is dat deze stap de deur opent, en betere resultaten voor onze klanten zijn de reden om erdoorheen te lopen.
Er zit een addertje onder het gras bij het overstappen naar een groter model, en het is goed daar eerlijk over te zijn. Een ander model produceert andere vectoren. De getallen die het ene CLIP-model aan een foto toekent, betekenen niets voor een ander CLIP-model, dus je kunt oud en nieuw niet mengen. Als we ooit naar een groter model overstappen, zouden we de embeddings voor elke foto in elke bibliotheek van elke gebruiker helemaal opnieuw moeten berekenen, op onze eigen GPU’s. Voor een groot gebruikersbestand is dat een enorme hoeveelheid rekenwerk, en het is precies het soort klus dat op CPU’s kansloos is en op GPU’s slechts een grote klus. Deze stap brengt ons dichter bij de gereedheid daarvoor, maar we zijn er nog niet. Als het zover is, is dat een eigen blogpost waard.
Waarom we het zelf doen
Geen van deze modellen is exotisch, en geen ervan is geheim. Dat is de aard van bouwen op opensource: je kunt precies lezen wat CLIP, buffalo_l en PP-OCRv5 doen, en wij ook. De beslissing die je privacy werkelijk vormgeeft is niet welk model draait, maar waar het draait en wie het beheert.
Wij draaien ze op onze eigen GPU’s, in onze eigen omgeving, op Europese bodem. Je foto’s worden gelezen door machines waar wij verantwoordelijk voor zijn, en de vectoren en vingerafdrukken die uit dat proces komen blijven bij ons en worden nooit verkocht. Dat is het deel dat Big Tech je niet zal bieden, want voor hen is het lezen van je foto’s het verdienmodel, niet een functie.
Om duidelijk te zijn over waar we staan: dit is een eerste stap, niet het voltooide plaatje. De machine learning-modellen draaien nu op onze eigen GPU’s, maar niet ons hele platform draait al op hardware die wij bezitten. Zelf beheerde infrastructuur bouwen we stukje bij beetje op, en de AI die je foto’s leest was een bewuste plek om te beginnen, omdat dat het gevoeligste deel is. We vertellen je liever precies wat vandaag waar draait dan te suggereren dat we verder zijn dan we zijn. Reken erop dat we hier in toekomstige posts op uitbreiden naarmate meer van de stack naar onze eigen hardware verhuist.
Als je een fotobibliotheek wilt met zoekfunctie die zo goed is, zonder de onderliggende data af te geven aan een bedrijf dat er geld aan verdient, dan is dat wat wij bouwen.
Een noot over de afbeeldingen: de infographics in deze post zijn met AI gegenereerd. We denken dat ze hun plek verdienen doordat ze deze ideeën makkelijker voorstelbaar maken.