Fins aquí el servidor d'inferència ha aparegut com un generador de text, però els servidors moderns serveixen més d'una família de models. Un pipeline RAG complet en necessita tres: un model **generatiu** que escriu la resposta, un model d'**embeddings** (*bi-encoder*) que converteix textos en vectors per indexar-los i cercar-los, i un **reranker** (*cross-encoder*) que reordena els candidats recuperats puntuant cada parella (consulta, fragment). Els dos últims són models encoder petits, no LLMs — la família que la nota de [Variants modernes](ml_transformers.md#variants-modernes) contraposa als generatius — però s'exposen amb el mateix mecanisme: una ruta HTTP per tasca.
| Reranking | cross-encoder | `/rerank`, `/v1/rerank`, `/v2/rerank` | cap — convencions de Jina i de Cohere |
Ollama serveix generació i embeddings; `llama-server` (llama.cpp) serveix exactament les tres tasques de la taula (amb els flags `--embeddings` i `--reranking`); vLLM hi afegeix classificació (`/classify`), scoring i transcripció d'àudio. La fila del reranking mostra un fet instructiu: no hi ha ruta estàndard perquè OpenAI no ofereix aquest producte, i cada servidor imita les APIs dels proveïdors que sí que l'ofereixen (Jina, Cohere). L'estandardització de facto la fa qui té el producte dominant, no cap comitè.
**Fins on arriba la compatibilitat.** Per a la generació i els embeddings, l'estàndard té un artefacte normatiu: OpenAI publica una especificació OpenAPI contra la qual tothom programa. La compatibilitat entre servidors és sòlida al nucli de cada esquema (`messages` → `choices`; `input` → `data[].embedding`) i difusa a les vores: cada servidor afegeix extensions pròpies (la sortida estructurada és `guided_json` a vLLM i gramàtiques GBNF a llama-server) i els paràmetres opcionals no estan garantits — `dimensions`, amb què OpenAI exposa el truncament Matryoshka dels embeddings, no existeix a llama-server.
El reranking no té ni això. El format que tots dos servidors accepten és el que van fixar Cohere i Jina:
```json
{"query":"com es defineix la quota d'un dataset?",
i la resposta reordena per rellevància: `{"results": [{"index": 2, "relevance_score": 0.94}, ...]}`. Però el compromís de cada servidor és diferent, i és una lliçó sobre què val un estàndard sense propietari: vLLM declara compatibilitat explícita amb l'API de Jina (v1) i les de Cohere (v1 i v2); llama-server només diu que la seva ruta és "similar a la de Jina, però pot canviar". Avui els cossos coincideixen; cap especificació no obliga que demà també. I com que l'SDK d'OpenAI no té cap mètode de rerank, la crida es fa amb HTTP pla (`httpx`, `requests`) o amb l'SDK d'un proveïdor.
Que els servidors hagin convergit exactament en aquestes tres tasques no és casualitat: són els tres rols de model del patró RAG (vegeu [Recuperació, embeddings i índexs](llm_systems.md#recuperació-embeddings-i-índexs)), que és el cas d'ús que ha empès la infraestructura. La línia de producte de Cohere ho fa explícit: Command (generar), Embed, Rerank.
### Model servit o model en procés
El principi que obre [el rol del servidor de models](#el-rol-del-servidor-de-models) — el model és un servei, no una llibreria — és cert per als LLMs generatius, on carregar desenes de GB per procés és inviable. Però els models d'embeddings i els rerankers solen ser encoders de ~100M paràmetres que corren bé en CPU, i aquests sí que es poden importar com a llibreria (`sentence-transformers`) dins del procés de l'aplicació. Apareix, doncs, una decisió arquitectònica real: servir el model o executar-lo **en procés** (*in-process*). Els criteris:
**Consistència entre consumidors** — el criteri més fort, i específic dels embeddings: els vectors de l'índex i els de les consultes han de sortir del mateix model exacte, i canviar-lo obliga a re-indexar. Quan més d'un procés o més d'una persona toquen el mateix índex, un servidor únic és la manera de garantir-ho — i el punt únic on una actualització de versió (amb la seva re-indexació) passa una sola vegada. El reranking no té aquest acoblament: és sense estat, cap dada persistida en depèn.
**Encaix de maquinari** — un model generatiu de 7B+ necessita una GPU que els clients normalment no tenen: servit. Un cross-encoder de ~90 MB corre a qualsevol CPU: executar-lo en procés no costa res.
**Utilització** — un model servit es carrega una vegada i agrupa peticions de molts clients (batching continu); un model en procés es carrega a cada worker i resta ociós entre crides. Molts consumidors amb poc ús cadascun afavoreixen servir; un sol consumidor intensiu no hi guanya res.
**Dependències i arrencada** — en procés vol dir PyTorch i els pesos dins de cada aplicació consumidora, i pagar la càrrega del model a cada arrencada de procés. Servit vol dir una crida HTTP i una URL de configuració.
**Privadesa i desconnexió** — dades que no poden sortir de la màquina o treball sense xarxa forcen l'execució local (en procés o amb un servidor a la mateixa màquina).
La regla pràctica: **prototipa en procés; serveix un model quan guanya un segon consumidor, quan no cap al maquinari del client o quan la seva versió comença a importar.** Aplicat a les tres tasques: la generació es serveix gairebé sempre (maquinari); els embeddings es serveixen tan bon punt l'índex es comparteix (consistència); el reranker es queda en procés fins que l'escala justifiqui el contrari (utilització).
### Abstraure múltiples proveïdors: la capa de routing
Quan un sistema necessita suportar diversos proveïdors (per fallback, per A/B testing de models, o per centralitzar el control de costos), afegir una **capa de routing** entre el backend i els proveïdors és el patró adequat. Aquesta capa exposa una única API estàndard cap al backend i gestiona la distribució cap als proveïdors de forma transparent.
@@ -169,7 +211,7 @@ La regla pràctica és començar amb el model més petit que compleixi aquestes
Un model amb \\(N\\) paràmetres en precisió FP16 ocupa aproximadament \\(2N\\) bytes de memòria GPU **només per emmagatzemar els pesos**: