Skip to content

Commit 34270c0

Browse files
Merge branch 'master' into fix/7461-mlx-file-uri
2 parents 69c7a8e + 84972cb commit 34270c0

1,468 files changed

Lines changed: 170614 additions & 43141 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/adding-backends.md

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The build matrix is data-only YAML at `.github/backend-matrix.yml` (not inside `
3434

3535
**Without an entry here no image is ever built or pushed, and the gallery entry in `backend/index.yaml` will point at a tag that does not exist.** The `dockerfile:` field must point at `./backend/Dockerfile.<lang>` matching the language bucket from step 1 (e.g. `Dockerfile.python`, `Dockerfile.golang`, `Dockerfile.rust`). The `tag-suffix` must match the `uri:` in the corresponding `backend/index.yaml` image entry exactly.
3636

37-
**`scripts/changed-backends.js` registration — REQUIRED for any new dockerfile suffix.** This is the single most common omission, because it has no effect on the PR that adds the backend (when no prior path filter could catch it anyway) — it only breaks the *next* PR that touches your backend's directory, which then gets zero CI jobs and looks broken for unrelated reasons. Edit `scripts/changed-backends.js:inferBackendPath` and add a branch BEFORE the more-generic suffixes:
37+
**Path-filter registration — REQUIRED for any new dockerfile suffix.** This is the single most common omission, because it has no effect on the PR that adds the backend (when no prior path filter could catch it anyway) — it only breaks the *next* PR that touches your backend's directory, which then gets zero CI jobs and looks broken for unrelated reasons. Edit `scripts/lib/backend-filter.mjs:inferBackendPath` and add a branch BEFORE the more-generic suffixes:
3838

3939
```js
4040
if (item.dockerfile.endsWith("<your-dockerfile-suffix>")) {
@@ -54,7 +54,9 @@ for (const e of m.include.filter(e => e.backend === '<your-backend>')) {
5454
}"
5555
```
5656

57-
A quick way to find the right insertion point: `grep -n 'item.dockerfile.endsWith' scripts/changed-backends.js`.
57+
A quick way to find the right insertion point: `grep -n 'item.dockerfile.endsWith' scripts/lib/backend-filter.mjs`.
58+
59+
If your backend consumes a *shared* build input that lives outside its own directory (a new script under `scripts/build/`, a new file copied into every image), add a rule to `SHARED_BUILD_INPUTS` in the same file — the per-backend prefix match cannot see those, and a miss ships your change to no image at all. See `scripts/lib/backend-filter_test.mjs` for the pattern; `make test-ci-scripts` runs it.
5860

5961
**`bump_deps.yaml` registration — REQUIRED for any backend pinning an upstream commit.** If your backend's Makefile has a `*_VERSION?=<sha>` pin to a third-party repo, the daily auto-bump bot at `.github/workflows/bump_deps.yaml` won't notice it unless you register the backend in its matrix. The bot runs `.github/bump_deps.sh` which `grep`s for `^$VAR?=` in the Makefile you list — so the pin MUST live in the Makefile (not in a separate shell script). The bump for ds4 (#9761) had to walk this back because the original landed the pin in `prepare.sh`, which the bot can't see. Pattern (for `antirez/ds4`):
6062

@@ -102,6 +104,24 @@ Multi-arch backends are NOT a single matrix entry with `platforms: 'linux/amd64,
102104

103105
Entries whose `dockerfile` is `./backend/Dockerfile.{llama-cpp,ik-llama-cpp,turboquant}` must also set a `builder-base-image` field pointing at a prebuilt base from `quay.io/go-skynet/ci-cache:base-grpc-*` (CI builds these via `.github/workflows/base-images.yml`). The mapping is by `(build-type, platforms)` — see existing entries for the pattern. CI uses these prebuilt bases to skip the gRPC compile (~25–35 min cold). Local `make backends/<name>` ignores `builder-base-image` and uses the from-source path inside the Dockerfile, so you don't need quay access for local builds.
104106

107+
### Cover every OS the project supports (Linux **and** Darwin)
108+
109+
`.github/backend-matrix.yml` has two matrices, and they are the source of truth for which OS a backend ships on:
110+
111+
- `include:` — the **Linux** matrix (x86_64 + arm64; CPU and CUDA / ROCm / SYCL / Vulkan).
112+
- `includeDarwin:` — the **macOS / Apple Silicon** matrix (arm64; Metal where the engine supports it, otherwise a native arm64 CPU build).
113+
114+
**A new backend must target every OS it can build for — do not ship Linux-only by default.** A backend that appears only under `include:` is silently unavailable on macOS even when its code would run there. Most C/C++/GGML engines build on Darwin out of the box (ggml defaults `GGML_METAL=ON` on Apple, so a plain build is Metal-enabled), and many Python backends do too (CPU / MPS wheels). If a backend genuinely cannot support an OS (e.g. CUDA-only, no CPU variant), state that in the PR description instead of omitting it silently.
115+
116+
Wiring a backend into `includeDarwin:` is more than the matrix entry:
117+
118+
1. **`includeDarwin:` entry** — `tag-suffix: "-metal-darwin-arm64-<backend>"`, `build-type: "metal"`, `lang: "go"` for go+ggml backends; omit `build-type` for the bespoke C++ ones (llama-cpp / ds4 / privacy-filter). Match an existing entry of the same shape.
119+
2. **`backend/index.yaml`** — add `metal:` to the backend's `capabilities` map (main and `-development`) and concrete `metal-<backend>` / `metal-<backend>-development` image entries pointing at the `-metal-darwin-arm64-<backend>` images.
120+
3. **C/C++ backends only** — add an `inferBackendPathDarwin` case in `scripts/lib/backend-filter.mjs` returning `backend/cpp/<backend>/` (the generic fallthrough assumes `backend/<lang>/`, which is wrong for a C++ source tree driven with `lang: go`), and give `run.sh` a Darwin branch that exports `DYLD_LIBRARY_PATH` instead of `LD_LIBRARY_PATH`. If the build is bespoke (single `grpc-server` + dylib bundling), model it on `scripts/build/ds4-darwin.sh` and add a `backends/<backend>-darwin` make target plus a gated step in `.github/workflows/backend_build_darwin.yml`.
121+
4. **C++ proto gotcha** — if the backend compiles the generated gRPC/protobuf in a separate CMake target (e.g. `hw_grpc_proto`), that target must link `protobuf::libprotobuf` + `gRPC::grpc++` so the Homebrew include dirs propagate; otherwise macOS fails with `google/protobuf/runtime_version.h not found` (Linux hides this because apt headers sit in `/usr/include`).
122+
123+
The CI path filter only builds a backend on a PR when a file under its directory changes, so a darwin-only YAML edit builds nothing — touch a file under `backend/<lang>/<backend>/` (a one-line comment is enough) in the same PR.
124+
105125
## 3. Add Backend Metadata to `backend/index.yaml`
106126

107127
**Step 3a: Add Meta Definition**
@@ -198,19 +218,108 @@ docker-build-backends: ... docker-build-<backend-name>
198218
- If the backend is in `backend/python/<backend-name>/` but uses `.` as context in the workflow file, use `.` context
199219
- Check similar backends to determine the correct context
200220

221+
## Engine preference for gallery model variants
222+
223+
A gallery entry can declare `variants`, alternative builds of the same weights,
224+
and LocalAI picks one per host: it drops builds whose backend cannot run here or
225+
that do not fit memory, then ranks the survivors by **engine preference
226+
first, serving feature second, size third** (`SelectVariant` in
227+
`core/gallery/resolve_variant.go`).
228+
229+
Ask whether your backend should outrank another one on some hardware. If it
230+
should, add it to `engineNamePreferenceRules` in `pkg/system/capabilities.go`,
231+
best engine first for that capability:
232+
233+
```go
234+
{Nvidia, []string{engineVLLM, engineSGLang, engineLlamaCpp}},
235+
+ {Nvidia, []string{engineVLLM, engineSGLang, engineMyEngine, engineLlamaCpp}},
236+
```
237+
238+
That is the ENGINE NAME table, matched as a substring of a gallery entry's
239+
`backend:` value. Two sibling tables in the same file speak different
240+
vocabularies and are matched against different things:
241+
242+
| Table | Vocabulary | Matched against | Consumer |
243+
|-------|-----------|-----------------|----------|
244+
| `backendBuildTagPreferenceRules` | build tags (`cuda`, `rocm`, `metal`) | installed build directory names, as a substring | alias resolution in `ListSystemBackends` |
245+
| `engineNamePreferenceRules` | engine names (`vllm`, `llama-cpp`, `mlx`) | a gallery entry's `backend:`, as a substring | gallery variant ranking |
246+
| `servingFeaturePreferenceTokens` | serving features (`dflash`, `mtp`) | a gallery entry's `tags:`, compared whole and case-insensitively, and nothing else | gallery variant ranking, one rank below the engine |
247+
248+
**Putting a token in the wrong table matches nothing and does not error**: every
249+
candidate scores equal and the next sort key decides, so the preference silently
250+
stops existing. The block comment above all three tables spells the contract out.
251+
252+
The serving feature table is the odd one: it is not keyed by capability, because
253+
no hardware prefers a plain build over an equivalent faster build of the same
254+
weights. It reads a declared tag and nothing else. The entry name was the
255+
original signal and is gone: a naming convention is not a contract, and names
256+
are author-supplied free text where a short marker like `mtp` turns up inside
257+
unrelated words or on weights whose entry enables nothing.
258+
`overrides.options` was rejected for the mirror-image reason: `spec_type:` is
259+
llama.cpp's config vocabulary, whereas a cross-backend ranking decision must
260+
work the same for `ds4`'s `mtp_path:` and `sglang`'s `speculative_algorithm:`.
261+
262+
**If your backend can serve the same weights faster** (speculative decoding,
263+
multi-token prediction), say so in the docs for its gallery entries so curators
264+
tag them: the tagging rule and the per-backend evidence table live in
265+
[adding-gallery-models.md](adding-gallery-models.md). A backend never needs to
266+
appear in the token table itself; it ranks builds, not engines.
267+
268+
Leaving your backend out is a valid choice when no ordering can be justified for
269+
it. It then ranks below every known engine and selection falls back to size,
270+
which is the behaviour that predates preference.
271+
272+
**Leaving a whole capability out is not.** A missing row gives that host an
273+
empty preference list, so size alone decides among everything that survives the
274+
filters, and the filter will not save you: `IsBackendCompatible` derives hardware
275+
support from the engine NAME, so `vllm` and `sglang` carry no darwin, cuda, rocm
276+
or sycl token and are never dropped on a host with no GPU. That is why `default`
277+
(no usable accelerator, including a GPU under the 4 GiB VRAM floor) and
278+
`darwin-x86` both have rows putting `llama-cpp` first. Every capability
279+
`getSystemCapabilities()` can return needs a row unless every engine really is
280+
equally at home there. When you add one, enumerate the engines you are demoting
281+
rather than relying on them falling through unmatched: unmatched engines all tie
282+
with each other, so size decides among them.
283+
284+
## Documenting the backend (README + docs)
285+
286+
A backend is not "added" until it is discoverable. Update the user-facing docs:
287+
288+
- **`docs/content/features/backends.md`** - add the backend to the right
289+
category in the "LocalAI supports various types of backends" list (and add a
290+
new category if it introduces a new modality, e.g. sound classification).
291+
- If the backend introduces a **new API surface** (a new endpoint or a realtime
292+
capability), document it under `docs/content/` where its area lives (audio,
293+
vision, etc.) and follow the api-endpoints checklist in
294+
[api-endpoints-and-auth.md](api-endpoints-and-auth.md).
295+
296+
**If the backend is a native C/C++/GGML engine created and maintained by the
297+
LocalAI team** (a from-scratch port like `parakeet.cpp`, `ced.cpp`,
298+
`vibevoice.cpp`, `rf-detr.cpp`, not a wrapper around a third-party runtime), it
299+
ALSO belongs in the top-level **`README.md`** table under "native C/C++/GGML
300+
engines ... developed and maintained by the LocalAI project itself". Add a row
301+
linking the upstream engine repo with a one-line description. This is the
302+
project's showcase of its own engines; a new in-house backend that is missing
303+
from it is a documentation bug.
304+
201305
## 5. Verification Checklist
202306

203307
After adding a new backend, verify:
204308

205309
- [ ] Backend directory structure is complete with all necessary files
206310
- [ ] Build configurations added to `.github/backend-matrix.yml` for all desired platforms (per-arch entries with `platform-tag` for multi-arch; `builder-base-image` for llama-cpp / ik-llama-cpp / turboquant)
311+
- [ ] **OS coverage considered**: added to `includeDarwin:` (macOS/Apple Silicon) if the backend can build there — with the `backend/index.yaml` `metal:` capability + `metal-<backend>` image entries, a `run.sh` Darwin/DYLD branch and `inferBackendPathDarwin` case (in `scripts/lib/backend-filter.mjs`) for C++ backends — or the PR explains why an OS is unsupported. Do not ship Linux-only by default.
207312
- [ ] Meta definition added to `backend/index.yaml` in the `## metas` section
208313
- [ ] Image entries added to `backend/index.yaml` for all build variants (latest + development)
209314
- [ ] Tag suffixes match between workflow file and index.yaml
210315
- [ ] Makefile updated with all 6 required changes (`.NOTPARALLEL`, `prepare-test-extra`, `test-extra`, backend definition, docker-build target eval, `docker-build-backends`)
211316
- [ ] No YAML syntax errors (check with linter)
212317
- [ ] No Makefile syntax errors (check with linter)
213318
- [ ] Follows the same pattern as similar backends (e.g., if it's a transcription backend, follow `faster-whisper` pattern)
319+
- [ ] **`Load` validates its input and refuses models it can't serve.** When a model config has no explicit `backend:`, the model loader greedily probes *every* installed backend with the model's name and binds to the first `Load` that succeeds — an accept-anything `Load` will capture arbitrary LLMs (issue #9287). Backends that load a real artefact get this for free (the load fails); backends with no artefact must gate on the name: `opus` accepts only its own name (or none), `local-store` requires the `store.NamespacePrefix` namespace marker sent by `core/backend/stores.go`.
320+
- [ ] **Gallery variant ranking considered**: if this backend should be preferred over another on some hardware, it is listed in `engineNamePreferenceRules` (NOT `backendBuildTagPreferenceRules`, NOT `servingFeaturePreferenceTokens`) in `pkg/system/capabilities.go`. A missing entry silently ranks it last and lets the next sort key decide.
321+
- [ ] Documented: added to the category list in `docs/content/features/backends.md` (and any new endpoint/realtime capability documented under `docs/content/`)
322+
- [ ] If it is an in-house native C/C++/GGML engine, added to the maintained-engines table in the top-level `README.md`
214323

215324
## Bundling runtime shared libraries (`package.sh`)
216325

0 commit comments

Comments
 (0)