Skip to main content
internal/training/deploy.go, internal/training/db/, internal/api/training/models.go, internal/sys/sidecar/, gligabao.go Deploying a trained model means handing its weights to code of yours. gligabao does the part that is the same for everybody, getting the weights from where they were trained onto the local disk and checking them, and calls a hook with the result: a deployer, a Go callback registered by the program that embeds gligabao. What the deployer does with the files (upload them to a service, copy them to a bucket, convert them to another format, …) is its own business; gligabao has no built-in destination. This page describes how deploying works inside and the hook’s contract. The hook’s entry in the list of all hooks is Hooks; the trained models themselves (the gb_trained_models table, where the weights live) are in Trained models. Deploying is part of the training API: it is there only when the server runs in training mode (--training) and, to fetch the weights, with an active sidecar.

The flow

The whole thing is synchronous: the HTTP request is open while the files are downloaded and while Deploy runs, which can take minutes for a large model. Callers must not use a short timeout. If the client disconnects, the request’s ctx is cancelled, which Deploy should notice; the deployment is then recorded as failed.

Registering a deployer

Deployers are passed in ServeOptions.TrainedModelDeployers (there is no command-line or configuration-file way to register one, since a deployer is code):

What Deploy receives

What Deploy returns

  • Ref: identifies what the deployment made, in the deployer’s own terms (the name of a model created elsewhere, an object-store path, …). A string, stored and returned with the deployment.
  • Details: anything else worth keeping; a map[string]any that must be JSON-encodable (a deployer that returns details that are not is treated as failed). May be nil.
  • A non-nil error, or a panic (it is caught), makes the deployment failed; the error text is stored and returned.

Rules of the hook

  • Read only. Dir is shared with later deployments of the same model. Deploy must not modify or delete the files in it; copy, archive or convert into a place of its own.
  • Concurrent. Deploy requests are handled concurrently, also several for the same model, so Deploy must be safe for concurrent use. The files of one model are fetched once even then (see below).
  • Honour ctx. It is the request’s context: it is cancelled if the client goes away. The fetch has its own limit of 30 minutes; Deploy has none besides ctx.
  • No retries. gligabao runs Deploy once per request. A failed deployment stays failed; ask for a new one to try again (the files are already on the disk by then).
  • No sidecar. The deployer never talks to the sidecar; it finds the weights on the local disk.

Validation at startup

Serve checks the registered deployers and returns an error (invalid trained model deployer: ...) for any of these, before the server starts:
  • more than 20 deployers;
  • a Name that is empty, longer than 64 characters, or not letters, digits, _, - and . starting with a letter or digit;
  • two deployers with the same Name;
  • a deployer with no Deploy function.
Deployers are sorted by name; each one registered is logged at startup. With no deployer registered, every deployment request is answered 400.

Fetching the weights

The weights of a trained model live on the sidecar’s storage (Modal: the models volume), not on the server. Before it calls the deployer, gligabao copies them to the server’s disk. This is the part of deploying that is the same for every deployer.
  • Where. <trained model deploy dir>/<job id>, an absolute path (Dir). The deployment directory is serve --trained-model-deploy-dir (ServeOptions.TrainedModelDeployDir); the default is a trained_models directory next to the database file (build/trained_models). There is one subdirectory per training job, so models never mix. A job ID that is not usable as a directory name is refused (400).
  • What. Every file in the model’s files (the list recorded when the job completed), at its recorded relative path; nested paths are allowed. A path that is absolute, contains .. or uses a backslash is refused.
  • How. One GET /models/file?model_path=trained/<job id>&file=<path> request per file to the sidecar, which streams the raw bytes out of the volume (see Sidecar training API); the file is never held in memory.
  • Verified. Each file must have exactly the recorded size and the recorded sha256 (a model with no sha256 recorded for a file is checked by size only). A mismatch fails the deployment; nothing is deployed from unverified bytes.
  • Atomic. A file is downloaded under a temporary name (.fetch-*, in its own directory) and renamed to its real name only when it is complete and verified, so the real names never hold half a file, even if the server dies halfway.
  • Reused. A file that is already in Dir with the right size and sha256 is kept and not downloaded again; only missing or damaged files are fetched. Deploying the same model a second time therefore costs only the verification.
  • Serialized. Fetches into one directory are serialized: two simultaneous deployments of the same model do not download it twice; the second waits and then finds the files in place.
  • Never deleted. gligabao does not clean Dir. The files stay until you remove them (rm -r build/trained_models/<job id>); the next deploy fetches them again. Model weights are large, so plan for the disk space.
  • No recorded files. A model with no recorded files (for instance one saved by a version before files were listed) cannot be deployed: the deployment fails with “the trained model has no recorded files”.

The record of a deployment

Every deployment that gets past the checks of steps 1 and 2 is stored in gb_trained_model_deployments, when it starts and again when it ends, so each can be reviewed later, failures included: The table exists on SQLite and PostgreSQL and is created automatically. A row that stays running long after it started was interrupted, for example by a restart of the server: nothing resumes it, and nothing marks it failed.

HTTP API

Admin UI

In the admin UI’s Training view, the action box of a trained model (click its row) has a Deploy button, after Audit: it opens a dialog listing the registered deployers with their descriptions, each with its own Deploy button; the one pressed calls POST .../deployments with that deployer, and its outcome (status, ref, duration, or the error) is shown in its row. The Deployers block at the bottom of the view lists the registered deployers; with none registered, Deploy is disabled.

System events

Each deployment that reaches the fetch emits a system event to the SystemEvent hook:

In the code

internal/training/deploy.go (the whole flow), internal/training/db/ (RecordTrainedModelDeployment, FinishTrainedModelDeployment, GetTrainedModelDeployment, ListTrainedModelDeployments; the table for SQLite and PostgreSQL), internal/api/training/models.go (DeployOf, ListDeploymentsOf), internal/sys/sidecar/ (Client.DownloadModelFile), gligabao.go (the public types and ServeOptions).
  • training.DeployModel(ctx, db, fetcher, deployers, dir, model, DeployRequest{Deployer, Options}) runs steps 2 to 7 and returns the stored deployment. Its errors are *training.Error with a kind that the handler maps to a status (invalid: 400, upstream: 502, internal: 500).
  • training.FetchModelFiles(ctx, fetcher, model, dir) is the fetch alone; fetcher is a training.ModelFileFetcher, one method, DownloadModelFile(ctx, modelPath, file, w), which *sidecar.Client implements. training.ModelDir(root, model) gives the directory of a model.
  • training.ResolveDeployers(list) is the startup validation.

Limits and not included

  • Deployers are code in the embedding program; none is built in. There is no endpoint to register one at run time.
  • A deployment is one synchronous request. There is no background deploy, no progress, no cancellation besides the client leaving, and no resuming.
  • gligabao does not merge LoRA adapters into the base model, and does not convert formats: the deployer gets the files as they were saved (for the LoRA fine-tuning: adapter_config.json, adapter_model.safetensors and the tokenizer files, not the base model; adapter_config.json names the base model).
  • Deployments are never deleted, and neither are the fetched files.