mirror of
https://github.com/mudler/LocalAI.git
synced 2026-08-04 12:22:22 -04:00
Compare commits
15 Commits
docs/prese
...
maint/1134
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79cc9e7678 | ||
|
|
8f52437c81 | ||
|
|
cd516452dd | ||
|
|
3f0db2a9c2 | ||
|
|
137dfcf15a | ||
|
|
750ab91b2b | ||
|
|
08598a8611 | ||
|
|
211aa0a536 | ||
|
|
c86b3b207b | ||
|
|
62316e52a9 | ||
|
|
3090101156 | ||
|
|
8b667cd1ce | ||
|
|
93fe086798 | ||
|
|
2e14511fe2 | ||
|
|
88fdda6211 |
@@ -8,8 +8,15 @@ build_type=${2-}
|
||||
# ggml-cpu/arch/x86/repack.cpp at -march=sapphirerapids: the job sits on that one
|
||||
# translation unit until GitHub kills it at 6h. gcc builds the same file in
|
||||
# seconds, so only the SYCL images have to give up the CPU variant matrix.
|
||||
#
|
||||
# ROCm runs out of the same 6h budget for a different reason: volume, not a
|
||||
# stall. hipcc compiles ggml's HIP kernels once per entry in AMDGPU_TARGETS,
|
||||
# which is eleven architectures (gfx908 through gfx1201), and the CPU variant
|
||||
# matrix lands on top of that. The job built in 2h27m before it was added and
|
||||
# has been killed at exactly 6h00m on every run since, so no ROCm llama-cpp
|
||||
# image has been published since 2026-08-01.
|
||||
case "$build_type" in
|
||||
sycl*)
|
||||
sycl*|hipblas*)
|
||||
echo llama-cpp-fallback
|
||||
exit 0
|
||||
;;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# recipe is a make target (not a prepare.sh) so 'make purge && make' is a clean
|
||||
# rebuild and so the bump bot can see the pin.
|
||||
|
||||
AUDIO_CPP_VERSION?=5a8312ef7b8aa7cf14e9a24ac568cabd8725d68a
|
||||
AUDIO_CPP_VERSION?=4e3aea2fd99aeaa5924e71c51eb2793846045332
|
||||
AUDIO_CPP_REPO?=https://github.com/0xShug0/audio.cpp
|
||||
|
||||
CURRENT_MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
|
||||
@@ -69,7 +69,15 @@ target_include_directories(hw_grpc_proto PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(DS4_OBJS "${DS4_DIR}/ds4.o")
|
||||
if(DS4_GPU STREQUAL "cuda")
|
||||
list(APPEND DS4_OBJS "${DS4_DIR}/ds4_cuda.o")
|
||||
list(APPEND DS4_OBJS
|
||||
"${DS4_DIR}/ds4_cuda.o"
|
||||
"${DS4_DIR}/cuda/mmq/ds4_ggml_stubs.o"
|
||||
"${DS4_DIR}/cuda/mmq/ds4_mmq.o"
|
||||
"${DS4_DIR}/cuda/mmq/ds4_mmq_d2r.o"
|
||||
"${DS4_DIR}/cuda/mmq/quantize.o"
|
||||
"${DS4_DIR}/cuda/mmq/mmid.o"
|
||||
"${DS4_DIR}/cuda/mmq/mmvq.o"
|
||||
"${DS4_DIR}/cuda/mmq/ds4_repack.o")
|
||||
elseif(DS4_GPU STREQUAL "metal")
|
||||
list(APPEND DS4_OBJS "${DS4_DIR}/ds4_metal.o")
|
||||
elseif(DS4_GPU STREQUAL "cpu")
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# ds4 backend Makefile.
|
||||
#
|
||||
# Upstream pin lives below as DS4_VERSION?=54b36ed9ba42da31b24f2d1a5feb075c2475dbb1
|
||||
# Upstream pin lives below as DS4_VERSION?=b7e9f0091139999b6c070a57590c447c5741da5c
|
||||
# (.github/bump_deps.sh) can find and update it - matches the
|
||||
# llama-cpp / ik-llama-cpp / turboquant convention.
|
||||
|
||||
DS4_VERSION?=54b36ed9ba42da31b24f2d1a5feb075c2475dbb1
|
||||
DS4_VERSION?=b7e9f0091139999b6c070a57590c447c5741da5c
|
||||
DS4_REPO?=https://github.com/antirez/ds4
|
||||
|
||||
CURRENT_MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
@@ -23,7 +23,9 @@ CMAKE_ARGS ?= -DCMAKE_BUILD_TYPE=Release
|
||||
# are shared by every GPU mode, so append them unconditionally below.
|
||||
ifeq ($(BUILD_TYPE),cublas)
|
||||
CMAKE_ARGS += -DDS4_GPU=cuda
|
||||
DS4_OBJ_TARGET := ds4.o ds4_cuda.o ds4_distributed.o ds4_tp.o ds4_ssd.o ds4_layer_pack.o
|
||||
DS4_OBJ_TARGET := ds4.o ds4_cuda.o ds4_distributed.o ds4_tp.o ds4_ssd.o ds4_layer_pack.o \
|
||||
cuda/mmq/ds4_ggml_stubs.o cuda/mmq/ds4_mmq.o cuda/mmq/ds4_mmq_d2r.o \
|
||||
cuda/mmq/quantize.o cuda/mmq/mmid.o cuda/mmq/mmvq.o cuda/mmq/ds4_repack.o
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
CMAKE_ARGS += -DDS4_GPU=metal
|
||||
DS4_OBJ_TARGET := ds4.o ds4_metal.o ds4_distributed.o ds4_tp.o ds4_ssd.o ds4_layer_pack.o
|
||||
@@ -55,7 +57,7 @@ ds4:
|
||||
# the right per-platform compile flags (Objective-C/Metal on Darwin, nvcc on Linux+CUDA).
|
||||
ds4/ds4.o: ds4
|
||||
ifeq ($(BUILD_TYPE),cublas)
|
||||
+$(MAKE) -C ds4 ds4.o ds4_cuda.o ds4_distributed.o ds4_tp.o ds4_ssd.o ds4_layer_pack.o
|
||||
+$(MAKE) -C ds4 $(DS4_OBJ_TARGET)
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
+$(MAKE) -C ds4 ds4.o ds4_metal.o ds4_distributed.o ds4_tp.o ds4_ssd.o ds4_layer_pack.o
|
||||
else
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
IK_LLAMA_VERSION?=cb9147fd0d9c08a9a84eee5ac405a73f4e10e3e1
|
||||
IK_LLAMA_VERSION?=60389410a1ff01f9d37dcc6261db33b3183bdea2
|
||||
LLAMA_REPO?=https://github.com/ikawrakow/ik_llama.cpp
|
||||
|
||||
CMAKE_ARGS?=
|
||||
|
||||
@@ -8,7 +8,7 @@ JOBS?=$(shell nproc --ignore=1)
|
||||
|
||||
# CrispASR version (release tag)
|
||||
CRISPASR_REPO?=https://github.com/CrispStrobe/CrispASR
|
||||
CRISPASR_VERSION?=fcb79282a6bc52e13d858026c42b24fb6e63c97a
|
||||
CRISPASR_VERSION?=fe3caf8e363b27572dbdd1a9d37083f25e6decda
|
||||
SO_TARGET?=libgocrispasr.so
|
||||
|
||||
CMAKE_ARGS+=-DBUILD_SHARED_LIBS=OFF
|
||||
|
||||
@@ -11,7 +11,30 @@ JOBS?=$(shell nproc --ignore=1 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || e
|
||||
|
||||
# vllm.cpp version
|
||||
VLLM_CPP_REPO?=https://github.com/mudler/vllm.cpp
|
||||
VLLM_CPP_VERSION?=9e1c9025ae61167a3335454d7cc0de6093c21845
|
||||
VLLM_CPP_VERSION?=9d1fad3cde0acb95eb0bb0a1025f40a0eb614147
|
||||
|
||||
# MLX GEMM provider (darwin/metal only; see the metal branch below for why).
|
||||
# Consumed as the prebuilt pip wheel: building MLX from source needs `xcrun
|
||||
# metal`, i.e. a full Xcode the macOS runners do not have, while the wheel ships
|
||||
# include/, lib/libmlx.dylib and the compiled mlx.metallib ready to link.
|
||||
#
|
||||
# DEFAULT ON, but ONLY because VLLM_CPP_VERSION above is pinned at or past
|
||||
# vllm.cpp 89c46aeb, which SHAPE-GATES the provider to prefill. The ordering is
|
||||
# load-bearing, not incidental:
|
||||
#
|
||||
# pin >= 89c46aeb, MLX on -> 99.1% of MLX-LM (gated: prefill only)
|
||||
# pin < 89c46aeb, MLX on -> ~51% (ungated: it also takes decode)
|
||||
#
|
||||
# MLX's steel GEMM wins prefill (537 ms TTFT against 602) and loses decode badly,
|
||||
# because the provider pays an mx::eval sync plus an output memcpy per call and
|
||||
# decode makes ~112 calls per TOKEN. Ungated it does both; gated it does only the
|
||||
# good half. So if this pin is ever moved BACKWARDS, this default must go with it.
|
||||
VLLM_CPP_MLX?=on
|
||||
MLX_VERSION?=0.29.4
|
||||
MLX_VENV?=$(abspath ./mlx-venv)
|
||||
# Resolved lazily (recursive `=`, not `:=`): the glob only matches once the venv
|
||||
# target has run, and the interpreter version in the path varies per runner.
|
||||
MLX_ROOT=$(shell echo $(MLX_VENV)/lib/python*/site-packages/mlx)
|
||||
|
||||
# The backend consumes only the stable C ABI (libvllm + include/vllm.h), so the
|
||||
# server, examples and tests of the engine are never built here.
|
||||
@@ -49,6 +72,23 @@ else ifeq ($(BUILD_TYPE),vulkan)
|
||||
CMAKE_ARGS+=-DVLLM_CPP_VULKAN=ON -DVLLM_CPP_CUDA=OFF
|
||||
else ifeq ($(BUILD_TYPE),metal)
|
||||
CMAKE_ARGS+=-DVLLM_CPP_METAL=ON
|
||||
# The optional MLX GEMM provider. vllm.cpp keeps it OFF by default because it
|
||||
# is a ~19 MB libmlx.dylib plus a ~105 MB mlx.metallib, and upstream's
|
||||
# position is that it must earn that cost by measurement. It does, on the
|
||||
# only hardware this build targets: measured on an Apple M4 against the
|
||||
# native MSL GEMM in the SAME binary (arms toggled by
|
||||
# VT_OP_PROVIDER_DISABLE=mlx), Qwen3-1.7B-bf16 p=512 g=128, it is 1.5x to
|
||||
# 2.2x aggregate throughput and 2x to 3x faster TTFT, at equal peak memory
|
||||
# and bit-identical output on every parity shape. See vllm.cpp
|
||||
# docs/BENCHMARKS.md "MLX GEMM provider A/B on Apple M4".
|
||||
#
|
||||
# MLX delegates the dense GEMM ONLY: kPagedAttention stays vllm.cpp's own
|
||||
# kernel, because MLX has no paged-KV primitive at all.
|
||||
#
|
||||
# Set VLLM_CPP_MLX=off for a Metal build without it (smaller image, slower).
|
||||
ifeq ($(VLLM_CPP_MLX),on)
|
||||
MLX_ENABLED=1
|
||||
endif
|
||||
else
|
||||
CMAKE_ARGS+=-DVLLM_CPP_CUDA=OFF
|
||||
endif
|
||||
@@ -68,10 +108,35 @@ sources/vllm.cpp:
|
||||
git fetch --depth 1 origin $(VLLM_CPP_VERSION) && \
|
||||
git checkout FETCH_HEAD
|
||||
|
||||
$(LIB): sources/vllm.cpp
|
||||
ifeq ($(MLX_ENABLED),1)
|
||||
# A stamp FILE, not a phony target: a phony prerequisite is always "newer" than
|
||||
# $(LIB) and would re-link libvllm on every invocation. Keyed on the version so
|
||||
# a MLX_VERSION bump reinstalls instead of silently reusing the old wheel.
|
||||
MLX_STAMP=$(MLX_VENV)/.mlx-$(MLX_VERSION).stamp
|
||||
MLX_CMAKE_ARGS=-DVLLM_CPP_MLX=ON -DMLX_ROOT=$(MLX_ROOT)
|
||||
|
||||
$(MLX_STAMP):
|
||||
@if [ ! -x "$(MLX_VENV)/bin/pip" ]; then \
|
||||
python3 -m venv "$(MLX_VENV)" || { echo "vllm-cpp: python3 with venv is required to build the MLX provider; pass VLLM_CPP_MLX=off to build Metal without it" >&2; exit 1; }; \
|
||||
fi
|
||||
"$(MLX_VENV)"/bin/pip install --quiet --disable-pip-version-check "mlx==$(MLX_VERSION)"
|
||||
@# Resolved in the SHELL, not by $(MLX_ROOT): make expands a whole recipe
|
||||
@# before running its first line, so the glob would still be unmatched here.
|
||||
@# Every later use (the cmake args, package.sh) expands after this target has
|
||||
@# completed, where $(MLX_ROOT) does resolve.
|
||||
@root=$$(echo "$(MLX_VENV)"/lib/python*/site-packages/mlx); \
|
||||
test -f "$$root/lib/libmlx.dylib" -a -f "$$root/include/mlx/array.h" || \
|
||||
{ echo "vllm-cpp: mlx==$(MLX_VERSION) did not provide lib/libmlx.dylib + include/mlx/array.h under $$root" >&2; exit 1; }
|
||||
touch $@
|
||||
else
|
||||
MLX_STAMP=
|
||||
MLX_CMAKE_ARGS=
|
||||
endif
|
||||
|
||||
$(LIB): sources/vllm.cpp $(MLX_STAMP)
|
||||
mkdir -p build && \
|
||||
cd build && \
|
||||
cmake ../sources/vllm.cpp $(CMAKE_ARGS) && \
|
||||
cmake ../sources/vllm.cpp $(CMAKE_ARGS) $(MLX_CMAKE_ARGS) && \
|
||||
cmake --build . --config Release -j$(JOBS) --target vllm_shared
|
||||
cp -fL build/$(LIB) ./$(LIB)
|
||||
|
||||
@@ -79,12 +144,12 @@ vllm-cpp: main.go govllmcpp.go backend.go options.go $(LIB)
|
||||
CGO_ENABLED=0 $(GOCMD) build -tags "$(GO_TAGS)" -o vllm-cpp ./
|
||||
|
||||
package: vllm-cpp
|
||||
bash package.sh
|
||||
MLX_ROOT="$(MLX_ROOT)" bash package.sh
|
||||
|
||||
build: package
|
||||
|
||||
clean: purge
|
||||
rm -rf libvllm.so libvllm.dylib package sources/vllm.cpp vllm-cpp
|
||||
rm -rf libvllm.so libvllm.dylib package sources/vllm.cpp vllm-cpp "$(MLX_VENV)"
|
||||
|
||||
purge:
|
||||
rm -rf build
|
||||
|
||||
@@ -41,5 +41,50 @@ options:
|
||||
- max_num_seqs:16
|
||||
```
|
||||
|
||||
## Apple Silicon: the MLX GEMM provider (ON by default, gated to prefill)
|
||||
|
||||
`BUILD_TYPE=metal` builds vllm.cpp's MLX provider for the dense GEMM
|
||||
(`VLLM_CPP_MLX=on`, the default here). It is on because upstream now SHAPE-GATES
|
||||
it to prefill; it was briefly off in this branch's history, and that was correct
|
||||
at the time for an ungated provider.
|
||||
|
||||
The gate matters more than the flag. MLX's steel GEMM wins prefill but loses
|
||||
decode, because the provider pays an `mx::eval` synchronisation plus an output
|
||||
memcpy on every call and decode makes ~112 calls *per token*. Measured on an
|
||||
Apple M4, Qwen3-1.7B-bf16 warm at p=512 g=128:
|
||||
|
||||
| configuration | prefill TTFT | warm throughput |
|
||||
|---|--:|--:|
|
||||
| MLX **gated to prefill** (pin >= 89c46aeb) | **524.5 ms** | **24.37 tok/s, 97.6% of MLX-LM** |
|
||||
| MLX ungated (older pins) | 537 ms | 12.7 tok/s |
|
||||
| MLX off | 602 ms | 23.9 tok/s, 95.9% |
|
||||
|
||||
Ratios are against an MLX-LM baseline measured INTERLEAVED with ours over four
|
||||
ABBA blocks (its spread 0.34%, ours 0.12%). An earlier revision of this file
|
||||
claimed 99.1%; that used a two-run MLX-LM baseline containing an outlier and
|
||||
overstated us by about 1.5 points.
|
||||
|
||||
**`VLLM_CPP_VERSION` and this flag are coupled.** Moving the pin back before
|
||||
`89c46aeb` while leaving `VLLM_CPP_MLX=on` would take the middle row — roughly
|
||||
half throughput. If you roll the pin back, roll the default back with it.
|
||||
|
||||
One caveat: MLX's GEMM is not bit-identical to the native kernel, so an MLX build
|
||||
produces a different greedy sequence than a non-MLX one. That is a property of the
|
||||
provider, not of the gate, and it predates this packaging. Full disposition in
|
||||
vllm.cpp `docs/BENCHMARKS.md`.
|
||||
|
||||
Build knobs:
|
||||
|
||||
- `VLLM_CPP_MLX=off` builds Metal without the provider: ~124 MB smaller, and
|
||||
96.4% of MLX-LM instead of 99.1%.
|
||||
- `MLX_VERSION` pins the wheel (default `0.29.4`). MLX is consumed as the
|
||||
prebuilt pip wheel because building it from source needs `xcrun metal`, i.e. a
|
||||
full Xcode the macOS runners do not have.
|
||||
|
||||
Packaging vendors `libmlx.dylib`, `mlx.metallib` and MLX's MIT license into
|
||||
`package/lib/`, and rewrites `libvllm.dylib`'s rpath to `@loader_path/lib`
|
||||
(re-signing it, since `install_name_tool` invalidates the signature). The
|
||||
metallib must stay beside `libmlx.dylib`: MLX looks for it there.
|
||||
|
||||
Testing: `make test` runs the unit specs; export `VLLM_CPP_MODEL=<model>` (and
|
||||
optionally `VLLM_CPP_LIBRARY=<libvllm path>`) to enable the e2e specs.
|
||||
|
||||
@@ -43,6 +43,50 @@ elif [ -f "/lib/ld-linux-aarch64.so.1" ]; then
|
||||
cp -arfLv /lib/aarch64-linux-gnu/libpthread.so.0 $CURDIR/package/lib/libpthread.so.0
|
||||
elif [ $(uname -s) = "Darwin" ]; then
|
||||
echo "Detected Darwin"
|
||||
# Vendor the optional MLX GEMM provider, when libvllm was built against it.
|
||||
# Three facts drive every line below, each verified on an Apple M4 before it
|
||||
# was written:
|
||||
# 1. libvllm.dylib carries an LC_LOAD_DYLIB on @rpath/libmlx.dylib, and its
|
||||
# build-time LC_RPATH points inside the build venv. That path does not
|
||||
# exist on a user's machine, so it must become @loader_path/lib.
|
||||
# 2. MLX finds its ~100 MB mlx.metallib beside its OWN dylib, so the two
|
||||
# files have to land in the same directory or every Metal op dies with
|
||||
# "Failed to load the default metallib".
|
||||
# 3. install_name_tool invalidates the code signature, and macOS refuses to
|
||||
# load an arm64 image whose signature does not match, so the patched
|
||||
# library must be re-signed ad-hoc afterwards.
|
||||
if otool -L "$CURDIR/package/libvllm.dylib" 2>/dev/null | grep -q "libmlx.dylib"; then
|
||||
MLX_LIB_DIR="${MLX_ROOT}/lib"
|
||||
if [ ! -f "$MLX_LIB_DIR/libmlx.dylib" ] || [ ! -f "$MLX_LIB_DIR/mlx.metallib" ]; then
|
||||
echo "Error: libvllm.dylib links libmlx.dylib but $MLX_LIB_DIR is missing libmlx.dylib/mlx.metallib" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Vendoring the MLX GEMM provider from $MLX_LIB_DIR"
|
||||
cp -fLv "$MLX_LIB_DIR/libmlx.dylib" "$CURDIR/package/lib/"
|
||||
cp -fLv "$MLX_LIB_DIR/mlx.metallib" "$CURDIR/package/lib/"
|
||||
# MLX is MIT and we redistribute its binaries, so its license ships with
|
||||
# them. mlx-metal is the wheel carrying the dylib and the metallib.
|
||||
MLX_LICENSE=$(ls "${MLX_ROOT}"/../mlx_metal-*.dist-info/licenses/LICENSE 2>/dev/null | head -1)
|
||||
if [ -z "$MLX_LICENSE" ]; then
|
||||
MLX_LICENSE=$(ls "${MLX_ROOT}"/../mlx-*.dist-info/licenses/LICENSE 2>/dev/null | head -1)
|
||||
fi
|
||||
if [ -z "$MLX_LICENSE" ]; then
|
||||
echo "Error: could not find the MLX LICENSE to redistribute alongside libmlx.dylib" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp -fLv "$MLX_LICENSE" "$CURDIR/package/lib/LICENSE.mlx"
|
||||
# Drop every build-tree rpath, then point at the packaged copy.
|
||||
otool -l "$CURDIR/package/libvllm.dylib" | awk '/LC_RPATH/{f=1;next} f&&/ path /{print $2;f=0}' | while read -r rp; do
|
||||
install_name_tool -delete_rpath "$rp" "$CURDIR/package/libvllm.dylib" 2>/dev/null || true
|
||||
done
|
||||
install_name_tool -add_rpath "@loader_path/lib" "$CURDIR/package/libvllm.dylib"
|
||||
codesign -f -s - "$CURDIR/package/libvllm.dylib"
|
||||
# A broken rpath must fail the BUILD, not the user's first inference.
|
||||
if ! otool -l "$CURDIR/package/libvllm.dylib" | grep -q "@loader_path/lib"; then
|
||||
echo "Error: libvllm.dylib did not get the @loader_path/lib rpath" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Error: Could not detect architecture"
|
||||
exit 1
|
||||
|
||||
@@ -8,7 +8,7 @@ JOBS?=$(shell nproc --ignore=1)
|
||||
|
||||
# whisper.cpp version
|
||||
WHISPER_REPO?=https://github.com/ggml-org/whisper.cpp
|
||||
WHISPER_CPP_VERSION?=2ca53bb45e38748d07b310eeb36245a7157ac882
|
||||
WHISPER_CPP_VERSION?=64d57d3df5c8dacee098577257edcaa154bf5ef3
|
||||
SO_TARGET?=libgowhisper.so
|
||||
|
||||
CMAKE_ARGS+=-DBUILD_SHARED_LIBS=OFF
|
||||
|
||||
@@ -6,4 +6,9 @@ else
|
||||
source $backend_dir/../common/libbackend.sh
|
||||
fi
|
||||
|
||||
cuda_home=${CUDA_HOME:-/usr/local/cuda}
|
||||
if [ -z "${TRITON_PTXAS_PATH:-}" ] && [ -x "$cuda_home/bin/ptxas" ]; then
|
||||
export TRITON_PTXAS_PATH="$cuda_home/bin/ptxas"
|
||||
fi
|
||||
|
||||
startBackend $@
|
||||
|
||||
90
core/http/react-ui/package-lock.json
generated
90
core/http/react-ui/package-lock.json
generated
@@ -21,9 +21,10 @@
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@modelcontextprotocol/ext-apps": "^1.2.2",
|
||||
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||
"@modelcontextprotocol/sdk": "^1.30.0",
|
||||
"dompurify": "^3.4.12",
|
||||
"highlight.js": "^11.11.1",
|
||||
"hono": "4.12.34",
|
||||
"i18next": "^26.0.8",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"i18next-http-backend": "^3.0.6",
|
||||
@@ -635,12 +636,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@hono/node-server": {
|
||||
"version": "1.19.14",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
|
||||
"integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.0.tgz",
|
||||
"integrity": "sha512-XovyyCCnBzW+zKu+z/zq8hwNs4KOR5rEMAOxo2f40Q5xoOI37IMm6MIg2COOUtUApo0i6850MTBKH2u4QLGIqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.14.1"
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"hono": "^4"
|
||||
@@ -944,11 +945,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "1.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz",
|
||||
"integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==",
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz",
|
||||
"integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.19.9",
|
||||
"@hono/node-server": "^1.19.9 || ^2.0.5",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^3.0.1",
|
||||
"content-type": "^1.0.5",
|
||||
@@ -1718,10 +1720,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -2876,9 +2879,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
|
||||
"integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -3432,9 +3435,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.12.31",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.31.tgz",
|
||||
"integrity": "sha512-zJIHFrl6bq3RDd2YusFNCDlM8qUprxKswyi/OPzPyzKDdyBXDqWx8bZlZ7R+saTdSTatUmb3O7K4SspGPaEOQg==",
|
||||
"version": "4.12.34",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.34.tgz",
|
||||
"integrity": "sha512-GqXJqY/xJkJmuloTrnV1ZEXG3fqte+VjkUqoRNZXcrUidiUOP4fMSIHHY4tsqZBK++kVyWmt/AAfSUuy57/eSA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
@@ -4193,9 +4196,9 @@
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
|
||||
"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz",
|
||||
"integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
@@ -4383,16 +4386,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-processinfo/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-processinfo/node_modules/glob": {
|
||||
@@ -5278,16 +5281,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nyc/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/nyc/node_modules/convert-source-map": {
|
||||
@@ -5974,10 +5977,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/quick-temp/node_modules/brace-expansion": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
|
||||
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -6569,16 +6573,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/spawn-wrap/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/spawn-wrap/node_modules/foreground-child": {
|
||||
@@ -6902,16 +6906,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/glob": {
|
||||
@@ -7134,9 +7138,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
|
||||
"integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
|
||||
"integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"coverage:report": "nyc report"
|
||||
},
|
||||
"overrides": {
|
||||
"hono": "4.12.25"
|
||||
"hono": "4.12.34"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
@@ -35,10 +35,10 @@
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@modelcontextprotocol/ext-apps": "^1.2.2",
|
||||
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||
"@modelcontextprotocol/sdk": "^1.30.0",
|
||||
"dompurify": "^3.4.12",
|
||||
"highlight.js": "^11.11.1",
|
||||
"hono": "4.12.25",
|
||||
"hono": "4.12.34",
|
||||
"i18next": "^26.0.8",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"i18next-http-backend": "^3.0.6",
|
||||
|
||||
@@ -54,62 +54,57 @@ var _ = Describe("RunLeaderLoop", func() {
|
||||
close(done)
|
||||
}()
|
||||
|
||||
// Let it run a bit then cancel
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
Eventually(func() int32 {
|
||||
return atomic.LoadInt32(&callCount)
|
||||
}, 500*time.Millisecond, 10*time.Millisecond).Should(BeNumerically(">=", 1))
|
||||
cancel()
|
||||
|
||||
// RunLeaderLoop should return
|
||||
Eventually(done, 500*time.Millisecond).Should(BeClosed())
|
||||
|
||||
// Record count after cancellation
|
||||
countAfterCancel := atomic.LoadInt32(&callCount)
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
countLater := atomic.LoadInt32(&callCount)
|
||||
|
||||
Expect(countLater).To(Equal(countAfterCancel),
|
||||
"function should stop being called after context cancellation")
|
||||
})
|
||||
|
||||
It("only one leader executes at a time (two concurrent loops)", func() {
|
||||
db := testutil.SetupTestDB()
|
||||
const lockKey int64 = 5002
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
maxRunning int32
|
||||
running int32
|
||||
)
|
||||
var running int32
|
||||
entered := make(chan struct{}, 2)
|
||||
release := make(chan struct{})
|
||||
var releaseOnce sync.Once
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
done := make(chan struct{}, 2)
|
||||
DeferCleanup(func() {
|
||||
cancel()
|
||||
releaseOnce.Do(func() { close(release) })
|
||||
})
|
||||
|
||||
fn := func() {
|
||||
cur := atomic.AddInt32(&running, 1)
|
||||
mu.Lock()
|
||||
if cur > maxRunning {
|
||||
maxRunning = cur
|
||||
atomic.AddInt32(&running, 1)
|
||||
select {
|
||||
case entered <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
|
||||
<-release
|
||||
atomic.AddInt32(&running, -1)
|
||||
}
|
||||
|
||||
// Start two competing leader loops with the same lock key
|
||||
go RunLeaderLoop(ctx, db, lockKey, 50*time.Millisecond, fn)
|
||||
go RunLeaderLoop(ctx, db, lockKey, 50*time.Millisecond, fn)
|
||||
for range 2 {
|
||||
go func() {
|
||||
RunLeaderLoop(ctx, db, lockKey, 1*time.Millisecond, fn)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
||||
Eventually(entered, 500*time.Millisecond).Should(Receive())
|
||||
Consistently(func() int32 {
|
||||
return atomic.LoadInt32(&running)
|
||||
}, 50*time.Millisecond, 5*time.Millisecond).Should(Equal(int32(1)),
|
||||
"expected only the lock holder to run while both loops tick")
|
||||
|
||||
// Let them run for a while
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
cancel()
|
||||
|
||||
mu.Lock()
|
||||
observed := maxRunning
|
||||
mu.Unlock()
|
||||
|
||||
Expect(observed).To(BeNumerically("<=", 1),
|
||||
"expected at most 1 goroutine running the leader function at a time")
|
||||
releaseOnce.Do(func() { close(release) })
|
||||
Eventually(done, 500*time.Millisecond).Should(Receive())
|
||||
Eventually(done, 500*time.Millisecond).Should(Receive())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -192,6 +192,24 @@ You can use the env variable COQUI_LANGUAGE to set the language used by the coqu
|
||||
|
||||
You can also use config files to configure tts models (see section below on how to use config files).
|
||||
|
||||
### Fish Speech
|
||||
|
||||
Fish Speech models accept the `compile` backend option. Enabling it can improve
|
||||
inference performance on CUDA hardware, but the first request after loading the
|
||||
model includes the `torch.compile` warmup cost:
|
||||
|
||||
```yaml
|
||||
backend: fish-speech
|
||||
options:
|
||||
- compile:true
|
||||
```
|
||||
|
||||
When compilation is enabled, the backend uses the CUDA toolkit's executable
|
||||
`ptxas` from `$CUDA_HOME/bin` (defaulting to `/usr/local/cuda/bin`) instead of
|
||||
the copy bundled with Triton. This allows newer GPU architectures supported by
|
||||
the installed CUDA toolkit to compile kernels. Set `TRITON_PTXAS_PATH` on the
|
||||
backend explicitly to select a different assembler.
|
||||
|
||||
### Piper
|
||||
|
||||
To install the `piper` audio models manually:
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
files:
|
||||
- filename: DeepSeek-V4-Flash-0731-MXFP4.gguf
|
||||
uri: huggingface://ggml-org/DeepSeek-V4-Flash-0731-GGUF/DeepSeek-V4-Flash-0731-MXFP4.gguf
|
||||
sha256: c8b46876c3939a6e141f9e4d4aa422981df4a9b84f19e9bb4e1c9a28be31e484
|
||||
sha256: 65f73494afaf27d3add0751a5b716dd2d3e012c66ae0dbbcc1bf8477f92b3ab7
|
||||
- name: instella-moe-16b-a3b-think
|
||||
url: github:mudler/LocalAI/gallery/virtual.yaml@master
|
||||
urls:
|
||||
@@ -311,7 +311,7 @@
|
||||
files:
|
||||
- filename: llama-cpp/models/Parable-Granite-4.1-3B-Claude-Fable-5-Q4_K_M/Parable-Granite-4.1-3B-Claude-Fable-5-GGUF-Q4_K_M.gguf
|
||||
uri: https://huggingface.co/AnkitAI/Parable-Granite-4.1-3B-Claude-Fable-5-GGUF/resolve/main/Parable-Granite-4.1-3B-Claude-Fable-5-GGUF-Q4_K_M.gguf
|
||||
sha256: 67dc7695d92939c713165761f115c9d892fdff74fcbd987c8bb453b9b8ab645d
|
||||
sha256: dbf202638af23e72508d8316577655d24ba2037fda51ce802b8996977e290bce
|
||||
- name: "parable-qwen3-4b-claude-fable-5"
|
||||
url: "github:mudler/LocalAI/gallery/virtual.yaml@master"
|
||||
urls:
|
||||
@@ -345,7 +345,7 @@
|
||||
files:
|
||||
- filename: llama-cpp/models/Parable-Qwen3-4B-Claude-Fable-5-Q4_K_M/Parable-Qwen3-4B-Claude-Fable-5-GGUF-Q4_K_M.gguf
|
||||
uri: https://huggingface.co/AnkitAI/Parable-Qwen3-4B-Claude-Fable-5-GGUF/resolve/main/Parable-Qwen3-4B-Claude-Fable-5-GGUF-Q4_K_M.gguf
|
||||
sha256: c94b06a912aa901f3da5689754577ad534415efafc50dcee3f389594a153bf38
|
||||
sha256: 65cc4824fb78ecaf55afdfcdb6dd2e27e1aa805d289db89eae94d32d450403f0
|
||||
- name: "parable-granite-4.1-8b-claude-fable-5"
|
||||
url: "github:mudler/LocalAI/gallery/virtual.yaml@master"
|
||||
urls:
|
||||
@@ -381,7 +381,7 @@
|
||||
files:
|
||||
- filename: llama-cpp/models/Parable-Granite-4.1-8B-Claude-Fable-5-Q4_K_M/Parable-Granite-4.1-8B-Claude-Fable-5-GGUF-Q4_K_M.gguf
|
||||
uri: https://huggingface.co/AnkitAI/Parable-Granite-4.1-8B-Claude-Fable-5-GGUF/resolve/main/Parable-Granite-4.1-8B-Claude-Fable-5-GGUF-Q4_K_M.gguf
|
||||
sha256: 61a8133c344a0d0a00188395afe33c803e3b973cb4bbfd5ef1fa7110e80bc1c3
|
||||
sha256: 57e464ae3d35253d4351639757dc35e71bab8324d12d49a5870695ce73dc19cf
|
||||
- name: "parable-qwen3-8b-claude-fable-5"
|
||||
url: "github:mudler/LocalAI/gallery/virtual.yaml@master"
|
||||
urls:
|
||||
@@ -415,7 +415,7 @@
|
||||
files:
|
||||
- filename: llama-cpp/models/Parable-Qwen3-8B-Claude-Fable-5-Q4_K_M/Parable-Qwen3-8B-Claude-Fable-5-GGUF-Q4_K_M.gguf
|
||||
uri: https://huggingface.co/AnkitAI/Parable-Qwen3-8B-Claude-Fable-5-GGUF/resolve/main/Parable-Qwen3-8B-Claude-Fable-5-GGUF-Q4_K_M.gguf
|
||||
sha256: 956070afc8023b8665fe450842f7be76b505b53d142460fd9b588222f4e16112
|
||||
sha256: 4532d2379d38a37279866a030e51d419561f9d4d22fee00d2a33647d66f05065
|
||||
- &pocket-35b
|
||||
name: "pocket-35b"
|
||||
variants:
|
||||
@@ -785,35 +785,18 @@
|
||||
- name: "qwen3.6-35b-a3b-uncensored-genesis-hermes-v6"
|
||||
url: "github:mudler/LocalAI/gallery/virtual.yaml@master"
|
||||
urls:
|
||||
- https://huggingface.co/HauhauCS/Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive
|
||||
- https://huggingface.co/LuffyTheFox/Qwen3.6-35B-A3B-Uncensored-Genesis-Hermes-V6-GGUF
|
||||
description: |
|
||||
# Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive
|
||||
Qwen3.6-35B-A3B Uncensored Genesis Hermes V6 is LuffyTheFox's multimodal,
|
||||
agentic derivative of HauhauCS's uncensored Qwen3.6-35B-A3B model. It
|
||||
combines Genesis tensor calibration with Hermes function-calling data while
|
||||
retaining the 35B mixture-of-experts architecture, roughly 3B active
|
||||
parameters per token, and the native 262K-token context window.
|
||||
|
||||
> **Join the Discord** for updates, roadmaps, projects, or just to chat.
|
||||
|
||||
Qwen3.6-35B-A3B uncensored by HauhauCS. **0/465 Refusals.**
|
||||
|
||||
> **HuggingFace's "Hardware Compatibility" widget doesn't recognize K_P quants** — it may show fewer files than actually exist. Click **"View +X variants"** or go to **Files and versions** to see all available downloads.
|
||||
|
||||
## About
|
||||
|
||||
No changes to datasets or capabilities. Fully functional, 100% of what the original authors intended - just without the refusals.
|
||||
|
||||
These are meant to be the best lossless uncensored models out there.
|
||||
|
||||
## Aggressive Variant
|
||||
|
||||
Stronger uncensoring — model is fully unlocked and won't refuse prompts. May occasionally append short disclaimers (baked into base model training, not refusals) but full content is always generated.
|
||||
|
||||
For a more conservative uncensor that keeps some safety guardrails, check the Balanced variant when it's available.
|
||||
|
||||
## Downloads
|
||||
|
||||
All quants generated with importance matrix (imatrix) for optimal quality preservation on abliterated weights.
|
||||
|
||||
## What are K_P quants?
|
||||
|
||||
...
|
||||
This entry installs the Q8_0 GGUF together with its F16 multimodal projector
|
||||
for llama.cpp. The model card recommends Jinja chat templates and at least a
|
||||
128K context for its thinking behavior. License: Apache-2.0.
|
||||
license: "apache-2.0"
|
||||
tags:
|
||||
- llm
|
||||
@@ -2009,7 +1992,7 @@
|
||||
files:
|
||||
- filename: ds4flash.gguf
|
||||
uri: https://huggingface.co/unsloth/DeepSeek-V4-Flash-GGUF
|
||||
sha256: 856c407993ccffa9ad52e23fbef8bb7b458c792a52278f4ca7931741b0c20ce2
|
||||
sha256: 1bfdafd1c288eb1b2bcb629ee9e1b7567dcf0abbe4d20995905a3c3465e9bd1e
|
||||
- name: "qwopus3.6-35b-a3b-coder-mtp"
|
||||
url: "github:mudler/LocalAI/gallery/virtual.yaml@master"
|
||||
urls:
|
||||
|
||||
39
scripts/build/fish-speech-ptxas_test.sh
Normal file
39
scripts/build/fish-speech-ptxas_test.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
REPO_ROOT=$(dirname "$(dirname "$(dirname "$(realpath "$0")")")")
|
||||
BACKEND_DIR="$WORK/fish-speech"
|
||||
mkdir -p "$BACKEND_DIR/common" "$WORK/cuda/bin"
|
||||
cp "$REPO_ROOT/backend/python/fish-speech/run.sh" "$BACKEND_DIR/run.sh"
|
||||
|
||||
cat > "$BACKEND_DIR/common/libbackend.sh" <<'LIBBACKEND'
|
||||
startBackend() {
|
||||
printf '%s\n' "${TRITON_PTXAS_PATH:-}"
|
||||
}
|
||||
LIBBACKEND
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
touch "$WORK/cuda/bin/ptxas"
|
||||
chmod +x "$WORK/cuda/bin/ptxas"
|
||||
|
||||
got=$(CUDA_HOME="$WORK/cuda" bash "$BACKEND_DIR/run.sh")
|
||||
[ "$got" = "$WORK/cuda/bin/ptxas" ] || \
|
||||
fail "expected toolkit ptxas, got '$got'"
|
||||
|
||||
got=$(CUDA_HOME="$WORK/cuda" TRITON_PTXAS_PATH=/custom/ptxas \
|
||||
bash "$BACKEND_DIR/run.sh")
|
||||
[ "$got" = "/custom/ptxas" ] || \
|
||||
fail "explicit TRITON_PTXAS_PATH was overwritten with '$got'"
|
||||
|
||||
chmod -x "$WORK/cuda/bin/ptxas"
|
||||
got=$(CUDA_HOME="$WORK/cuda" bash "$BACKEND_DIR/run.sh")
|
||||
[ -z "$got" ] || fail "non-executable ptxas was selected as '$got'"
|
||||
|
||||
echo "PASS: fish-speech selects a usable toolkit ptxas"
|
||||
@@ -29,4 +29,11 @@ assert_target arm64 "" llama-cpp-cpu-all
|
||||
assert_target amd64 sycl_f16 llama-cpp-fallback
|
||||
assert_target amd64 sycl_f32 llama-cpp-fallback
|
||||
|
||||
# ROCm exhausts the same 6h budget through volume rather than a stall: hipcc
|
||||
# compiles ggml's HIP kernels once per AMDGPU target, eleven of them, and the
|
||||
# CPU variant matrix goes on top. 2h27m before it was added, killed at exactly
|
||||
# 6h00m on every run since.
|
||||
assert_target amd64 hipblas llama-cpp-fallback
|
||||
assert_target arm64 hipblas llama-cpp-fallback
|
||||
|
||||
echo "PASS: llama.cpp build target preserves CPU variants where supported"
|
||||
|
||||
59
website/content/blog/what-landed-in-localai-3-10.md
Normal file
59
website/content/blog/what-landed-in-localai-3-10.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: "LocalAI 3.10: the Anthropic and Responses APIs, and one image for every GPU"
|
||||
date: 2026-01-18
|
||||
author: "Ettore Di Giacinto"
|
||||
category: "Release"
|
||||
tags: ["release", "anthropic", "open-responses", "gpu", "moonshine"]
|
||||
summary: "A /v1/messages endpoint that Claude clients can talk to unchanged, Open Responses compatibility that passes the official acceptance tests, and GPU libraries moved inside the backend containers so one image works on any hardware."
|
||||
extracss: ["blog.css"]
|
||||
---
|
||||
|
||||
Half the tooling worth using speaks a shape of API that is not OpenAI's. You find a client you like, it talks to Anthropic, and swapping it onto a local model means either rewriting the client or gluing a translation layer in front of it. Same story with the agent frameworks that went all in on the Responses API.
|
||||
|
||||
3.10.0 adds both surfaces natively, so the client does not have to know.
|
||||
|
||||
## Two more front doors
|
||||
|
||||
The Anthropic Messages API is served at `/v1/messages`, and at `/messages` for clients that do not prefix. Tool calling, streaming and non-streaming all work, so `anthropic-sdk-go`, LangChain and anything else built on that shape can be pointed at your instance without a code change.
|
||||
|
||||
The Open Responses API is at `/v1/responses`, with `/v1/responses/:id` to fetch one and `/v1/responses/:id/cancel` to stop it. It is stateful: pass a `response_id` and the conversation resumes, set `background: true` and the agent runs asynchronously while you go and do something else, then come back for the result. Streaming covers tools, images and audio.
|
||||
|
||||
That one passes the [official acceptance tests](https://www.openresponses.org/compliance), which was the bar I wanted to hit before shipping it.
|
||||
|
||||
## One image for every GPU
|
||||
|
||||
This is the change most likely to affect you even if you do not care about agents.
|
||||
|
||||
GPU libraries (CUDA, ROCm, Vulkan) now live inside the backend containers rather than in the image you pull. There is no longer a CUDA image, a ROCm image and a CPU image to choose between. You pull the image, and acceleration works if the hardware is there! Vulkan arm64 builds are in too.
|
||||
|
||||
It is experimental, and I want to be clear about that rather than bury it. It is a real architectural change to how every backend gets its libraries, and there will be hardware combinations we did not hit. If it does not work on yours, please file an issue, that is genuinely the most useful thing you can do for this one.
|
||||
|
||||
## Everything else
|
||||
|
||||
The backend gallery is system aware now, so it only lists backends your machine can actually run. No more scrolling past MLX entries on a Linux box.
|
||||
|
||||
Tool calls stream properly, including partial arguments as `input_json_delta`, and models that emit tools as XML (`<function>...</function>`) get parsed instead of dumping the markup into the message text. Both work across llama.cpp, vLLM and diffusers.
|
||||
|
||||
Thinking tags are extracted into a separate `reasoning` field rather than being left in the answer, in both SSE and non-SSE mode. The chat UI shows them under a Thinking tab.
|
||||
|
||||
There is a video generation page in the web UI with LTX-2 behind it, doing text-to-video and image-to-video with the usual `fps`, `num_frames` and `guidance_scale` controls.
|
||||
|
||||
There is request tracing now. `GET /api/traces` returns in-memory request and response logs, `/api/traces/clear` empties them. It is memory backed and drops old entries past a size cap, so it is for debugging an agent that is misbehaving right now, not for an audit trail.
|
||||
|
||||
Two new speech backends. Moonshine is an ONNX transcription engine aimed at low-end hardware, and it is the one to reach for on a Pi or an old laptop. It is quick! Pocket-TTS does lightweight TTS with voice cloning, though the cloning path needs a HuggingFace login and a registered voice model, so it is not quite copy-paste.
|
||||
|
||||
## Old hardware, and AMD memory
|
||||
|
||||
Two fixes worth calling out because they were silent failures rather than errors.
|
||||
|
||||
LocalAI was crashing on Intel CPUs without BMI2 (Sandy Bridge, Ivy Bridge), showing up as an `EOF` during model warmup rather than anything that pointed at the cause. It now falls back to `llama-cpp-fallback` on those chips.
|
||||
|
||||
On AMD, used and total VRAM were swapped when parsing `rocm-smi` output, so a dual-Radeon box reported nonsense. `HIP_VISIBLE_DEVICES` is also handled properly now, which matters if you are pinning to the discrete GPU.
|
||||
|
||||
## Thanks
|
||||
|
||||
Thanks to @richiejp, @majiayu000, @nanoandrew4, @DEVMANISHOFFL, @coffeerunhobby, @rampa3, @Nold360, @jroeber and @Divyanshupandey007 for the work in this cycle.
|
||||
|
||||
If the unified GPU backends misbehave on your setup, open an issue with what hardware you are on. And if you are wiring up the Anthropic or Responses endpoints and something does not match the spec, tell me, I would rather hear it from you than find out later.
|
||||
|
||||
[Full release notes](https://github.com/mudler/LocalAI/releases/tag/v3.10.0).
|
||||
69
website/content/blog/what-landed-in-localai-4-0.md
Normal file
69
website/content/blog/what-landed-in-localai-4-0.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: "LocalAI 4.0: agents in the core, and a React interface"
|
||||
date: 2026-03-14
|
||||
author: "Ettore Di Giacinto"
|
||||
category: "Release"
|
||||
tags: ["release", "agents", "agenthub", "mcp", "react", "webrtc"]
|
||||
summary: "Native agent orchestration with the Agenthub, a rewritten interface with Canvas mode, MCP Apps with tool streaming, and two things removed."
|
||||
extracss: ["blog.css"]
|
||||
---
|
||||
|
||||
Running an agent locally has meant running two things: an inference server, and a separate orchestrator that talks to it. That is a lot of moving parts for something you wanted to try on a Tuesday evening.
|
||||
|
||||
4.0.0 puts the agent side in the core. You create agents, give them memory and skills, connect them to MCP servers, and start and stop them from the same interface you already use for models.
|
||||
|
||||
This is a major version bump, so there are two removals near the bottom of this post. Read those before you upgrade.
|
||||
|
||||
## Agents, and the Agenthub
|
||||
|
||||
Agents are managed through the React interface: create one, wire up MCP servers and skills, connect it to Slack, watch what it is doing through a new Events column in the agents list.
|
||||
|
||||
Memory has two options. Hybrid search backed by PostgreSQL if you already run one, or in-memory storage via Chromem if you do not want another service. Skills live in a central database rather than being pasted per agent.
|
||||
|
||||
The bit I am most curious to see used is [Agenthub](https://agenthub.localai.io), a community space for sharing agent configurations. You publish one, somebody else imports it into their instance and runs it against their own models on their own hardware!
|
||||
|
||||
## The interface is React now
|
||||
|
||||
The web interface has been rewritten. The old one had reached the point where adding anything meant fighting it.
|
||||
|
||||
Canvas mode is the new thing worth turning on: enable it in chat and code blocks and artifacts the model produces render in a preview pane on the right instead of scrolling past you as text. The System view splits Models and Backends into tabs. Traces render as accordions, which makes a long one readable. And if you try to install a model whose weights exceed your system RAM, you get a warning first rather than a locked-up machine.
|
||||
|
||||
## MCP Apps
|
||||
|
||||
Client-side MCP support is complete in this release ([#8947](https://github.com/mudler/LocalAI/pull/8947)). You pick which MCP servers to enable for a chat directly in the interface, and their tools get injected into the normal chat with streaming, so there is no separate agent mode to switch into.
|
||||
|
||||
If you would rather not have any of it, `LOCALAI_DISABLE_MCP` turns the whole thing off.
|
||||
|
||||
## Audio, video, and MLX across machines
|
||||
|
||||
WebRTC is wired into the Realtime API and the Talk page ([#8790](https://github.com/mudler/LocalAI/pull/8790)), which is a real improvement for latency over what was there before.
|
||||
|
||||
Three new audio backends: fish-speech, ace-step.cpp, and faster-qwen3-tts (CUDA only). TTS gained `sample_rate` support through post-processing, and Qwen TTS handles multiple voices.
|
||||
|
||||
There is also an experimental MLX distributed backend for spreading a workload across Apple machines ([#8801](https://github.com/mudler/LocalAI/pull/8801)). It is early, so expect rough edges if you try it.
|
||||
|
||||
## Infrastructure
|
||||
|
||||
Persistent data now has its own location, separate from configuration. `LOCALAI_DATA_PATH` (or `--data-path`) points at where agents, skills, tasks, jobs and the collection database live, defaulting to `data/` under the base path. If you are mounting volumes, this is the one to look at.
|
||||
|
||||
Shell completion scripts generate for bash, zsh and fish. There is dedicated Podman documentation now, including rootless setup.
|
||||
|
||||
## Two things are gone
|
||||
|
||||
The HuggingFace backend has been removed.
|
||||
|
||||
AIO images are dropped. They existed to bundle a preset of models with the runtime, and maintaining them across every hardware variant stopped being worth what they gave people. Use the main images and install models from the gallery.
|
||||
|
||||
## One known issue
|
||||
|
||||
The `diffusers` backend is not in this release. It failed to build because we exhausted our CI limits, so the previous version is still what you get if you install it.
|
||||
|
||||
This is an infrastructure problem, not a code one, and it is the kind of thing that will keep happening to us. If you know anybody at GitHub who could help us get better ARM runners, please reach out, I am not too proud to ask.
|
||||
|
||||
## Thanks
|
||||
|
||||
Thanks to @richiejp, @nanoandrew4, @Weathercold, @sozercan, @lukasdotcom, @loryanstrant, @bittoby and @attilagyorffy.
|
||||
|
||||
If you build an agent worth sharing, put it on the Agenthub. The more the merrier!
|
||||
|
||||
[Full release notes](https://github.com/mudler/LocalAI/releases/tag/v4.0.0).
|
||||
76
website/content/blog/what-landed-in-localai-4-1.md
Normal file
76
website/content/blog/what-landed-in-localai-4-1.md
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: "LocalAI 4.1: more than one box, and more than one user"
|
||||
date: 2026-04-02
|
||||
author: "Ettore Di Giacinto"
|
||||
category: "Release"
|
||||
tags: ["release", "distributed", "auth", "oidc", "quotas", "fine-tuning"]
|
||||
summary: "Distributed cluster mode that places requests by real free VRAM, OIDC with per-user API keys and quotas, and LoRA fine-tuning that exports straight to GGUF."
|
||||
extracss: ["blog.css"]
|
||||
---
|
||||
|
||||
Two problems show up the moment LocalAI stops being a thing you run for yourself.
|
||||
|
||||
The first is that you have more than one machine, and only one of them is doing any work. The second is that other people are using your instance, and you have no way to tell who is burning the GPU, or to stop them.
|
||||
|
||||
4.1.0 is mostly about those two.
|
||||
|
||||
## Running as a cluster
|
||||
|
||||
Distributed mode lets you point several nodes at one control plane and stop thinking about which one to call.
|
||||
|
||||
Routing orders nodes by available VRAM, so the request lands on the card with room for it. Node groups let you pin models to a subset of the cluster, which is how you keep a heavy diffusion model off the boxes doing embeddings. There is a min/max autoscaler with a reconciler managing node lifecycle, and you can drain a node for maintenance and resume it later through the API instead of pulling it out from under in-flight requests.
|
||||
|
||||
Model transfer between nodes goes over S3 or peer to peer, so a model you have already pulled once does not have to come down from the internet again on every node!
|
||||
|
||||
The cluster status shows up on the home page.
|
||||
|
||||
## Users, keys and quotas
|
||||
|
||||
LocalAI ships a multi-user platform now, which is the piece that makes it deployable for a team or a classroom rather than just for you.
|
||||
|
||||
- User management from the React interface.
|
||||
- OIDC/OAuth against your own identity provider (Google, Keycloak, Authentik, whatever you already run).
|
||||
- Invite mode, so registration is closed unless an admin lets somebody in.
|
||||
- Per-user API keys.
|
||||
- Admin impersonation, for when somebody reports a bug you cannot reproduce.
|
||||
|
||||
On top of that there is a quota system: set per-user limits and have them enforced, with a usage dashboard broken down per user and a predictive view of where consumption is heading.
|
||||
|
||||
## Fine-tuning without leaving the interface
|
||||
|
||||
Both of these are experimental. I would use them on something you can afford to throw away.
|
||||
|
||||
Fine-tuning uses HuggingFace TRL to train LoRA adapters, exports the result to GGUF automatically, and imports it back into LocalAI so you can serve what you just trained without moving files around by hand. There is a small evals framework included to check whether the thing you trained is actually better.
|
||||
|
||||
The quantization backend produces optimized variants of a model on the fly.
|
||||
|
||||
## Agents from the terminal
|
||||
|
||||
You can run an agent without the server now:
|
||||
|
||||
```sh
|
||||
local-ai agent run <name>
|
||||
local-ai agent list
|
||||
```
|
||||
|
||||
`run` takes an agent from the pool registry in `pool.json`, or a single-turn `--prompt` if you just want one answer. Tool calls stream in real time, and the interleaved-thinking bug that mangled output when a model reasoned mid-tool-call is fixed.
|
||||
|
||||
## The rest of the interface work
|
||||
|
||||
The model pipeline editor is visual, so wiring models together no longer means editing YAML. Backend logs can be scoped to a single model rather than reading the whole stream. Studio pages remember past generations, so images and audio you made last week are still there. The model and backend selectors are searchable. Error toasts link straight to the trace that produced them.
|
||||
|
||||
## Under the hood
|
||||
|
||||
Inference defaults are pulled from Unsloth and applied across all endpoints and gallery models, so models arrive with sane sampling parameters instead of whatever the default happened to be. `min_p` is supported. When native tool-call parsing fails, an iterative fallback parser takes over rather than returning nothing.
|
||||
|
||||
Repeated log lines get collapsed. NVIDIA Jetson and Tegra are detected as first-class platforms. SYCL backends auto-disable `mmap`, which was crashing them on Intel GPUs. llama.cpp bundles `libdl`, `librt` and `libpthread` for portability. And the downloader rewrites HuggingFace URIs through `HF_ENDPOINT`, which is the one you need if you are behind a corporate mirror.
|
||||
|
||||
## Thanks
|
||||
|
||||
Thanks to @richiejp for a large chunk of this cycle, and to @tv42, @walcz-de, @majiayu000 and @ER-EPR.
|
||||
|
||||
There is a full setup walkthrough on video if you would rather watch than read: [youtube.com/watch?v=cMVNnlqwfw4](https://www.youtube.com/watch?v=cMVNnlqwfw4).
|
||||
|
||||
If you are setting up distributed mode or OIDC and hit a wall, reach out, I am happy to help you get it standing up.
|
||||
|
||||
[Full release notes](https://github.com/mudler/LocalAI/releases/tag/v4.1.0).
|
||||
117
website/content/blog/what-landed-in-localai-4-2.md
Normal file
117
website/content/blog/what-landed-in-localai-4-2.md
Normal file
@@ -0,0 +1,117 @@
|
||||
---
|
||||
title: "LocalAI 4.2: who spoke when, and whose face is that"
|
||||
date: 2026-05-11
|
||||
author: "Ettore Di Giacinto"
|
||||
category: "Release"
|
||||
tags: ["release", "diarization", "voice-recognition", "face-recognition", "ollama", "backends"]
|
||||
summary: "A /v1/audio/diarization endpoint, voice and face recognition with liveness, a drop-in Ollama API, and eleven new backends."
|
||||
extracss: ["blog.css"]
|
||||
---
|
||||
|
||||
You record an hour of standup, run it through Whisper, and get back one long wall of text. Every word is correct. You still have no idea who said any of them, so you end up scrubbing through the audio with the transcript open in another window, guessing at voices.
|
||||
|
||||
4.2.0 is mostly about that class of problem. Audio and images carry more than "here are the words" or "here is a picture", and until now LocalAI had nowhere to put the rest of it.
|
||||
|
||||
## Who spoke when
|
||||
|
||||
There is a new `/v1/audio/diarization` endpoint, shaped like `/v1/audio/transcriptions` so your existing multipart code mostly carries over:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8080/v1/audio/diarization \
|
||||
-H "Content-Type: multipart/form-data" \
|
||||
-F file="@meeting.wav" \
|
||||
-F model="vibevoice-cpp-asr" \
|
||||
-F num_speakers=3
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"task": "diarize",
|
||||
"duration": 12.34,
|
||||
"num_speakers": 2,
|
||||
"segments": [
|
||||
{"id": 0, "speaker": "SPEAKER_00", "label": "0", "start": 0.00, "end": 2.34},
|
||||
{"id": 1, "speaker": "SPEAKER_01", "label": "1", "start": 2.34, "end": 4.10}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Two backends serve it. [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) does pure diarization (pyannote-3.0 segmentation, a speaker-embedding extractor, then clustering) and never transcribes, so you do not pay for ASR you did not ask for. `vibevoice-cpp` emits speaker-labelled segments as a by-product of its long-form ASR pass, so with `include_text=true` you get a transcript per segment for free! `response_format` gives you `json`, `verbose_json`, or `rttm` if you want to feed the output to `dscore`.
|
||||
|
||||
One thing to know before you build on it: `SPEAKER_00` is local to a single request. Run the same meeting twice and the numbering can come out differently, and nothing promises that `SPEAKER_00` in Monday's recording is the same human as `SPEAKER_00` in Tuesday's. If you need identity across files, pair it with `/v1/voice/embed` and keep your own embedding store. Which brings me to..
|
||||
|
||||
## Voices and faces
|
||||
|
||||
`/v1/voice/*` is new ([#9500](https://github.com/mudler/LocalAI/pull/9500)): verify (are these two clips the same person?), identify (which of my enrolled speakers is this?), embed (give me the vector, I will do the rest myself), and analyze (age, gender, emotion).
|
||||
|
||||
```bash
|
||||
local-ai models install speechbrain-ecapa-tdnn
|
||||
|
||||
curl -sX POST http://localhost:8080/v1/voice/verify \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "speechbrain-ecapa-tdnn",
|
||||
"audio1": "https://example.com/alice_1.wav",
|
||||
"audio2": "https://example.com/alice_2.wav"
|
||||
}'
|
||||
```
|
||||
|
||||
```json
|
||||
{"verified": true, "distance": 0.18, "threshold": 0.25}
|
||||
```
|
||||
|
||||
The default threshold is around 0.25 for ECAPA-TDNN, and it moves per engine, so pass `threshold` explicitly if you swap the model out.
|
||||
|
||||
`/v1/face/*` does the same thing for faces ([#9480](https://github.com/mudler/LocalAI/pull/9480)), plus detection and demographics, and 4.2.0 adds antispoofing. Holding a printed photo or a phone screen up to the camera is the oldest attack on face auth there is, and the liveness check rejects it.
|
||||
|
||||
Some honest limits. Liveness is an arms race and this is not bank-grade. The demographic heads emit confident-looking numbers for age and emotion that you should read as a rough signal and not as a fact about a person. And the default `insightface` buffalo packs are released for non-commercial research use only, so if you are shipping this in a product, pick the OpenCV Zoo entry instead. That is in the docs, but people skip docs, so it is here too.
|
||||
|
||||
The samples never leave your machine, which is the part I actually care about. They go from your process to the backend running next to it and nowhere else. Doing biometrics against somebody else's cloud API always felt like the worst possible trade.
|
||||
|
||||
## Point your ollama client at LocalAI
|
||||
|
||||
```sh
|
||||
OLLAMA_HOST=http://localhost:8080 ollama run qwen3
|
||||
```
|
||||
|
||||
LocalAI answers the Ollama API now ([#9284](https://github.com/mudler/LocalAI/pull/9284)), so a tool that only ever learned to talk to Ollama keeps working with no code change on your side. `/api/chat`, `/api/generate`, `/api/embed`, `/api/tags`, `/api/show`, `/api/ps` and `/api/version` all land on the engine you were already running, and your existing `/v1/*` clients are untouched.
|
||||
|
||||
There is no `/api/pull` in there. Models come from the LocalAI gallery or from a URL you hand it, so `ollama run` against something you have not installed yet will not go and fetch it for you.
|
||||
|
||||
## Video, and an interface repaint
|
||||
|
||||
`stable-diffusion.ggml` generates video now ([#9420](https://github.com/mudler/LocalAI/pull/9420))! There are gallery entries for Wan 2.1 FLF2V 14B 720P and Wan i2v 720p, including first-last-frame interpolation.
|
||||
|
||||
The React interface got a long cycle of work. The chat is redesigned, the palette moved to Nord, and there is i18n across English, Italiano, Español, Deutsch and 简体中文. You can brand your instance too - name, tagline, logo, favicon - and the login page, sidebar, footer and browser tab all pick it up. Handy if you run LocalAI for a team and would rather it did not look like somebody's side project.
|
||||
|
||||
The model config editor is interactive now, with autocomplete over known fields and live validation, and it renames the file on save so you stop accumulating three copies of the same config.
|
||||
|
||||
## Eleven new backends
|
||||
|
||||
sglang, ik-llama.cpp, TurboQuant, sam.cpp, Kokoros, qwen3tts.cpp, tinygrad-multimodal (experimental, do not build anything load-bearing on it yet), vibevoice.cpp, LocalVQE, insightface, and voice-rec.
|
||||
|
||||
vLLM reached feature parity with llama.cpp in this cycle. The full `AsyncEngineArgs` surface is exposed as a generic YAML map, and tensor-parallel distributed workers let a single model span nodes. There are CUDA 13 builds for vLLM, vLLM-omni and sglang, plus L4T arm64 for Jetson-class boards.
|
||||
|
||||
## The unglamorous half
|
||||
|
||||
Most of the 279 pull requests here are not features. A sample of what actually went in:
|
||||
|
||||
- llama.cpp renamed its `common` target to `llama-common`, which broke the TurboQuant build until the detection was fixed.
|
||||
- ik-llama.cpp needed a patch to `clip.cpp` for the new `ggml_quantize_chunk` signature, plus adapting to the `common_grammar` struct in `sampling.h`.
|
||||
- `mlx-vlm` is pinned to v0.4.4 to unblock CUDA builds.
|
||||
- vLLM dropped the flash-attn wheel to avoid a torch 2.10 ABI mismatch.
|
||||
- Whisper transcriptions can be cancelled by the client, through the ggml `abort_callback`, so aborting a request frees the GPU instead of letting it run to completion in the background.
|
||||
- faster-whisper emits word-level timestamps.
|
||||
- gfx1151 (Strix Halo / Ryzen AI MAX) works, with `AMDGPU_TARGETS` exposed as a build-arg.
|
||||
|
||||
On the security side: an unsafe `sprintf()` came out of the C++ grpc-server, env-supplied API keys are stripped from Settings API requests before they get persisted so they cannot leak back out through the config, and deleting a user on PostgreSQL cascades across everything they owned instead of leaving orphaned rows behind.
|
||||
|
||||
Distributed mode got a hardening pass. Round-robin across replicas of the same model, "Upgrade All" scoped to the nodes that actually have the backend installed, NATS `backend.upgrade` split off from install, and correct VRAM/RAM reporting on NVIDIA unified-memory hosts.
|
||||
|
||||
## Thanks
|
||||
|
||||
This one had a lot of hands on it. Thanks to @richiejp for the model config editor, Kokoros and a pile of build fixes, @Anai-Guo, @russell, @leinasi2014, @keithmattix for gfx1151, @orbisai0security and @SAY-5 for the security work, @walcz-de, @thelittlefireman, @sec171, @pjbrzozowski, @mvanhorn, @arteven, @Dennisadira, @eglia, @arbrick, @neurocis and @ER-EPR.
|
||||
|
||||
If you are wiring up diarization or the voice endpoints and get stuck, open an issue or reach out, I am genuinely happy to help you get it working.
|
||||
|
||||
[Full release notes](https://github.com/mudler/LocalAI/releases/tag/v4.2.0).
|
||||
105
website/content/blog/what-landed-in-localai-4-3.md
Normal file
105
website/content/blog/what-landed-in-localai-4-3.md
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: "LocalAI 4.3: signed backends, and the prompt cache that was off"
|
||||
date: 2026-05-24
|
||||
author: "Ettore Di Giacinto"
|
||||
category: "Release"
|
||||
tags: ["release", "security", "cosign", "prompt-cache", "distributed", "usage"]
|
||||
summary: "Keyless cosign verification for backend OCI images, the llama.cpp prompt cache enabled by default, per-API-key usage attribution, and the replica-pinning bug that kept a second node idle."
|
||||
extracss: ["blog.css"]
|
||||
---
|
||||
|
||||
Here is a gap that had been sitting in LocalAI for a while. The gallery YAML tells LocalAI which OCI image to pull for a backend, and then LocalAI pulls it. Nothing checked that the bytes coming back were the bytes we built. A compromised registry, or somebody in the middle, and you would never know.
|
||||
|
||||
4.3.0 closes that, and fixes a default that had been quietly costing everybody a lot of prefill time.
|
||||
|
||||
## Signed backends
|
||||
|
||||
Every backend image merged by CI is now signed with [sigstore](https://www.sigstore.dev/)/cosign, keyless via Fulcio and Rekor, including each per-arch entry under the manifest list ([#9823](https://github.com/mudler/LocalAI/pull/9823)). It uses OCI 1.1 referrers rather than the legacy `:tag.sig` convention.
|
||||
|
||||
On your side, verification runs against a policy that the gallery declares:
|
||||
|
||||
```yaml
|
||||
verification:
|
||||
issuer_regex: "^https://token\\.actions\\.githubusercontent\\.com$"
|
||||
identity_regex: "^https://github\\.com/mudler/LocalAI/\\.github/workflows/backend_merge\\.yml@.*$"
|
||||
not_before: "2026-05-22T00:00:00Z"
|
||||
```
|
||||
|
||||
A few details that took some thinking.
|
||||
|
||||
`not_before` is the revocation lever. Keyless Fulcio certificates are ephemeral, so there is nothing to revoke on the signing side. Revocation has to be policy side: move the date forward in the gallery YAML and every signature older than it stops validating.
|
||||
|
||||
The TUF trusted root is cached process-wide, so installing ten backends from one gallery does one fetch instead of ten.
|
||||
|
||||
Digest pinning closes the window between verifying and pulling, which is otherwise a TOCTOU you could drive a truck through.
|
||||
|
||||
Strict mode is `--require-backend-integrity`, or `LOCALAI_REQUIRE_BACKEND_INTEGRITY=true`. It turns a missing policy or an empty SHA256 from a warning into a hard failure.
|
||||
|
||||
Now the honest part: strict mode is opt-in and off by default, and until a gallery ships a `verification:` block, installs go through with a warning. The default `backend/index.yaml` does not have the blocks populated yet, that is the next step. So today this is machinery that works and is not yet enforcing much. Turn on strict mode in production once your gallery is populated, not before, or you will just break your own installs.
|
||||
|
||||
## The prompt cache was off
|
||||
|
||||
`llama-cpp` has a server-side prompt cache. LocalAI was not enabling it. So every agent turn, every coding-assistant call, every OpenAI-compatible CLI with a long system prompt, re-prefilled that whole prompt from scratch.
|
||||
|
||||
On the reported workload, a repeated system prompt took 5 to 8 minutes per call before this change and seconds after it. Your numbers will depend on how long your prompt is and what hardware you are on.
|
||||
|
||||
Two defaults flipped ([#9925](https://github.com/mudler/LocalAI/pull/9925), [#9951](https://github.com/mudler/LocalAI/pull/9951)):
|
||||
|
||||
1. `kv_unified` is now `true` in `grpc-server.cpp`. The old `false` was silently force-disabling `cache_idle_slots` at server init, so the host prompt cache got allocated and then never written across requests. That is the one that actually explains the behaviour.
|
||||
2. `prompt_cache_all` defaults to `true` at the YAML layer, matching upstream llama.cpp's own default in `common.h`. The per-request `cache_prompt` knob is on out of the box.
|
||||
|
||||
You can opt out with `options: ["kv_unified:false"]` or `prompt_cache_all: false`, and there are new keys (`cache_idle_slots`, `checkpoint_every_nt`) if you want to tune it. The model configuration docs got a worked example for the repeated-system-prompt case and an explanation of how `kv_unified`, `cache_ram` and `cache_idle_slots` interact, because they interact in ways that are not obvious.
|
||||
|
||||
## Who is burning the GPU
|
||||
|
||||
The usage page could tell you how many tokens were spent. It could not tell you who spent them ([#9920](https://github.com/mudler/LocalAI/pull/9920)).
|
||||
|
||||
`usage_records` gained a `Source` column (`apikey`, `web`, `legacy`) plus the API key id and name, with an idempotent backfill of older rows on `InitDB`. The auth middleware passes the resolved key and the request source through, and usage middleware snapshots the key id and name at write time, so a key you revoke later still reads correctly in history (it renders as `(revoked)` rather than vanishing).
|
||||
|
||||
Two new endpoints:
|
||||
|
||||
```
|
||||
GET /api/auth/usage/sources # your own
|
||||
GET /api/auth/admin/usage/sources # everyone, with user_id / api_key_id filters
|
||||
```
|
||||
|
||||
The admin view truncates at 200 keys. The React usage page gained a Sources tab with a source-mix ribbon, a top-7-plus-Other time chart, and a sortable table. Web interface session traffic is split per user instead of being lumped into one global row.
|
||||
|
||||
## Distributed v3, and one good bug
|
||||
|
||||
This one is worth writing down because the symptom and the cause were far apart.
|
||||
|
||||
An operator reported this:
|
||||
|
||||
```
|
||||
dgx-spark1 loaded in_flight=6
|
||||
nvidia-thor1 loaded in_flight=0
|
||||
```
|
||||
|
||||
Two replicas of the same model, one taking everything, one idle forever. The round-robin was there and looked correct.
|
||||
|
||||
The cause: `ModelLoader.Load` cached a `*Model` whose embedded `InFlightTrackingClient` was bound to a single `(nodeID, replicaIndex)`. The first request picked a node and got wrapped. Every request after that reused the wrapper, so it kept going to whichever node won the first pick, even after the reconciler scaled the model out. The routing code was fine. It just was not being consulted again!
|
||||
|
||||
`SmartRouter.Route` now runs per request ([#9968](https://github.com/mudler/LocalAI/pull/9968)), the `in_flight ASC, last_used ASC, available_vram DESC` ordering actually fires, and replica selection lives in one place (`PickBestReplica`) with a spec asserting the SQL `ORDER BY` and the Go picker agree on a seeded dataset. `probeHealth` is memoized per `(nodeID, addr)` with a 30 second TTL and `singleflight` coalescing, because llama.cpp serializes `HealthCheck` against in-flight `Predict` and a burst of new requests would otherwise stall on it.
|
||||
|
||||
Two other distributed changes.
|
||||
|
||||
`POST /api/nodes/:id/backends/install` used to block for up to 3 minutes while the worker pulled the image, which froze the Backends picker in the interface. It returns HTTP 202 and a `jobID` immediately now ([#9928](https://github.com/mudler/LocalAI/pull/9928)). Install and upgrade timeouts are configurable via `LOCALAI_NATS_BACKEND_INSTALL_TIMEOUT` and `LOCALAI_NATS_BACKEND_UPGRADE_TIMEOUT`, defaulting to 15 minutes instead of the hardcoded 3. A NATS round-trip timeout while the worker is still pulling reports as `running_on_worker` rather than a hard failure.
|
||||
|
||||
Workers also publish debounced install progress (~250ms) that the master forwards into the operations status ([#9958](https://github.com/mudler/LocalAI/pull/9958)), so distributed installs show per-byte progress the same way local ones do. Old workers stay silent and new masters tolerate the silence, so mixed-version clusters keep working.
|
||||
|
||||
## Smaller things
|
||||
|
||||
`LOCALAI_TRACING_MAX_BODY_BYTES` caps trace payload size, which stops the admin Traces page from trying to render a 40 MB embedding response.
|
||||
|
||||
There is a `flake.nix` with a dev shell for NixOS users who do not want to go through Docker.
|
||||
|
||||
The `vllm`, `sglang` and `vllm-omni` L4T13 backends are back for Jetson and DGX boxes, switched to PyPI aarch64+cu130 wheels to fix the torch 2.10 ABI mismatch.
|
||||
|
||||
A distributed test harness landed in `tests/distributed/`, aimed at catching the class of regression the replica-pinning bug belonged to.
|
||||
|
||||
## Thanks
|
||||
|
||||
If you run LocalAI in production, the two things to look at here are strict mode (once your gallery has a `verification:` block) and whether the prompt cache change speeds up your workload. I would like to hear numbers from real setups, mine are one data point.
|
||||
|
||||
[Full release notes](https://github.com/mudler/LocalAI/releases/tag/v4.3.0).
|
||||
Reference in New Issue
Block a user