mirror of
https://github.com/mudler/LocalAI.git
synced 2026-04-16 12:59:33 -04:00
* feat(backend): add turboquant llama.cpp-fork backend
turboquant is a llama.cpp fork (TheTom/llama-cpp-turboquant, branch
feature/turboquant-kv-cache) that adds a TurboQuant KV-cache scheme.
It ships as a first-class backend reusing backend/cpp/llama-cpp sources
via a thin wrapper Makefile: each variant target copies ../llama-cpp
into a sibling build dir and invokes llama-cpp's build-llama-cpp-grpc-server
with LLAMA_REPO/LLAMA_VERSION overridden to point at the fork. No
duplication of grpc-server.cpp — upstream fixes flow through automatically.
Wires up the full matrix (CPU, CUDA 12/13, L4T, L4T-CUDA13, ROCm, SYCL
f32/f16, Vulkan) in backend.yml and the gallery entries in index.yaml,
adds a tests-turboquant-grpc e2e job driven by BACKEND_TEST_CACHE_TYPE_K/V=q8_0
to exercise the KV-cache config path (backend_test.go gains dedicated env
vars wired into ModelOptions.CacheTypeKey/Value — a generic improvement
usable by any llama.cpp-family backend), and registers a nightly auto-bump
PR in bump_deps.yaml tracking feature/turboquant-kv-cache.
scripts/changed-backends.js gets a special-case so edits to
backend/cpp/llama-cpp/ also retrigger the turboquant CI pipeline, since
the wrapper reuses those sources.
* feat(turboquant): carry upstream patches against fork API drift
turboquant branched from llama.cpp before upstream commit 66060008
("server: respect the ignore eos flag", #21203) which added the
`logit_bias_eog` field to `server_context_meta` and a matching
parameter to `server_task::params_from_json_cmpl`. The shared
backend/cpp/llama-cpp/grpc-server.cpp depends on that field, so
building it against the fork unmodified fails.
Cherry-pick that commit as a patch file under
backend/cpp/turboquant/patches/ and apply it to the cloned fork
sources via a new apply-patches.sh hook called from the wrapper
Makefile. Simplifies the build flow too: instead of hopping through
llama-cpp's build-llama-cpp-grpc-server indirection, the wrapper now
drives the copied Makefile directly (clone -> patch -> build).
Drop the corresponding patch whenever the fork catches up with
upstream — the build fails fast if a patch stops applying, which
is the signal to retire it.
* docs: add turboquant backend section + clarify cache_type_k/v
Document the new turboquant (llama.cpp fork with TurboQuant KV-cache)
backend alongside the existing llama-cpp / ik-llama-cpp sections in
features/text-generation.md: when to pick it, how to install it from
the gallery, and a YAML example showing backend: turboquant together
with cache_type_k / cache_type_v.
Also expand the cache_type_k / cache_type_v table rows in
advanced/model-configuration.md to spell out the accepted llama.cpp
quantization values and note that these fields apply to all
llama.cpp-family backends, not just vLLM.
* feat(turboquant): patch ggml-rpc GGML_OP_COUNT assertion
The fork adds new GGML ops bringing GGML_OP_COUNT to 97, but
ggml/include/ggml-rpc.h static-asserts it equals 96, breaking
the GGML_RPC=ON build paths (turboquant-grpc / turboquant-rpc-server).
Carry a one-line patch that updates the expected count so the
assertion holds. Drop this patch whenever the fork fixes it upstream.
* feat(turboquant): allow turbo* KV-cache types and exercise them in e2e
The shared backend/cpp/llama-cpp/grpc-server.cpp carries its own
allow-list of accepted KV-cache types (kv_cache_types[]) and rejects
anything outside it before the value reaches llama.cpp's parser. That
list only contains the standard llama.cpp types — turbo2/turbo3/turbo4
would throw "Unsupported cache type" at LoadModel time, meaning
nothing the LocalAI gRPC layer accepted was actually fork-specific.
Add a build-time augmentation step (patch-grpc-server.sh, called from
the turboquant wrapper Makefile) that inserts GGML_TYPE_TURBO2_0/3_0/4_0
into the allow-list of the *copied* grpc-server.cpp under
turboquant-<flavor>-build/. The original file under backend/cpp/llama-cpp/
is never touched, so the stock llama-cpp build keeps compiling against
vanilla upstream which has no notion of those enum values.
Switch test-extra-backend-turboquant to set
BACKEND_TEST_CACHE_TYPE_K=turbo3 / _V=turbo3 so the e2e gRPC suite
actually runs the fork's TurboQuant KV-cache code paths (turbo3 also
auto-enables flash_attention in the fork). Picking q8_0 here would
only re-test the standard llama.cpp path that the upstream llama-cpp
backend already covers.
Refresh the docs (text-generation.md + model-configuration.md) to
list turbo2/turbo3/turbo4 explicitly and call out that you only get
the TurboQuant code path with this backend + a turbo* cache type.
* fix(turboquant): rewrite patch-grpc-server.sh in awk, not python3
The builder image (ubuntu:24.04 stage-2 in Dockerfile.turboquant)
does not install python3, so the python-based augmentation step
errored with `python3: command not found` at make time. Switch to
awk, which ships in coreutils and is already available everywhere
the rest of the wrapper Makefile runs.
* Apply suggestion from @mudler
Signed-off-by: Ettore Di Giacinto <mudler@users.noreply.github.com>
---------
Signed-off-by: Ettore Di Giacinto <mudler@users.noreply.github.com>
149 lines
5.1 KiB
JavaScript
149 lines
5.1 KiB
JavaScript
import fs from "fs";
|
|
import yaml from "js-yaml";
|
|
import { Octokit } from "@octokit/core";
|
|
|
|
// Load backend.yml and parse matrix.include
|
|
const backendYml = yaml.load(fs.readFileSync(".github/workflows/backend.yml", "utf8"));
|
|
const jobs = backendYml.jobs;
|
|
const backendJobs = jobs["backend-jobs"];
|
|
const backendJobsDarwin = jobs["backend-jobs-darwin"];
|
|
const includes = backendJobs.strategy.matrix.include;
|
|
const includesDarwin = backendJobsDarwin.strategy.matrix.include;
|
|
|
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
const event = JSON.parse(fs.readFileSync(eventPath, "utf8"));
|
|
|
|
// Infer backend path
|
|
function inferBackendPath(item) {
|
|
if (item.dockerfile.endsWith("python")) {
|
|
return `backend/python/${item.backend}/`;
|
|
}
|
|
if (item.dockerfile.endsWith("golang")) {
|
|
return `backend/go/${item.backend}/`;
|
|
}
|
|
if (item.dockerfile.endsWith("rust")) {
|
|
return `backend/rust/${item.backend}/`;
|
|
}
|
|
if (item.dockerfile.endsWith("ik-llama-cpp")) {
|
|
return `backend/cpp/ik-llama-cpp/`;
|
|
}
|
|
if (item.dockerfile.endsWith("turboquant")) {
|
|
// turboquant is a llama.cpp fork that reuses backend/cpp/llama-cpp sources
|
|
// via a thin wrapper Makefile. Changes to either dir should retrigger it.
|
|
return `backend/cpp/turboquant/`;
|
|
}
|
|
if (item.dockerfile.endsWith("llama-cpp")) {
|
|
return `backend/cpp/llama-cpp/`;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function inferBackendPathDarwin(item) {
|
|
if (!item.lang) {
|
|
return `backend/python/${item.backend}/`;
|
|
}
|
|
|
|
return `backend/${item.lang}/${item.backend}/`;
|
|
}
|
|
|
|
// Build a deduplicated map of backend name -> path prefix from all matrix entries
|
|
function getAllBackendPaths() {
|
|
const paths = new Map();
|
|
for (const item of includes) {
|
|
const p = inferBackendPath(item);
|
|
if (p && !paths.has(item.backend)) {
|
|
paths.set(item.backend, p);
|
|
}
|
|
}
|
|
for (const item of includesDarwin) {
|
|
const p = inferBackendPathDarwin(item);
|
|
if (p && !paths.has(item.backend)) {
|
|
paths.set(item.backend, p);
|
|
}
|
|
}
|
|
return paths;
|
|
}
|
|
|
|
const allBackendPaths = getAllBackendPaths();
|
|
|
|
// Non-PR events: output run-all=true and all backends as true
|
|
if (!event.pull_request) {
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `run-all=true\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `has-backends=true\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `has-backends-darwin=true\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `matrix=${JSON.stringify({ include: includes })}\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `matrix-darwin=${JSON.stringify({ include: includesDarwin })}\n`);
|
|
for (const backend of allBackendPaths.keys()) {
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${backend}=true\n`);
|
|
}
|
|
process.exit(0);
|
|
}
|
|
|
|
// PR context
|
|
const prNumber = event.pull_request.number;
|
|
const repo = event.repository.name;
|
|
const owner = event.repository.owner.login;
|
|
|
|
const token = process.env.GITHUB_TOKEN;
|
|
const octokit = new Octokit({ auth: token });
|
|
|
|
async function getChangedFiles() {
|
|
let files = [];
|
|
let page = 1;
|
|
while (true) {
|
|
const res = await octokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}/files', {
|
|
owner,
|
|
repo,
|
|
pull_number: prNumber,
|
|
per_page: 100,
|
|
page
|
|
});
|
|
files = files.concat(res.data.map(f => f.filename));
|
|
if (res.data.length < 100) break;
|
|
page++;
|
|
}
|
|
return files;
|
|
}
|
|
|
|
(async () => {
|
|
const changedFiles = await getChangedFiles();
|
|
|
|
console.log("Changed files:", changedFiles);
|
|
|
|
const filtered = includes.filter(item => {
|
|
const backendPath = inferBackendPath(item);
|
|
if (!backendPath) return false;
|
|
return changedFiles.some(file => file.startsWith(backendPath));
|
|
});
|
|
|
|
const filteredDarwin = includesDarwin.filter(item => {
|
|
const backendPath = inferBackendPathDarwin(item);
|
|
return changedFiles.some(file => file.startsWith(backendPath));
|
|
})
|
|
|
|
console.log("Filtered files:", filtered);
|
|
console.log("Filtered files Darwin:", filteredDarwin);
|
|
|
|
const hasBackends = filtered.length > 0 ? 'true' : 'false';
|
|
const hasBackendsDarwin = filteredDarwin.length > 0 ? 'true' : 'false';
|
|
console.log("Has backends?:", hasBackends);
|
|
console.log("Has Darwin backends?:", hasBackendsDarwin);
|
|
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `run-all=false\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `has-backends=${hasBackends}\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `has-backends-darwin=${hasBackendsDarwin}\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `matrix=${JSON.stringify({ include: filtered })}\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `matrix-darwin=${JSON.stringify({ include: filteredDarwin })}\n`);
|
|
|
|
// Per-backend boolean outputs
|
|
for (const [backend, pathPrefix] of allBackendPaths) {
|
|
let changed = changedFiles.some(file => file.startsWith(pathPrefix));
|
|
// turboquant reuses backend/cpp/llama-cpp sources via a thin wrapper;
|
|
// changes to either directory should retrigger its pipeline.
|
|
if (backend === "turboquant" && !changed) {
|
|
changed = changedFiles.some(file => file.startsWith("backend/cpp/llama-cpp/"));
|
|
}
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${backend}=${changed ? 'true' : 'false'}\n`);
|
|
}
|
|
})();
|