LLM cluster governance · Universidad de Sevilla
Large language models, served from the cluster you already have.
ALMA gives every researcher an SLA, an API key and a list of endpoints with their own rate limits, then keeps the model behind them alive on SLURM: submitted, tunnelled, published to the gateway and restarted without anyone watching the queue.
Request access See how a request travels
- Runs on
- SLURM and vLLM, across A100 / A40 / A30 nodes
- Speaks
- OpenAI-compatible HTTP, one
apikeyheader - Asks of you
- No VPN, no
sbatch, no node to babysit
02 — the path
From your terminal to a GPU, in five hops
None of it is hidden from you. This is the whole route a request takes, in order, and what each hop is allowed to do to it.
-
your machine
One HTTPS call, one header
You send
apikey: <your key>to a public URL. No VPN, no SSH session, nosbatch, and nothing to install beyond the HTTP client you already use. -
caddy · alma.us.es
TLS ends here
Caddy terminates TLS, forces HTTPS and sets the security headers, then splits the host by path:
/api/models/*reaches the SLA gateway,/api/*the backend, and/portal/the portal you sign in to. -
sla gateway · nginx
Your SLA, compiled
The YAML template an administrator assigned you is compiled into an nginx configuration: which models you may reach, which endpoints on them, at what rate. A model outside your SLA is not a permission error you have to interpret — it was never routed to you.
-
alma-telemeter
Counted per request
nginx asks the telemeter on every call, through
auth_request. It answers with what is left of your budget inX-RateLimit-*headers and turns an exhausted one into a429carryingRetry-After, never a silent drop. When its Redis is unreachable it omits the headers rather than reporting numbers it cannot stand behind. -
ssh tunnel → vllm
Wherever SLURM put the model
The gateway forwards to a local port tunnelled to the compute node SLURM chose. Models are not pinned to a node, so that node is only known once the job leaves the queue — which is why every open tunnel is reconciled, every 30 seconds, against what the running models actually need.
curl -X POST "https://alma.us.es/api/models/<model>/v1/chat/completions" \
-H "apikey: $ALMA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"<model>","messages":[{"role":"user","content":"Hello"}],"max_tokens":32}'
HTTP/2 200
content-type: application/json
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
x-ratelimit-reset: 41
Past the limit the same request returns 429 with Retry-After,
so a client can back off instead of retrying into a wall.
https://alma.us.es/api/models/<model>/v1
That is a complete OpenAI base URL: a client appends /models to check the
connection and /chat/completions to talk. In Open WebUI it goes in
Admin Settings → Connections → OpenAI, with your key in the API Key field —
one connection per model. Those clients send the key as
Authorization: Bearer, which the gateway accepts alongside the
apikey header.
03 — your account
An SLA, not a quota you have to guess
Everything you are allowed to do is written down, visible in the portal and exportable as JSON. This is what an account looks like from the inside.
-
An API key you can rotate
Issued with the SLA, sent as a single
apikeyheader, revocable by an administrator without you touching your code. -
Endpoints carrying their own limits
Each model exposes named endpoints, each with its own rate. The portal prints the limit next to the path instead of burying it in a policy document.
-
OpenAI-compatible calls
vLLM serves the chat-completions shape, so existing clients, notebooks and front-ends like Open WebUI work after changing one base URL. The key travels either as the
apikeyheader or as a standardAuthorization: Bearertoken. -
Honest model state
Every model in your SLA reads running or not running as of now. The portal reports the queue; it never assumes it.
-
An export, not a screenshot
One click gives you a JSON file of your endpoints, methods and rate limits, ready for a script or a colleague.
-
Roles that mean something
Admin, researcher, student and viewer see different surfaces. A researcher never has to walk through an operator's console to do their work.
active sla
research-standard
- API key
ak_live_••••••••••••3f9a
llama-3-8b-instruct
running · A100 node
| method | endpoint | rate |
|---|---|---|
| POST | /v1/chat/completions | 60 / min |
| POST | /v1/completions | 60 / min |
| GET | /v1/models | 120 / min |
04 — the stack
Four services, one job each
ALMA is not one program. It is a small set of services with sharp edges between them, so a failure in one stays legible from the others.
| service | what it does | how it ships |
|---|---|---|
| alma-frontend | The admin console and the user portal: models, users, SLA templates, cluster configuration, live SLURM logs, the OpenAPI spec. React 18 and Vite, served under /portal/. |
Docker and nginx |
| alma-backend | The REST API and the decisions behind it: model lifecycle, SLA generation and nginx configuration, SSH tunnels, the restart and tunnel schedulers, operator alerts, multi-device sessions. | Push a v1.x.y tag |
| slurm-controller | A thin HTTP API over sbatch, squeue, scancel and the job logs, plus GPU availability per node. The only component that touches SLURM directly. |
Submitted to the cluster by hand |
| alma-telemeter | Rate-limit accounting behind nginx auth_request, backed by Redis, with the SLA limits watched on disk and the X-RateLimit-* headers computed per call. |
Docker |
The controller ships by hand on purpose: it runs as a SLURM job, inside the cluster, where no CI reaches. That asymmetry is the origin of the first failure mode below.
05 — when it breaks
The interesting part is the failure handling
A cluster is not a cloud. Jobs get preempted, nodes fill up, a deploy lands on one side of the system and not the other. These are the cases ALMA is written around.
-
Version skew is named, not guessed
The backend asks the controller what it can do —
GET /healthreturns a capability list, a version and a commit — and reports the answer as one of OK DEGRADED MISROUTED UNREACHABLE. A base URL pointing at the wrong service answers 404 exactly like an old controller does, so the two are told apart by whether the 404 carried the controller's own request id. An answer nobody can attribute is never read as a missing feature. -
A lost job is adopted, not duplicated
Every launch is tagged
alma-<modelHash>. If the record of a job goes missing, the next attempt finds the job in the queue by that name and rebuilds the record instead of submitting a second copy beside it. Nothing is ever submitted that cannot be named, and therefore cannot be stopped. -
Restarts have a budget, and it has to be earned back
A model that dies while an SLA still references it is relaunched with exponential backoff and jitter, bounded by attempts and by elapsed time. Deferrals — the queue could not be read, something else is already launching it — do not spend the budget; only the model's own failures do. The budget returns after the model has stayed up, not after a single sighting of Running.
-
Tunnels are reconciled, not assumed
Every 30 seconds the tunnel units on the host are compared against the running models: orphans left by deleted models are torn down, two units claiming one port are separated, and a tunnel whose ssh process hung — active to
systemctl, dead to a connection — is reinstalled. Gateway routes are published and withdrawn in the same tick, so a tunnel is never open onto a port nothing points at. -
Anything needing a person raises an alert
A suspended model, an unresolvable port conflict, a scheduler blocked by a stale controller: each is recorded, deduplicated and pushed to Slack, Discord, Teams or e-mail, addressed to whoever holds the admin role at that moment. A dead webhook cannot fail the tick that raised the alert.
06 — access
Getting an account
ALMA runs on academic infrastructure at the Universidad de Sevilla. Access is granted per person, with an SLA sized to what you are actually doing.
-
Write to us
Say who you are, your group or degree, the model or task you have in mind, and roughly how much traffic you expect.
-
An administrator assigns an SLA
You get a user, a role and an SLA template carrying the endpoints and rate limits that fit the request.
-
Collect your key in the portal
Sign in, copy the API key, copy the
curlprinted next to any endpoint, export the list as JSON, and start.