mirror of
https://github.com/mudler/LocalAI.git
synced 2026-06-25 00:59:28 -04:00
Compare commits
175 Commits
feat/dllm-
...
fix/parake
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56c50c4b66 | ||
|
|
9f7c58d69c | ||
|
|
e1994579f8 | ||
|
|
e5620989dd | ||
|
|
fc618dcee6 | ||
|
|
e6042080c0 | ||
|
|
0f3b24436d | ||
|
|
4b6f911835 | ||
|
|
a5e28942a6 | ||
|
|
dba9cd7ca4 | ||
|
|
c93190de50 | ||
|
|
4dbf69f889 | ||
|
|
deb430f3ec | ||
|
|
dd8c8778e2 | ||
|
|
06a7b6cadb | ||
|
|
67c8889866 | ||
|
|
1d49041c85 | ||
|
|
2edc4e25b3 | ||
|
|
7888067914 | ||
|
|
9eedbf537a | ||
|
|
69c16481c8 | ||
|
|
56f8a6623f | ||
|
|
4755d676a3 | ||
|
|
10184b5e28 | ||
|
|
fdf475ec5f | ||
|
|
9d54a599b0 | ||
|
|
63bcbf6c12 | ||
|
|
95b058e1c5 | ||
|
|
f2abcc7503 | ||
|
|
62c99c10b3 | ||
|
|
7226bb9f30 | ||
|
|
569d9bbd9e | ||
|
|
682fb2718c | ||
|
|
20c643e1f6 | ||
|
|
64a4351f3a | ||
|
|
b7d67f5779 | ||
|
|
600dafd20b | ||
|
|
ce8a3e9266 | ||
|
|
a88d9d2de3 | ||
|
|
1cf1bf32e1 | ||
|
|
f45c6acc54 | ||
|
|
1a1bd57469 | ||
|
|
1f29e96030 | ||
|
|
64560a974b | ||
|
|
32c47706ae | ||
|
|
e58870a573 | ||
|
|
8fab1d2e45 | ||
|
|
7b462a0d51 | ||
|
|
aed181e6c1 | ||
|
|
a556cd9afc | ||
|
|
b50b1fe418 | ||
|
|
b4c0dc67fe | ||
|
|
01fa12e0de | ||
|
|
cf7f9573a2 | ||
|
|
c6303104c7 | ||
|
|
3e96d811b7 | ||
|
|
23f225260c | ||
|
|
aef10723c9 | ||
|
|
9565db5f94 | ||
|
|
e19c43cf04 | ||
|
|
b081247d95 | ||
|
|
1be959ce30 | ||
|
|
518381278e | ||
|
|
93706fec57 | ||
|
|
11aee03a80 | ||
|
|
8915f2ab91 | ||
|
|
f143d7f688 | ||
|
|
dd928f0bdd | ||
|
|
c43a752afc | ||
|
|
079ac0e15a | ||
|
|
2e734bf560 | ||
|
|
72d46c1115 | ||
|
|
606128e4e9 | ||
|
|
59c7ad5153 | ||
|
|
78d682224a | ||
|
|
29dbba7a25 | ||
|
|
4ad754eea3 | ||
|
|
67692cb984 | ||
|
|
f68edfc85f | ||
|
|
c3b3336654 | ||
|
|
c4cd86bb15 | ||
|
|
13f59f0822 | ||
|
|
3fa7b2955c | ||
|
|
c133ca39dc | ||
|
|
757822cd74 | ||
|
|
91f97f2a54 | ||
|
|
55f9ff6805 | ||
|
|
88726f2da4 | ||
|
|
5c2ae7857a | ||
|
|
4af360300f | ||
|
|
5ac864dbed | ||
|
|
9b57dcb721 | ||
|
|
95e7149c87 | ||
|
|
fd26c8c753 | ||
|
|
e60c094a7d | ||
|
|
159df8e2ef | ||
|
|
de299ca101 | ||
|
|
980ec4a311 | ||
|
|
dfd5a00e6f | ||
|
|
63be479066 | ||
|
|
4c6750fe6b | ||
|
|
a6e1c6d0b3 | ||
|
|
294170d3ed | ||
|
|
1ab61a0875 | ||
|
|
f44034021e | ||
|
|
6b9f1bd4b3 | ||
|
|
416f871bea | ||
|
|
8bd2df8f68 | ||
|
|
6799d802d3 | ||
|
|
40cc549882 | ||
|
|
3d295adfa8 | ||
|
|
4fa2064875 | ||
|
|
cb74399b3a | ||
|
|
2388686369 | ||
|
|
edc61053aa | ||
|
|
9ba8521e7e | ||
|
|
51c23197ed | ||
|
|
2df2876db2 | ||
|
|
f648f07b13 | ||
|
|
1dedb5277c | ||
|
|
7d2a762b53 | ||
|
|
61cde6fd77 | ||
|
|
ca1668dd85 | ||
|
|
fdc352a618 | ||
|
|
692970e507 | ||
|
|
e046a7749f | ||
|
|
e5c95e0449 | ||
|
|
4d3d54d61b | ||
|
|
36e3419203 | ||
|
|
4ec6e3221e | ||
|
|
4bb592cf91 | ||
|
|
3e838c0cff | ||
|
|
36b4a81d1e | ||
|
|
0854932a25 | ||
|
|
203410871b | ||
|
|
7637f8cf1b | ||
|
|
f0e001b7f8 | ||
|
|
cf9debf4eb | ||
|
|
e1556aa1dc | ||
|
|
53cbb578a9 | ||
|
|
99c8205740 | ||
|
|
d7162b9f89 | ||
|
|
3351b62c91 | ||
|
|
0eca930b8d | ||
|
|
81ab62e874 | ||
|
|
0413fc03f8 | ||
|
|
7088572f75 | ||
|
|
c1e8440f5b | ||
|
|
8f0059123b | ||
|
|
a906438a69 | ||
|
|
d28a5b6da1 | ||
|
|
edeacf22c4 | ||
|
|
51f4f67c47 | ||
|
|
cf71e291b4 | ||
|
|
a7a7bd646b | ||
|
|
cec93d2e00 | ||
|
|
722bdb87e9 | ||
|
|
50dea8c983 | ||
|
|
46ba70632b | ||
|
|
60facc7252 | ||
|
|
8c8204d3c4 | ||
|
|
4ce0f6102a | ||
|
|
085fc53bbc | ||
|
|
56cc4f63fc | ||
|
|
a53f34e78f | ||
|
|
1cea96f09f | ||
|
|
006a9d38c7 | ||
|
|
892ce951ce | ||
|
|
7cda221d36 | ||
|
|
9a88eb81e7 | ||
|
|
58cdc050e9 | ||
|
|
b962f4a192 | ||
|
|
b6fcb3e1db | ||
|
|
ff09683d84 | ||
|
|
f618636c71 |
@@ -198,6 +198,27 @@ docker-build-backends: ... docker-build-<backend-name>
|
|||||||
- If the backend is in `backend/python/<backend-name>/` but uses `.` as context in the workflow file, use `.` context
|
- If the backend is in `backend/python/<backend-name>/` but uses `.` as context in the workflow file, use `.` context
|
||||||
- Check similar backends to determine the correct context
|
- Check similar backends to determine the correct context
|
||||||
|
|
||||||
|
## Documenting the backend (README + docs)
|
||||||
|
|
||||||
|
A backend is not "added" until it is discoverable. Update the user-facing docs:
|
||||||
|
|
||||||
|
- **`docs/content/features/backends.md`** - add the backend to the right
|
||||||
|
category in the "LocalAI supports various types of backends" list (and add a
|
||||||
|
new category if it introduces a new modality, e.g. sound classification).
|
||||||
|
- If the backend introduces a **new API surface** (a new endpoint or a realtime
|
||||||
|
capability), document it under `docs/content/` where its area lives (audio,
|
||||||
|
vision, etc.) and follow the api-endpoints checklist in
|
||||||
|
[api-endpoints-and-auth.md](api-endpoints-and-auth.md).
|
||||||
|
|
||||||
|
**If the backend is a native C/C++/GGML engine created and maintained by the
|
||||||
|
LocalAI team** (a from-scratch port like `parakeet.cpp`, `ced.cpp`,
|
||||||
|
`vibevoice.cpp`, `rf-detr.cpp`, not a wrapper around a third-party runtime), it
|
||||||
|
ALSO belongs in the top-level **`README.md`** table under "native C/C++/GGML
|
||||||
|
engines ... developed and maintained by the LocalAI project itself". Add a row
|
||||||
|
linking the upstream engine repo with a one-line description. This is the
|
||||||
|
project's showcase of its own engines; a new in-house backend that is missing
|
||||||
|
from it is a documentation bug.
|
||||||
|
|
||||||
## 5. Verification Checklist
|
## 5. Verification Checklist
|
||||||
|
|
||||||
After adding a new backend, verify:
|
After adding a new backend, verify:
|
||||||
@@ -211,6 +232,8 @@ After adding a new backend, verify:
|
|||||||
- [ ] No YAML syntax errors (check with linter)
|
- [ ] No YAML syntax errors (check with linter)
|
||||||
- [ ] No Makefile syntax errors (check with linter)
|
- [ ] No Makefile syntax errors (check with linter)
|
||||||
- [ ] Follows the same pattern as similar backends (e.g., if it's a transcription backend, follow `faster-whisper` pattern)
|
- [ ] Follows the same pattern as similar backends (e.g., if it's a transcription backend, follow `faster-whisper` pattern)
|
||||||
|
- [ ] Documented: added to the category list in `docs/content/features/backends.md` (and any new endpoint/realtime capability documented under `docs/content/`)
|
||||||
|
- [ ] If it is an in-house native C/C++/GGML engine, added to the maintained-engines table in the top-level `README.md`
|
||||||
|
|
||||||
## Bundling runtime shared libraries (`package.sh`)
|
## Bundling runtime shared libraries (`package.sh`)
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,39 @@ maps to `DS4_THINK_HIGH`. We pass the chosen mode to `ds4_chat_append_assistant_
|
|||||||
via `ModelOptions.Options[] = "kv_cache_dir:/some/path"`. Format is **our own** -
|
via `ModelOptions.Options[] = "kv_cache_dir:/some/path"`. Format is **our own** -
|
||||||
NOT bit-compatible with ds4-server's KVC files (interop is a follow-up plan).
|
NOT bit-compatible with ds4-server's KVC files (interop is a follow-up plan).
|
||||||
|
|
||||||
|
## Engine options (LoadModel)
|
||||||
|
|
||||||
|
`LoadModel` maps `ModelOptions.Options[]` (`"key:value"`, from model-YAML
|
||||||
|
`options:`) onto `ds4_engine_options` through a **declarative table**
|
||||||
|
(`kEngineOptSpecs` + `apply_engine_option` in `grpc-server.cpp`). The struct is
|
||||||
|
plain C with no reflection, so the field set is enumerated once in the table;
|
||||||
|
adding a future engine knob is a one-line table row, not a new branch. Unknown
|
||||||
|
keys are ignored (back-compat). A bare flag (`ssd_streaming` with no value)
|
||||||
|
means `true`. Path-type values (`mtp_path`, `expert_profile_path`,
|
||||||
|
`directional_steering_file`) resolve **relative to the model directory**, so a
|
||||||
|
gallery entry can reference a companion file it downloaded by bare filename;
|
||||||
|
absolute values pass through. `ds4_role` / `ds4_layers` / `ds4_listen` /
|
||||||
|
`ds4_route_timeout` / `kv_cache_dir` keep their dedicated handling (validation
|
||||||
|
+ coordinator wiring) and are not in the table.
|
||||||
|
|
||||||
|
Wired keys: `mtp_path`, `mtp_draft`, `mtp_margin`, `prefill_chunk`,
|
||||||
|
`power_percent`, `warm_weights`, `quality`, `ssd_streaming`,
|
||||||
|
`ssd_streaming_cold`, `ssd_streaming_preload_experts`,
|
||||||
|
`ssd_streaming_cache_experts` (count or `NGB`, sets both experts+bytes via
|
||||||
|
`ds4_parse_streaming_cache_experts_arg`), `simulate_used_memory` (`NGB` via
|
||||||
|
`ds4_parse_gib_arg`), `expert_profile_path`, `directional_steering_file`,
|
||||||
|
`directional_steering_attn`, `directional_steering_ffn`.
|
||||||
|
|
||||||
|
## SSD streaming (running models larger than RAM)
|
||||||
|
|
||||||
|
ds4's **SSD streaming** keeps non-routed weights resident and streams routed MoE
|
||||||
|
experts from the GGUF on cache misses, turning "does it fit in RAM" into a speed
|
||||||
|
spectrum. **Metal (Darwin) only** - it is a no-op on CUDA/CPU. Enable with
|
||||||
|
`options: ["ssd_streaming"]`; size the routed-expert cache with
|
||||||
|
`ssd_streaming_cache_experts:NGB` (omit for ds4's automatic 80%-of-working-set
|
||||||
|
budget). Gallery entries built on this: `deepseek-v4-flash-q4-ssd` (153 GB Flash
|
||||||
|
on a 128 GB Mac) and `deepseek-v4-pro-q2-ssd` (433 GB Pro, experimental).
|
||||||
|
|
||||||
## Build matrix
|
## Build matrix
|
||||||
|
|
||||||
| Build | Where | Notes |
|
| Build | Where | Notes |
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ if [ "${BUILD_TYPE:-}" = "vulkan" ] && [ "${SKIP_DRIVERS:-false}" = "false" ]; t
|
|||||||
git python-is-python3 bison libx11-xcb-dev liblz4-dev libzstd-dev \
|
git python-is-python3 bison libx11-xcb-dev liblz4-dev libzstd-dev \
|
||||||
ocaml-core ninja-build pkg-config libxml2-dev wayland-protocols python3-jsonschema \
|
ocaml-core ninja-build pkg-config libxml2-dev wayland-protocols python3-jsonschema \
|
||||||
clang-format qtbase5-dev qt6-base-dev libxcb-glx0-dev sudo xz-utils
|
clang-format qtbase5-dev qt6-base-dev libxcb-glx0-dev sudo xz-utils
|
||||||
|
# Mesa Vulkan ICD drivers (ANV/RADV/lavapipe + Arm SoC) and their ICD
|
||||||
|
# manifests. The LunarG SDK below only provides the loader and shader
|
||||||
|
# tooling, not hardware drivers — without Mesa the packaged Vulkan backend
|
||||||
|
# would ship a loader that finds no GPU. package-gpu-libs.sh bundles these
|
||||||
|
# .so files plus their deps into the backend so it stays self-contained.
|
||||||
|
apt-get install -y mesa-vulkan-drivers libdrm2
|
||||||
if [ "amd64" = "${TARGETARCH:-}" ]; then
|
if [ "amd64" = "${TARGETARCH:-}" ]; then
|
||||||
wget "https://sdk.lunarg.com/sdk/download/1.4.335.0/linux/vulkansdk-linux-x86_64-1.4.335.0.tar.xz"
|
wget "https://sdk.lunarg.com/sdk/download/1.4.335.0/linux/vulkansdk-linux-x86_64-1.4.335.0.tar.xz"
|
||||||
tar -xf vulkansdk-linux-x86_64-1.4.335.0.tar.xz
|
tar -xf vulkansdk-linux-x86_64-1.4.335.0.tar.xz
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ backend/python/**/source
|
|||||||
backend/cpp/llama-cpp/llama.cpp
|
backend/cpp/llama-cpp/llama.cpp
|
||||||
backend/cpp/llama-cpp-*-build
|
backend/cpp/llama-cpp-*-build
|
||||||
|
|
||||||
|
# privacy-filter: same in-place pattern. The Makefile fetches privacy-filter.cpp
|
||||||
|
# at the pinned commit (or symlinks a PRIVACY_FILTER_SRC checkout for local dev).
|
||||||
|
# A stale dir/symlink COPY'd into the image makes the clone step fail (dangling
|
||||||
|
# symlink) or compile against the wrong commit, so keep host build state out.
|
||||||
|
backend/cpp/privacy-filter/privacy-filter.cpp
|
||||||
|
backend/cpp/privacy-filter/build
|
||||||
|
backend/cpp/privacy-filter/grpc-server
|
||||||
|
backend/cpp/privacy-filter/package
|
||||||
|
|
||||||
# Rust backend build output (sources are tracked; target/ is generated)
|
# Rust backend build output (sources are tracked; target/ is generated)
|
||||||
backend/rust/*/target
|
backend/rust/*/target
|
||||||
|
|
||||||
|
|||||||
655
.github/backend-matrix.yml
vendored
655
.github/backend-matrix.yml
vendored
@@ -703,6 +703,32 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "12"
|
||||||
|
cuda-minor-version: "8"
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-nvidia-cuda-12-locate-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "locate-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "12"
|
||||||
|
cuda-minor-version: "8"
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-nvidia-cuda-12-depth-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "depth-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
- build-type: 'cublas'
|
- build-type: 'cublas'
|
||||||
cuda-major-version: "12"
|
cuda-major-version: "12"
|
||||||
cuda-minor-version: "8"
|
cuda-minor-version: "8"
|
||||||
@@ -768,6 +794,19 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "12"
|
||||||
|
cuda-minor-version: "8"
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-nvidia-cuda-12-omnivoice-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "omnivoice-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
- build-type: 'cublas'
|
- build-type: 'cublas'
|
||||||
cuda-major-version: "12"
|
cuda-major-version: "12"
|
||||||
cuda-minor-version: "8"
|
cuda-minor-version: "8"
|
||||||
@@ -1543,6 +1582,32 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "13"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-nvidia-cuda-13-locate-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "locate-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "13"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-nvidia-cuda-13-depth-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "depth-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
- build-type: 'cublas'
|
- build-type: 'cublas'
|
||||||
cuda-major-version: "13"
|
cuda-major-version: "13"
|
||||||
cuda-minor-version: "0"
|
cuda-minor-version: "0"
|
||||||
@@ -1569,6 +1634,32 @@ include:
|
|||||||
backend: "rfdetr-cpp"
|
backend: "rfdetr-cpp"
|
||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "13"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
skip-drivers: 'false'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-nvidia-l4t-cuda-13-arm64-locate-anything-cpp'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
backend: "locate-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "13"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
skip-drivers: 'false'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-nvidia-l4t-cuda-13-arm64-depth-anything-cpp'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
backend: "depth-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
- build-type: 'cublas'
|
- build-type: 'cublas'
|
||||||
cuda-major-version: "13"
|
cuda-major-version: "13"
|
||||||
cuda-minor-version: "0"
|
cuda-minor-version: "0"
|
||||||
@@ -1673,6 +1764,19 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "13"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-nvidia-cuda-13-omnivoice-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "omnivoice-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
- build-type: 'cublas'
|
- build-type: 'cublas'
|
||||||
cuda-major-version: "13"
|
cuda-major-version: "13"
|
||||||
cuda-minor-version: "0"
|
cuda-minor-version: "0"
|
||||||
@@ -1712,6 +1816,19 @@ include:
|
|||||||
backend: "qwen3-tts-cpp"
|
backend: "qwen3-tts-cpp"
|
||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "13"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
skip-drivers: 'false'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-nvidia-l4t-cuda-13-arm64-omnivoice-cpp'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
backend: "omnivoice-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
- build-type: 'cublas'
|
- build-type: 'cublas'
|
||||||
cuda-major-version: "13"
|
cuda-major-version: "13"
|
||||||
cuda-minor-version: "0"
|
cuda-minor-version: "0"
|
||||||
@@ -2553,6 +2670,78 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.ds4"
|
dockerfile: "./backend/Dockerfile.ds4"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
# privacy-filter: PII/NER token classifier (per-arch native -> manifest merge).
|
||||||
|
# Every variant builds FROM a prebuilt quay.io/go-skynet/ci-cache:base-grpc-*
|
||||||
|
# image (gRPC + cmake + protoc + conditional CUDA/Vulkan already installed),
|
||||||
|
# exactly like llama-cpp — no toolchain is installed in Dockerfile.privacy-filter.
|
||||||
|
# builder-base-image makes the workflow use the Dockerfile's builder-prebuilt
|
||||||
|
# stage; without it (local builds) the builder-fromsource stage runs the same
|
||||||
|
# .docker/install-base-deps.sh.
|
||||||
|
- build-type: ''
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
platform-tag: 'amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-cpu-privacy-filter'
|
||||||
|
builder-base-image: 'quay.io/go-skynet/ci-cache:base-grpc-amd64'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'true'
|
||||||
|
backend: "privacy-filter"
|
||||||
|
dockerfile: "./backend/Dockerfile.privacy-filter"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: ''
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
platform-tag: 'arm64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-cpu-privacy-filter'
|
||||||
|
builder-base-image: 'quay.io/go-skynet/ci-cache:base-grpc-arm64'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'true'
|
||||||
|
backend: "privacy-filter"
|
||||||
|
dockerfile: "./backend/Dockerfile.privacy-filter"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
# Vulkan: base-grpc-vulkan-amd64 carries the SDK. arm64 vulkan is a one-line
|
||||||
|
# add once amd64 is proven in CI.
|
||||||
|
- build-type: 'vulkan'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
platform-tag: 'amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-vulkan-privacy-filter'
|
||||||
|
builder-base-image: 'quay.io/go-skynet/ci-cache:base-grpc-vulkan-amd64'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "privacy-filter"
|
||||||
|
dockerfile: "./backend/Dockerfile.privacy-filter"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
# CUDA: base-grpc-cuda-13-amd64 carries the toolkit; BUILD_TYPE=cublas ->
|
||||||
|
# -DPF_CUDA=ON. cuda-12 and arm64/l4t are one-line adds once cuda-13 amd64 is
|
||||||
|
# proven in CI.
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "13"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
platform-tag: 'amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-nvidia-cuda-13-privacy-filter'
|
||||||
|
builder-base-image: 'quay.io/go-skynet/ci-cache:base-grpc-cuda-13-amd64'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'true'
|
||||||
|
backend: "privacy-filter"
|
||||||
|
dockerfile: "./backend/Dockerfile.privacy-filter"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
- build-type: ''
|
- build-type: ''
|
||||||
cuda-major-version: ""
|
cuda-major-version: ""
|
||||||
cuda-minor-version: ""
|
cuda-minor-version: ""
|
||||||
@@ -2806,6 +2995,141 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
# locate-anything-cpp
|
||||||
|
- build-type: ''
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-cpu-locate-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "locate-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: ''
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-cpu-depth-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "depth-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'sycl_f32'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-intel-sycl-f32-locate-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "intel/oneapi-basekit:2025.3.0-0-devel-ubuntu24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "locate-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'sycl_f32'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-intel-sycl-f32-depth-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "intel/oneapi-basekit:2025.3.0-0-devel-ubuntu24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "depth-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'sycl_f16'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-intel-sycl-f16-locate-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "intel/oneapi-basekit:2025.3.0-0-devel-ubuntu24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "locate-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'sycl_f16'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-intel-sycl-f16-depth-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "intel/oneapi-basekit:2025.3.0-0-devel-ubuntu24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "depth-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'vulkan'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
platform-tag: 'amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-vulkan-locate-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "locate-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'vulkan'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
platform-tag: 'amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-vulkan-depth-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "depth-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'vulkan'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
platform-tag: 'arm64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-vulkan-locate-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "locate-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'vulkan'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
platform-tag: 'arm64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-vulkan-depth-anything-cpp'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "depth-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
- build-type: 'sycl_f32'
|
- build-type: 'sycl_f32'
|
||||||
cuda-major-version: ""
|
cuda-major-version: ""
|
||||||
cuda-minor-version: ""
|
cuda-minor-version: ""
|
||||||
@@ -2899,6 +3223,32 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2204'
|
ubuntu-version: '2204'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "12"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
skip-drivers: 'false'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-nvidia-l4t-arm64-locate-anything-cpp'
|
||||||
|
base-image: "nvcr.io/nvidia/l4t-jetpack:r36.4.0"
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
backend: "locate-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2204'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "12"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
skip-drivers: 'false'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-nvidia-l4t-arm64-depth-anything-cpp'
|
||||||
|
base-image: "nvcr.io/nvidia/l4t-jetpack:r36.4.0"
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
backend: "depth-anything-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2204'
|
||||||
# whisper
|
# whisper
|
||||||
- build-type: ''
|
- build-type: ''
|
||||||
cuda-major-version: ""
|
cuda-major-version: ""
|
||||||
@@ -3225,6 +3575,154 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
# ced
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "12"
|
||||||
|
cuda-minor-version: "8"
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-nvidia-cuda-12-ced'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "ced"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "13"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-nvidia-cuda-13-ced'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "ced"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "13"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
skip-drivers: 'false'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-nvidia-l4t-cuda-13-arm64-ced'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
backend: "ced"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
- build-type: ''
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
platform-tag: 'amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-cpu-ced'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "ced"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: ''
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
platform-tag: 'arm64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-cpu-ced'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "ced"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'sycl_f32'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-intel-sycl-f32-ced'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "intel/oneapi-basekit:2025.3.0-0-devel-ubuntu24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "ced"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'sycl_f16'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-intel-sycl-f16-ced'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "intel/oneapi-basekit:2025.3.0-0-devel-ubuntu24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "ced"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'vulkan'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
platform-tag: 'amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-vulkan-ced'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "ced"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'vulkan'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
platform-tag: 'arm64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-vulkan-ced'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "ced"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "12"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
skip-drivers: 'false'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-nvidia-l4t-arm64-ced'
|
||||||
|
base-image: "nvcr.io/nvidia/l4t-jetpack:r36.4.0"
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
backend: "ced"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2204'
|
||||||
|
- build-type: 'hipblas'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-rocm-hipblas-ced'
|
||||||
|
base-image: "rocm/dev-ubuntu-24.04:7.2.1"
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "ced"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
# acestep-cpp
|
# acestep-cpp
|
||||||
- build-type: ''
|
- build-type: ''
|
||||||
cuda-major-version: ""
|
cuda-major-version: ""
|
||||||
@@ -3363,6 +3861,35 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
# omnivoice-cpp
|
||||||
|
- build-type: ''
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
platform-tag: 'amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-cpu-omnivoice-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "omnivoice-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
- build-type: ''
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
platform-tag: 'arm64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-cpu-omnivoice-cpp'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "omnivoice-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
- build-type: 'sycl_f32'
|
- build-type: 'sycl_f32'
|
||||||
cuda-major-version: ""
|
cuda-major-version: ""
|
||||||
cuda-minor-version: ""
|
cuda-minor-version: ""
|
||||||
@@ -3376,6 +3903,19 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'sycl_f32'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-intel-sycl-f32-omnivoice-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "intel/oneapi-basekit:2025.3.0-0-devel-ubuntu24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "omnivoice-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
- build-type: 'sycl_f16'
|
- build-type: 'sycl_f16'
|
||||||
cuda-major-version: ""
|
cuda-major-version: ""
|
||||||
cuda-minor-version: ""
|
cuda-minor-version: ""
|
||||||
@@ -3389,6 +3929,19 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'sycl_f16'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-intel-sycl-f16-omnivoice-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "intel/oneapi-basekit:2025.3.0-0-devel-ubuntu24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "omnivoice-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
- build-type: 'vulkan'
|
- build-type: 'vulkan'
|
||||||
cuda-major-version: ""
|
cuda-major-version: ""
|
||||||
cuda-minor-version: ""
|
cuda-minor-version: ""
|
||||||
@@ -3403,6 +3956,20 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'vulkan'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
platform-tag: 'amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-vulkan-omnivoice-cpp'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "omnivoice-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
- build-type: 'vulkan'
|
- build-type: 'vulkan'
|
||||||
cuda-major-version: ""
|
cuda-major-version: ""
|
||||||
cuda-minor-version: ""
|
cuda-minor-version: ""
|
||||||
@@ -3417,6 +3984,20 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'vulkan'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
platform-tag: 'arm64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-vulkan-omnivoice-cpp'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "omnivoice-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
- build-type: 'cublas'
|
- build-type: 'cublas'
|
||||||
cuda-major-version: "12"
|
cuda-major-version: "12"
|
||||||
cuda-minor-version: "0"
|
cuda-minor-version: "0"
|
||||||
@@ -3430,6 +4011,19 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2204'
|
ubuntu-version: '2204'
|
||||||
|
- build-type: 'cublas'
|
||||||
|
cuda-major-version: "12"
|
||||||
|
cuda-minor-version: "0"
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
skip-drivers: 'false'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-nvidia-l4t-arm64-omnivoice-cpp'
|
||||||
|
base-image: "nvcr.io/nvidia/l4t-jetpack:r36.4.0"
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
backend: "omnivoice-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2204'
|
||||||
- build-type: 'hipblas'
|
- build-type: 'hipblas'
|
||||||
cuda-major-version: ""
|
cuda-major-version: ""
|
||||||
cuda-minor-version: ""
|
cuda-minor-version: ""
|
||||||
@@ -3443,6 +4037,19 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
- build-type: 'hipblas'
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-gpu-rocm-hipblas-omnivoice-cpp'
|
||||||
|
base-image: "rocm/dev-ubuntu-24.04:6.4.4"
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "omnivoice-cpp"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
# vibevoice-cpp
|
# vibevoice-cpp
|
||||||
- build-type: ''
|
- build-type: ''
|
||||||
cuda-major-version: ""
|
cuda-major-version: ""
|
||||||
@@ -4222,6 +4829,36 @@ include:
|
|||||||
dockerfile: "./backend/Dockerfile.golang"
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
context: "./"
|
context: "./"
|
||||||
ubuntu-version: '2404'
|
ubuntu-version: '2404'
|
||||||
|
# supertonic CPU (amd64)
|
||||||
|
- build-type: ''
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/amd64'
|
||||||
|
platform-tag: 'amd64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-cpu-supertonic'
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "supertonic"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
# supertonic CPU (arm64)
|
||||||
|
- build-type: ''
|
||||||
|
cuda-major-version: ""
|
||||||
|
cuda-minor-version: ""
|
||||||
|
platforms: 'linux/arm64'
|
||||||
|
platform-tag: 'arm64'
|
||||||
|
tag-latest: 'auto'
|
||||||
|
tag-suffix: '-cpu-supertonic'
|
||||||
|
runs-on: 'ubuntu-24.04-arm'
|
||||||
|
base-image: "ubuntu:24.04"
|
||||||
|
skip-drivers: 'false'
|
||||||
|
backend: "supertonic"
|
||||||
|
dockerfile: "./backend/Dockerfile.golang"
|
||||||
|
context: "./"
|
||||||
|
ubuntu-version: '2404'
|
||||||
|
|
||||||
# Darwin matrix (consumed by backend-jobs-darwin).
|
# Darwin matrix (consumed by backend-jobs-darwin).
|
||||||
includeDarwin:
|
includeDarwin:
|
||||||
@@ -4265,6 +4902,10 @@ includeDarwin:
|
|||||||
tag-suffix: "-metal-darwin-arm64-parakeet-cpp"
|
tag-suffix: "-metal-darwin-arm64-parakeet-cpp"
|
||||||
build-type: "metal"
|
build-type: "metal"
|
||||||
lang: "go"
|
lang: "go"
|
||||||
|
- backend: "ced"
|
||||||
|
tag-suffix: "-metal-darwin-arm64-ced"
|
||||||
|
build-type: "metal"
|
||||||
|
lang: "go"
|
||||||
- backend: "acestep-cpp"
|
- backend: "acestep-cpp"
|
||||||
tag-suffix: "-metal-darwin-arm64-acestep-cpp"
|
tag-suffix: "-metal-darwin-arm64-acestep-cpp"
|
||||||
build-type: "metal"
|
build-type: "metal"
|
||||||
@@ -4273,6 +4914,10 @@ includeDarwin:
|
|||||||
tag-suffix: "-metal-darwin-arm64-qwen3-tts-cpp"
|
tag-suffix: "-metal-darwin-arm64-qwen3-tts-cpp"
|
||||||
build-type: "metal"
|
build-type: "metal"
|
||||||
lang: "go"
|
lang: "go"
|
||||||
|
- backend: "omnivoice-cpp"
|
||||||
|
tag-suffix: "-metal-darwin-arm64-omnivoice-cpp"
|
||||||
|
build-type: "metal"
|
||||||
|
lang: "go"
|
||||||
- backend: "vibevoice-cpp"
|
- backend: "vibevoice-cpp"
|
||||||
tag-suffix: "-metal-darwin-arm64-vibevoice-cpp"
|
tag-suffix: "-metal-darwin-arm64-vibevoice-cpp"
|
||||||
build-type: "metal"
|
build-type: "metal"
|
||||||
@@ -4341,6 +4986,10 @@ includeDarwin:
|
|||||||
tag-suffix: "-metal-darwin-arm64-silero-vad"
|
tag-suffix: "-metal-darwin-arm64-silero-vad"
|
||||||
build-type: "metal"
|
build-type: "metal"
|
||||||
lang: "go"
|
lang: "go"
|
||||||
|
- backend: "sherpa-onnx"
|
||||||
|
tag-suffix: "-metal-darwin-arm64-sherpa-onnx"
|
||||||
|
build-type: "metal"
|
||||||
|
lang: "go"
|
||||||
- backend: "local-store"
|
- backend: "local-store"
|
||||||
tag-suffix: "-metal-darwin-arm64-local-store"
|
tag-suffix: "-metal-darwin-arm64-local-store"
|
||||||
build-type: "metal"
|
build-type: "metal"
|
||||||
@@ -4348,3 +4997,9 @@ includeDarwin:
|
|||||||
- backend: "llama-cpp-quantization"
|
- backend: "llama-cpp-quantization"
|
||||||
tag-suffix: "-metal-darwin-arm64-llama-cpp-quantization"
|
tag-suffix: "-metal-darwin-arm64-llama-cpp-quantization"
|
||||||
build-type: "mps"
|
build-type: "mps"
|
||||||
|
- backend: "speaker-recognition"
|
||||||
|
tag-suffix: "-metal-darwin-arm64-speaker-recognition"
|
||||||
|
build-type: "mps"
|
||||||
|
- backend: "ds4"
|
||||||
|
tag-suffix: "-metal-darwin-arm64-ds4"
|
||||||
|
lang: "go"
|
||||||
|
|||||||
2
.github/workflows/backend.yml
vendored
2
.github/workflows/backend.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
|||||||
has-merges-singlearch: ${{ steps.set-matrix.outputs['has-merges-singlearch'] }}
|
has-merges-singlearch: ${{ steps.set-matrix.outputs['has-merges-singlearch'] }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
|
|||||||
2
.github/workflows/backend_build.yml
vendored
2
.github/workflows/backend_build.yml
vendored
@@ -101,7 +101,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
|||||||
11
.github/workflows/backend_build_darwin.yml
vendored
11
.github/workflows/backend_build_darwin.yml
vendored
@@ -57,7 +57,7 @@ jobs:
|
|||||||
HOMEBREW_NO_ANALYTICS: '1'
|
HOMEBREW_NO_ANALYTICS: '1'
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -98,6 +98,7 @@ jobs:
|
|||||||
/opt/homebrew/Cellar/hiredis
|
/opt/homebrew/Cellar/hiredis
|
||||||
/opt/homebrew/Cellar/xxhash
|
/opt/homebrew/Cellar/xxhash
|
||||||
/opt/homebrew/Cellar/zstd
|
/opt/homebrew/Cellar/zstd
|
||||||
|
/opt/homebrew/Cellar/nlohmann-json
|
||||||
key: brew-${{ runner.os }}-${{ runner.arch }}-v1-${{ hashFiles('.github/workflows/backend_build_darwin.yml') }}
|
key: brew-${{ runner.os }}-${{ runner.arch }}-v1-${{ hashFiles('.github/workflows/backend_build_darwin.yml') }}
|
||||||
|
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -109,7 +110,10 @@ jobs:
|
|||||||
# Without explicitly installing them, a brew cache-hit run restores
|
# Without explicitly installing them, a brew cache-hit run restores
|
||||||
# ccache's Cellar dir but skips installing those transitive deps,
|
# ccache's Cellar dir but skips installing those transitive deps,
|
||||||
# and ccache fails at runtime with `dyld: Library not loaded`.
|
# and ccache fails at runtime with `dyld: Library not loaded`.
|
||||||
brew install protobuf grpc make protoc-gen-go protoc-gen-go-grpc libomp llvm ccache blake3 fmt hiredis xxhash zstd
|
# nlohmann-json is header-only and required by the ds4 backend
|
||||||
|
# (dsml_renderer.cpp includes <nlohmann/json.hpp>); on Linux it comes
|
||||||
|
# from the apt-installed nlohmann-json3-dev in the build image.
|
||||||
|
brew install protobuf grpc make protoc-gen-go protoc-gen-go-grpc libomp llvm ccache blake3 fmt hiredis xxhash zstd nlohmann-json
|
||||||
# Force-reinstall ccache so brew re-validates its full runtime-dep
|
# Force-reinstall ccache so brew re-validates its full runtime-dep
|
||||||
# closure on every run. This is the durable fix: when the upstream
|
# closure on every run. This is the durable fix: when the upstream
|
||||||
# ccache formula gains a new transitive dep (as it has multiple times
|
# ccache formula gains a new transitive dep (as it has multiple times
|
||||||
@@ -128,7 +132,7 @@ jobs:
|
|||||||
# and decides "already installed" without re-linking, so on a cache-
|
# and decides "already installed" without re-linking, so on a cache-
|
||||||
# hit run the formulas aren't on PATH. Force-link them; --overwrite
|
# hit run the formulas aren't on PATH. Force-link them; --overwrite
|
||||||
# tolerates pre-existing symlinks from earlier installs.
|
# tolerates pre-existing symlinks from earlier installs.
|
||||||
brew link --overwrite protobuf grpc make protoc-gen-go protoc-gen-go-grpc libomp llvm ccache blake3 fmt hiredis xxhash zstd 2>/dev/null || true
|
brew link --overwrite protobuf grpc make protoc-gen-go protoc-gen-go-grpc libomp llvm ccache blake3 fmt hiredis xxhash zstd nlohmann-json 2>/dev/null || true
|
||||||
|
|
||||||
- name: Save Homebrew cache
|
- name: Save Homebrew cache
|
||||||
if: github.event_name != 'pull_request' && steps.brew-cache.outputs.cache-hit != 'true'
|
if: github.event_name != 'pull_request' && steps.brew-cache.outputs.cache-hit != 'true'
|
||||||
@@ -148,6 +152,7 @@ jobs:
|
|||||||
/opt/homebrew/Cellar/hiredis
|
/opt/homebrew/Cellar/hiredis
|
||||||
/opt/homebrew/Cellar/xxhash
|
/opt/homebrew/Cellar/xxhash
|
||||||
/opt/homebrew/Cellar/zstd
|
/opt/homebrew/Cellar/zstd
|
||||||
|
/opt/homebrew/Cellar/nlohmann-json
|
||||||
key: brew-${{ runner.os }}-${{ runner.arch }}-v1-${{ hashFiles('.github/workflows/backend_build_darwin.yml') }}
|
key: brew-${{ runner.os }}-${{ runner.arch }}-v1-${{ hashFiles('.github/workflows/backend_build_darwin.yml') }}
|
||||||
|
|
||||||
# ---- ccache for llama.cpp CMake builds ----
|
# ---- ccache for llama.cpp CMake builds ----
|
||||||
|
|||||||
2
.github/workflows/backend_merge.yml
vendored
2
.github/workflows/backend_merge.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
|||||||
# Sparse checkout: the merge job needs `.github/scripts/` (for the
|
# Sparse checkout: the merge job needs `.github/scripts/` (for the
|
||||||
# keepalive cleanup script) but none of the source tree.
|
# keepalive cleanup script) but none of the source tree.
|
||||||
- name: Checkout (.github/scripts only)
|
- name: Checkout (.github/scripts only)
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
sparse-checkout: |
|
sparse-checkout: |
|
||||||
.github/scripts
|
.github/scripts
|
||||||
|
|||||||
2
.github/workflows/backend_pr.yml
vendored
2
.github/workflows/backend_pr.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
has-merges-singlearch: ${{ steps.set-matrix.outputs['has-merges-singlearch'] }}
|
has-merges-singlearch: ${{ steps.set-matrix.outputs['has-merges-singlearch'] }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
|
|||||||
2
.github/workflows/base-images.yml
vendored
2
.github/workflows/base-images.yml
vendored
@@ -127,7 +127,7 @@ jobs:
|
|||||||
# the original l4t matrix entry which set skip-drivers: 'true'.
|
# the original l4t matrix entry which set skip-drivers: 'true'.
|
||||||
skip-drivers: 'true'
|
skip-drivers: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: false
|
submodules: false
|
||||||
- name: Free disk space
|
- name: Free disk space
|
||||||
|
|||||||
6
.github/workflows/build-test.yaml
vendored
6
.github/workflows/build-test.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Configure apt mirror on runner
|
- name: Configure apt mirror on runner
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
bump:
|
bump:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
|
|||||||
28
.github/workflows/bump_deps.yaml
vendored
28
.github/workflows/bump_deps.yaml
vendored
@@ -26,6 +26,10 @@ jobs:
|
|||||||
variable: "DS4_VERSION"
|
variable: "DS4_VERSION"
|
||||||
branch: "main"
|
branch: "main"
|
||||||
file: "backend/cpp/ds4/Makefile"
|
file: "backend/cpp/ds4/Makefile"
|
||||||
|
- repository: "localai-org/privacy-filter.cpp"
|
||||||
|
variable: "PRIVACY_FILTER_VERSION"
|
||||||
|
branch: "master"
|
||||||
|
file: "backend/cpp/privacy-filter/Makefile"
|
||||||
- repository: "ggml-org/whisper.cpp"
|
- repository: "ggml-org/whisper.cpp"
|
||||||
variable: "WHISPER_CPP_VERSION"
|
variable: "WHISPER_CPP_VERSION"
|
||||||
branch: "master"
|
branch: "master"
|
||||||
@@ -38,6 +42,14 @@ jobs:
|
|||||||
variable: "PARAKEET_VERSION"
|
variable: "PARAKEET_VERSION"
|
||||||
branch: "master"
|
branch: "master"
|
||||||
file: "backend/go/parakeet-cpp/Makefile"
|
file: "backend/go/parakeet-cpp/Makefile"
|
||||||
|
- repository: "mudler/ced.cpp"
|
||||||
|
variable: "CED_VERSION"
|
||||||
|
branch: "master"
|
||||||
|
file: "backend/go/ced/Makefile"
|
||||||
|
- repository: "mudler/depth-anything.cpp"
|
||||||
|
variable: "DEPTHANYTHING_VERSION"
|
||||||
|
branch: "master"
|
||||||
|
file: "backend/go/depth-anything-cpp/Makefile"
|
||||||
- repository: "leejet/stable-diffusion.cpp"
|
- repository: "leejet/stable-diffusion.cpp"
|
||||||
variable: "STABLEDIFFUSION_GGML_VERSION"
|
variable: "STABLEDIFFUSION_GGML_VERSION"
|
||||||
branch: "master"
|
branch: "master"
|
||||||
@@ -62,17 +74,25 @@ jobs:
|
|||||||
variable: "RFDETR_VERSION"
|
variable: "RFDETR_VERSION"
|
||||||
branch: "main"
|
branch: "main"
|
||||||
file: "backend/go/rfdetr-cpp/Makefile"
|
file: "backend/go/rfdetr-cpp/Makefile"
|
||||||
- repository: "predict-woo/qwen3-tts.cpp"
|
- repository: "mudler/locate-anything.cpp"
|
||||||
|
variable: "LOCATEANYTHING_VERSION"
|
||||||
|
branch: "master"
|
||||||
|
file: "backend/go/locate-anything-cpp/Makefile"
|
||||||
|
- repository: "ServeurpersoCom/qwentts.cpp"
|
||||||
variable: "QWEN3TTS_CPP_VERSION"
|
variable: "QWEN3TTS_CPP_VERSION"
|
||||||
branch: "main"
|
branch: "master"
|
||||||
file: "backend/go/qwen3-tts-cpp/Makefile"
|
file: "backend/go/qwen3-tts-cpp/Makefile"
|
||||||
|
- repository: "ServeurpersoCom/omnivoice.cpp"
|
||||||
|
variable: "OMNIVOICE_VERSION"
|
||||||
|
branch: "master"
|
||||||
|
file: "backend/go/omnivoice-cpp/Makefile"
|
||||||
- repository: "localai-org/vibevoice.cpp"
|
- repository: "localai-org/vibevoice.cpp"
|
||||||
variable: "VIBEVOICE_CPP_VERSION"
|
variable: "VIBEVOICE_CPP_VERSION"
|
||||||
branch: "master"
|
branch: "master"
|
||||||
file: "backend/go/vibevoice-cpp/Makefile"
|
file: "backend/go/vibevoice-cpp/Makefile"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Bump dependencies 🔧
|
- name: Bump dependencies 🔧
|
||||||
id: bump
|
id: bump
|
||||||
run: |
|
run: |
|
||||||
@@ -108,7 +128,7 @@ jobs:
|
|||||||
if: github.repository == 'mudler/LocalAI'
|
if: github.repository == 'mudler/LocalAI'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Bump vLLM cu130 wheel pin 🔧
|
- name: Bump vLLM cu130 wheel pin 🔧
|
||||||
id: bump
|
id: bump
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/bump_docs.yaml
vendored
2
.github/workflows/bump_docs.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
- repository: "mudler/LocalAI"
|
- repository: "mudler/LocalAI"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Bump dependencies 🔧
|
- name: Bump dependencies 🔧
|
||||||
run: |
|
run: |
|
||||||
bash .github/bump_docs.sh ${{ matrix.repository }}
|
bash .github/bump_docs.sh ${{ matrix.repository }}
|
||||||
|
|||||||
2
.github/workflows/checksum_checker.yaml
vendored
2
.github/workflows/checksum_checker.yaml
vendored
@@ -8,7 +8,7 @@ jobs:
|
|||||||
if: github.repository == 'mudler/LocalAI'
|
if: github.repository == 'mudler/LocalAI'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Configure apt mirror on runner
|
- name: Configure apt mirror on runner
|
||||||
uses: ./.github/actions/configure-apt-mirror
|
uses: ./.github/actions/configure-apt-mirror
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|||||||
2
.github/workflows/deploy-explorer.yaml
vendored
2
.github/workflows/deploy-explorer.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
|
|||||||
2
.github/workflows/gallery-agent.yaml
vendored
2
.github/workflows/gallery-agent.yaml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/generate_intel_image.yaml
vendored
2
.github/workflows/generate_intel_image.yaml
vendored
@@ -44,7 +44,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@master
|
uses: docker/setup-buildx-action@master
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Cache Intel images
|
- name: Cache Intel images
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v7
|
||||||
|
|||||||
2
.github/workflows/gh-pages.yml
vendored
2
.github/workflows/gh-pages.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
HUGO_VERSION: "0.146.3"
|
HUGO_VERSION: "0.146.3"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # needed for enableGitInfo
|
fetch-depth: 0 # needed for enableGitInfo
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|||||||
2
.github/workflows/image_build.yml
vendored
2
.github/workflows/image_build.yml
vendored
@@ -80,7 +80,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Configure apt mirror on runner
|
- name: Configure apt mirror on runner
|
||||||
id: apt_mirror
|
id: apt_mirror
|
||||||
|
|||||||
2
.github/workflows/image_merge.yml
vendored
2
.github/workflows/image_merge.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
# Sparse checkout: needed for .github/scripts/ (the keepalive cleanup
|
# Sparse checkout: needed for .github/scripts/ (the keepalive cleanup
|
||||||
# script). Skips the rest of the source tree.
|
# script). Skips the rest of the source tree.
|
||||||
- name: Checkout (.github/scripts only)
|
- name: Checkout (.github/scripts only)
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
sparse-checkout: |
|
sparse-checkout: |
|
||||||
.github/scripts
|
.github/scripts
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
golangci-lint:
|
golangci-lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
# Full history so golangci-lint's new-from-merge-base can reach
|
# Full history so golangci-lint's new-from-merge-base can reach
|
||||||
# origin/master and compute the diff against it.
|
# origin/master and compute the diff against it.
|
||||||
|
|||||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Configure apt mirror on runner
|
- name: Configure apt mirror on runner
|
||||||
|
|||||||
7
.github/workflows/secscan.yaml
vendored
7
.github/workflows/secscan.yaml
vendored
@@ -14,14 +14,17 @@ jobs:
|
|||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Source
|
- name: Checkout Source
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||||
- name: Run Gosec Security Scanner
|
- name: Run Gosec Security Scanner
|
||||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||||
uses: securego/gosec@v2.27.1
|
uses: securego/gosec@v2.27.1
|
||||||
with:
|
with:
|
||||||
# we let the report trigger content trigger a failure using the GitHub Security features.
|
# we let the report trigger content trigger a failure using the GitHub Security features.
|
||||||
args: '-no-fail -fmt sarif -out results.sarif ./...'
|
# backend/go/supertonic is excluded: it vendors upstream supertone-inc/supertonic
|
||||||
|
# (helper.go), whose findings (G304 model-file loads, G404 math/rand for flow-matching
|
||||||
|
# noise, G104 unhandled errors) are inherent to that upstream code, not ours to rewrite.
|
||||||
|
args: '-no-fail -exclude-dir=backend/go/supertonic -fmt sarif -out results.sarif ./...'
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||||
uses: github/codeql-action/upload-sarif@v4
|
uses: github/codeql-action/upload-sarif@v4
|
||||||
|
|||||||
126
.github/workflows/test-extra.yml
vendored
126
.github/workflows/test-extra.yml
vendored
@@ -38,6 +38,7 @@ jobs:
|
|||||||
acestep-cpp: ${{ steps.detect.outputs.acestep-cpp }}
|
acestep-cpp: ${{ steps.detect.outputs.acestep-cpp }}
|
||||||
qwen3-tts-cpp: ${{ steps.detect.outputs.qwen3-tts-cpp }}
|
qwen3-tts-cpp: ${{ steps.detect.outputs.qwen3-tts-cpp }}
|
||||||
rfdetr-cpp: ${{ steps.detect.outputs.rfdetr-cpp }}
|
rfdetr-cpp: ${{ steps.detect.outputs.rfdetr-cpp }}
|
||||||
|
locate-anything-cpp: ${{ steps.detect.outputs.locate-anything-cpp }}
|
||||||
vibevoice-cpp: ${{ steps.detect.outputs.vibevoice-cpp }}
|
vibevoice-cpp: ${{ steps.detect.outputs.vibevoice-cpp }}
|
||||||
localvqe: ${{ steps.detect.outputs.localvqe }}
|
localvqe: ${{ steps.detect.outputs.localvqe }}
|
||||||
voxtral: ${{ steps.detect.outputs.voxtral }}
|
voxtral: ${{ steps.detect.outputs.voxtral }}
|
||||||
@@ -49,7 +50,7 @@ jobs:
|
|||||||
parakeet-cpp: ${{ steps.detect.outputs.parakeet-cpp }}
|
parakeet-cpp: ${{ steps.detect.outputs.parakeet-cpp }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -66,7 +67,7 @@ jobs:
|
|||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Clone
|
# - name: Clone
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v7
|
||||||
# with:
|
# with:
|
||||||
# submodules: true
|
# submodules: true
|
||||||
# - name: Dependencies
|
# - name: Dependencies
|
||||||
@@ -89,7 +90,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -112,7 +113,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -136,7 +137,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -157,7 +158,7 @@ jobs:
|
|||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Clone
|
# - name: Clone
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v7
|
||||||
# with:
|
# with:
|
||||||
# submodules: true
|
# submodules: true
|
||||||
# - name: Dependencies
|
# - name: Dependencies
|
||||||
@@ -177,7 +178,7 @@ jobs:
|
|||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Clone
|
# - name: Clone
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v7
|
||||||
# with:
|
# with:
|
||||||
# submodules: true
|
# submodules: true
|
||||||
# - name: Dependencies
|
# - name: Dependencies
|
||||||
@@ -239,7 +240,7 @@ jobs:
|
|||||||
# sudo rm -rf "$AGENT_TOOLSDIRECTORY" || true
|
# sudo rm -rf "$AGENT_TOOLSDIRECTORY" || true
|
||||||
# df -h
|
# df -h
|
||||||
# - name: Clone
|
# - name: Clone
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v7
|
||||||
# with:
|
# with:
|
||||||
# submodules: true
|
# submodules: true
|
||||||
# - name: Dependencies
|
# - name: Dependencies
|
||||||
@@ -264,7 +265,7 @@ jobs:
|
|||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Clone
|
# - name: Clone
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v7
|
||||||
# with:
|
# with:
|
||||||
# submodules: true
|
# submodules: true
|
||||||
# - name: Dependencies
|
# - name: Dependencies
|
||||||
@@ -287,7 +288,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -308,7 +309,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -329,7 +330,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -350,7 +351,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -372,7 +373,7 @@ jobs:
|
|||||||
# timeout-minutes: 45
|
# timeout-minutes: 45
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Clone
|
# - name: Clone
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v7
|
||||||
# with:
|
# with:
|
||||||
# submodules: true
|
# submodules: true
|
||||||
# - name: Dependencies
|
# - name: Dependencies
|
||||||
@@ -393,7 +394,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -414,7 +415,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -435,7 +436,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -461,7 +462,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -483,7 +484,7 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -512,7 +513,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -529,7 +530,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -551,7 +552,7 @@ jobs:
|
|||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -563,7 +564,7 @@ jobs:
|
|||||||
- name: Run e2e-backends smoke
|
- name: Run e2e-backends smoke
|
||||||
env:
|
env:
|
||||||
BACKEND_IMAGE: quay.io/go-skynet/local-ai-backends:master-cpu-llama-cpp
|
BACKEND_IMAGE: quay.io/go-skynet/local-ai-backends:master-cpu-llama-cpp
|
||||||
BACKEND_TEST_CAPS: health,load,predict,stream,logprobs,logit_bias
|
BACKEND_TEST_CAPS: health,load,predict,stream,logprobs,logit_bias,tokenize
|
||||||
run: |
|
run: |
|
||||||
make test-extra-backend
|
make test-extra-backend
|
||||||
# Realtime e2e with sherpa-onnx driving VAD + STT + TTS against a mocked LLM.
|
# Realtime e2e with sherpa-onnx driving VAD + STT + TTS against a mocked LLM.
|
||||||
@@ -578,7 +579,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -603,7 +604,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -624,7 +625,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -644,7 +645,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -663,7 +664,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -680,7 +681,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -697,7 +698,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -740,7 +741,7 @@ jobs:
|
|||||||
# timeout-minutes: 90
|
# timeout-minutes: 90
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Clone
|
# - name: Clone
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v7
|
||||||
# with:
|
# with:
|
||||||
# submodules: true
|
# submodules: true
|
||||||
# - name: Dependencies
|
# - name: Dependencies
|
||||||
@@ -782,7 +783,7 @@ jobs:
|
|||||||
# timeout-minutes: 90
|
# timeout-minutes: 90
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Clone
|
# - name: Clone
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v7
|
||||||
# with:
|
# with:
|
||||||
# submodules: true
|
# submodules: true
|
||||||
# - name: Dependencies
|
# - name: Dependencies
|
||||||
@@ -807,7 +808,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -839,7 +840,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -875,7 +876,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -901,6 +902,45 @@ jobs:
|
|||||||
- name: Test rfdetr-cpp
|
- name: Test rfdetr-cpp
|
||||||
run: |
|
run: |
|
||||||
make --jobs=5 --output-sync=target -C backend/go/rfdetr-cpp test
|
make --jobs=5 --output-sync=target -C backend/go/rfdetr-cpp test
|
||||||
|
# Per-backend e2e for locate-anything-cpp: builds the .so + Go binary and
|
||||||
|
# runs `make -C backend/go/locate-anything-cpp test`. test.sh fetches the
|
||||||
|
# locate-anything-q8_0 GGUF (~6.3 GB, NVIDIA LocateAnything-3B) from the
|
||||||
|
# published mudler/locate-anything.cpp-gguf HF repo + a COCO image, then the
|
||||||
|
# Go wire test loads the model and runs an open-vocabulary Detect, asserting
|
||||||
|
# at least one labeled box. Heavier than the other Go backends (it is a 3B),
|
||||||
|
# so it is gated to changes under backend/go/locate-anything-cpp/.
|
||||||
|
tests-locate-anything-cpp:
|
||||||
|
needs: detect-changes
|
||||||
|
if: needs.detect-changes.outputs.locate-anything-cpp == 'true' || needs.detect-changes.outputs.run-all == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Clone
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential cmake curl libopenblas-dev
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
- name: Display Go version
|
||||||
|
run: go version
|
||||||
|
- name: Proto Dependencies
|
||||||
|
run: |
|
||||||
|
# Install protoc
|
||||||
|
curl -L -s https://github.com/protocolbuffers/protobuf/releases/download/v26.1/protoc-26.1-linux-x86_64.zip -o protoc.zip && \
|
||||||
|
unzip -j -d /usr/local/bin protoc.zip bin/protoc && \
|
||||||
|
rm protoc.zip
|
||||||
|
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.2
|
||||||
|
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@1958fcbe2ca8bd93af633f11e97d44e567e945af
|
||||||
|
PATH="$PATH:$HOME/go/bin" make protogen-go
|
||||||
|
- name: Build locate-anything-cpp
|
||||||
|
run: |
|
||||||
|
make --jobs=5 --output-sync=target -C backend/go/locate-anything-cpp
|
||||||
|
- name: Test locate-anything-cpp
|
||||||
|
run: |
|
||||||
|
make --jobs=5 --output-sync=target -C backend/go/locate-anything-cpp test
|
||||||
# Per-backend smoke for vibevoice-cpp: builds the .so + Go binary and
|
# Per-backend smoke for vibevoice-cpp: builds the .so + Go binary and
|
||||||
# runs `make -C backend/go/vibevoice-cpp test`. test.sh auto-downloads
|
# runs `make -C backend/go/vibevoice-cpp test`. test.sh auto-downloads
|
||||||
# the published mudler/vibevoice.cpp-models bundle (TTS Q8_0 + ASR Q4_K
|
# the published mudler/vibevoice.cpp-models bundle (TTS Q8_0 + ASR Q4_K
|
||||||
@@ -912,7 +952,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -947,7 +987,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -973,7 +1013,7 @@ jobs:
|
|||||||
timeout-minutes: 150
|
timeout-minutes: 150
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -1002,7 +1042,7 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
@@ -1018,7 +1058,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -1051,7 +1091,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -1074,7 +1114,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
@@ -1100,7 +1140,7 @@ jobs:
|
|||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
|
|||||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
go-version: ['1.26.x']
|
go-version: ['1.26.x']
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Free disk space
|
- name: Free disk space
|
||||||
@@ -84,7 +84,7 @@ jobs:
|
|||||||
go-version: ['1.26.x']
|
go-version: ['1.26.x']
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Setup Go ${{ matrix.go-version }}
|
- name: Setup Go ${{ matrix.go-version }}
|
||||||
|
|||||||
2
.github/workflows/tests-aio.yml
vendored
2
.github/workflows/tests-aio.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
|||||||
sudo rm -rfv build || true
|
sudo rm -rfv build || true
|
||||||
df -h
|
df -h
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Dependencies
|
- name: Dependencies
|
||||||
|
|||||||
2
.github/workflows/tests-e2e.yml
vendored
2
.github/workflows/tests-e2e.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
go-version: ['1.25.x']
|
go-version: ['1.25.x']
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Configure apt mirror on runner
|
- name: Configure apt mirror on runner
|
||||||
|
|||||||
97
.github/workflows/tests-pii-ner-e2e.yml
vendored
Normal file
97
.github/workflows/tests-pii-ner-e2e.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
name: 'PII NER tier E2E (live GGUF, CPU)'
|
||||||
|
|
||||||
|
# Runs the real privacy-filter GGUF NER tier end-to-end on CPU — the gap the
|
||||||
|
# hermetic tests/e2e suite cannot cover (it only exercises the in-process
|
||||||
|
# pattern tier). Heavy (builds the C++ backend image + downloads a ~2.7 GB
|
||||||
|
# GGUF), so it is path-filtered on PRs and otherwise runs nightly / on demand.
|
||||||
|
#
|
||||||
|
# This drives the container-level harness (tests/e2e-backends) via
|
||||||
|
# `make test-extra-backend-privacy-filter`: it builds the privacy-filter image,
|
||||||
|
# downloads the model, loads it on CPU, and asserts byte-correct, UTF-8-aligned
|
||||||
|
# TokenClassify spans. The complementary HTTP-path specs in tests/e2e
|
||||||
|
# (e2e_pii_ner_test.go) Skip unless PII_NER_MODEL_GGUF is wired.
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 3 * * *'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- 'backend/cpp/privacy-filter/**'
|
||||||
|
- 'backend/Dockerfile.privacy-filter'
|
||||||
|
- 'core/services/routing/pii/**'
|
||||||
|
- 'core/services/routing/piidetector/**'
|
||||||
|
- 'core/backend/token_classify.go'
|
||||||
|
- 'core/http/endpoints/localai/pii.go'
|
||||||
|
- 'core/schema/pii.go'
|
||||||
|
- 'tests/e2e-backends/**'
|
||||||
|
- 'tests/e2e/e2e_pii_ner_test.go'
|
||||||
|
- 'tests/e2e/e2e_suite_test.go'
|
||||||
|
- '.github/workflows/tests-pii-ner-e2e.yml'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'backend/cpp/privacy-filter/**'
|
||||||
|
- 'backend/Dockerfile.privacy-filter'
|
||||||
|
- 'core/services/routing/pii/**'
|
||||||
|
- 'core/services/routing/piidetector/**'
|
||||||
|
- 'core/backend/token_classify.go'
|
||||||
|
- 'core/http/endpoints/localai/pii.go'
|
||||||
|
- 'core/schema/pii.go'
|
||||||
|
- 'tests/e2e-backends/**'
|
||||||
|
- 'tests/e2e/e2e_pii_ner_test.go'
|
||||||
|
- 'tests/e2e/e2e_suite_test.go'
|
||||||
|
- '.github/workflows/tests-pii-ner-e2e.yml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-tests-pii-ner-e2e-${{ github.event.pull_request.number || github.sha }}-${{ github.repository }}
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests-pii-ner-e2e:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version: ['1.25.x']
|
||||||
|
steps:
|
||||||
|
- name: Clone
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Free disk space
|
||||||
|
run: |
|
||||||
|
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL || true
|
||||||
|
sudo docker image prune --all --force || true
|
||||||
|
df -h
|
||||||
|
- name: Configure apt mirror on runner
|
||||||
|
uses: ./.github/actions/configure-apt-mirror
|
||||||
|
- name: Setup Go ${{ matrix.go-version }}
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
cache: false
|
||||||
|
- name: Proto Dependencies
|
||||||
|
run: |
|
||||||
|
curl -L -s https://github.com/protocolbuffers/protobuf/releases/download/v26.1/protoc-26.1-linux-x86_64.zip -o protoc.zip && \
|
||||||
|
unzip -j -d /usr/local/bin protoc.zip bin/protoc && \
|
||||||
|
rm protoc.zip
|
||||||
|
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.2
|
||||||
|
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@1958fcbe2ca8bd93af633f11e97d44e567e945af
|
||||||
|
PATH="$PATH:$HOME/go/bin" make protogen-go
|
||||||
|
- name: Dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential
|
||||||
|
# Builds local-ai-backend:privacy-filter, downloads the GGUF, loads it on
|
||||||
|
# CPU and runs the token_classify capability spec (byte-offset contract).
|
||||||
|
- name: Run live PII NER backend E2E
|
||||||
|
run: PATH="$PATH:$HOME/go/bin" make test-extra-backend-privacy-filter
|
||||||
|
- name: Setup tmate session if tests fail
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: mxschmitt/action-tmate@v3.23
|
||||||
|
with:
|
||||||
|
detached: true
|
||||||
|
connect-timeout-seconds: 180
|
||||||
|
limit-access-to-actor: true
|
||||||
2
.github/workflows/tests-ui-e2e.yml
vendored
2
.github/workflows/tests-ui-e2e.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
go-version: ['1.26.x']
|
go-version: ['1.26.x']
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Configure apt mirror on runner
|
- name: Configure apt mirror on runner
|
||||||
|
|||||||
2
.github/workflows/update_swagger.yaml
vendored
2
.github/workflows/update_swagger.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Configure apt mirror on runner
|
- name: Configure apt mirror on runner
|
||||||
uses: ./.github/actions/configure-apt-mirror
|
uses: ./.github/actions/configure-apt-mirror
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -91,3 +91,6 @@ core/http/react-ui/test-results/
|
|||||||
|
|
||||||
# Local worktrees
|
# Local worktrees
|
||||||
.worktrees/
|
.worktrees/
|
||||||
|
|
||||||
|
# SDD / brainstorm scratch (agent-driven development)
|
||||||
|
.superpowers/
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ linters:
|
|||||||
paths:
|
paths:
|
||||||
# Upstream whisper.cpp source tree fetched by the whisper backend Makefile.
|
# Upstream whisper.cpp source tree fetched by the whisper backend Makefile.
|
||||||
- 'backend/go/whisper/sources'
|
- 'backend/go/whisper/sources'
|
||||||
|
# Vendored upstream supertonic pipeline (supertone-inc/supertonic go/helper.go).
|
||||||
|
- 'backend/go/supertonic/helper.go'
|
||||||
- 'docs/'
|
- 'docs/'
|
||||||
rules:
|
rules:
|
||||||
# CLI entry points: kong's `env:"..."` tag is the legitimate env→struct
|
# CLI entry points: kong's `env:"..."` tag is the legitimate env→struct
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ RUN <<EOT bash
|
|||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
cuda-nvcc-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
cuda-nvcc-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
||||||
|
cuda-nvrtc-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
||||||
libcufft-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
libcufft-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
||||||
libcurand-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
libcurand-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
||||||
libcublas-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
libcublas-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
||||||
|
|||||||
29
Makefile
29
Makefile
@@ -1,5 +1,5 @@
|
|||||||
# Disable parallel execution for backend builds
|
# Disable parallel execution for backend builds
|
||||||
.NOTPARALLEL: backends/diffusers backends/llama-cpp backends/turboquant backends/outetts backends/piper backends/stablediffusion-ggml backends/whisper backends/crispasr backends/parakeet-cpp backends/faster-whisper backends/silero-vad backends/local-store backends/huggingface backends/rfdetr backends/rfdetr-cpp backends/insightface backends/speaker-recognition backends/kitten-tts backends/kokoro backends/chatterbox backends/llama-cpp-darwin backends/neutts build-darwin-python-backend build-darwin-go-backend backends/mlx backends/diffuser-darwin backends/mlx-vlm backends/mlx-audio backends/mlx-distributed backends/stablediffusion-ggml-darwin backends/vllm backends/vllm-omni backends/sglang backends/moonshine backends/pocket-tts backends/qwen-tts backends/faster-qwen3-tts backends/qwen-asr backends/nemo backends/voxcpm backends/whisperx backends/ace-step backends/acestep-cpp backends/fish-speech backends/voxtral backends/opus backends/trl backends/llama-cpp-quantization backends/kokoros backends/sam3-cpp backends/qwen3-tts-cpp backends/vibevoice-cpp backends/localvqe backends/tinygrad backends/sherpa-onnx backends/ds4 backends/ds4-darwin backends/liquid-audio
|
.NOTPARALLEL: backends/diffusers backends/llama-cpp backends/turboquant backends/outetts backends/piper backends/stablediffusion-ggml backends/whisper backends/crispasr backends/parakeet-cpp backends/faster-whisper backends/silero-vad backends/local-store backends/huggingface backends/rfdetr backends/rfdetr-cpp backends/insightface backends/speaker-recognition backends/kitten-tts backends/kokoro backends/chatterbox backends/llama-cpp-darwin backends/neutts build-darwin-python-backend build-darwin-go-backend backends/mlx backends/diffuser-darwin backends/mlx-vlm backends/mlx-audio backends/mlx-distributed backends/stablediffusion-ggml-darwin backends/vllm backends/vllm-omni backends/sglang backends/moonshine backends/pocket-tts backends/qwen-tts backends/faster-qwen3-tts backends/qwen-asr backends/nemo backends/voxcpm backends/whisperx backends/ace-step backends/acestep-cpp backends/fish-speech backends/voxtral backends/opus backends/trl backends/llama-cpp-quantization backends/kokoros backends/sam3-cpp backends/qwen3-tts-cpp backends/omnivoice-cpp backends/vibevoice-cpp backends/localvqe backends/tinygrad backends/sherpa-onnx backends/ds4 backends/ds4-darwin backends/liquid-audio backends/supertonic backends/depth-anything-cpp backends/privacy-filter
|
||||||
|
|
||||||
GOCMD=go
|
GOCMD=go
|
||||||
GOTEST=$(GOCMD) test
|
GOTEST=$(GOCMD) test
|
||||||
@@ -566,6 +566,7 @@ prepare-test-extra: protogen-python
|
|||||||
$(MAKE) -C backend/python/speaker-recognition
|
$(MAKE) -C backend/python/speaker-recognition
|
||||||
$(MAKE) -C backend/rust/kokoros kokoros-grpc
|
$(MAKE) -C backend/rust/kokoros kokoros-grpc
|
||||||
$(MAKE) -C backend/go/rfdetr-cpp
|
$(MAKE) -C backend/go/rfdetr-cpp
|
||||||
|
$(MAKE) -C backend/go/locate-anything-cpp
|
||||||
|
|
||||||
test-extra: prepare-test-extra
|
test-extra: prepare-test-extra
|
||||||
$(MAKE) -C backend/python/transformers test
|
$(MAKE) -C backend/python/transformers test
|
||||||
@@ -593,6 +594,9 @@ test-extra: prepare-test-extra
|
|||||||
$(MAKE) -C backend/python/speaker-recognition test
|
$(MAKE) -C backend/python/speaker-recognition test
|
||||||
$(MAKE) -C backend/rust/kokoros test
|
$(MAKE) -C backend/rust/kokoros test
|
||||||
$(MAKE) -C backend/go/rfdetr-cpp test
|
$(MAKE) -C backend/go/rfdetr-cpp test
|
||||||
|
$(MAKE) -C backend/go/locate-anything-cpp test
|
||||||
|
$(MAKE) -C backend/go/depth-anything-cpp test
|
||||||
|
$(MAKE) -C backend/go/supertonic test
|
||||||
|
|
||||||
##
|
##
|
||||||
## End-to-end gRPC tests that exercise a built backend container image.
|
## End-to-end gRPC tests that exercise a built backend container image.
|
||||||
@@ -686,6 +690,16 @@ test-extra-backend-llama-cpp-transcription: docker-build-llama-cpp
|
|||||||
BACKEND_TEST_CTX_SIZE=2048 \
|
BACKEND_TEST_CTX_SIZE=2048 \
|
||||||
$(MAKE) test-extra-backend
|
$(MAKE) test-extra-backend
|
||||||
|
|
||||||
|
## privacy-filter: the PII/NER token-classification backend. Exercises the
|
||||||
|
## TokenClassify RPC and asserts byte-correct, UTF-8-aligned span offsets
|
||||||
|
## against the openai-privacy-filter multilingual GGUF (CPU-runnable, ~50M
|
||||||
|
## active params). This is the live-backend coverage for the PII NER tier.
|
||||||
|
test-extra-backend-privacy-filter: docker-build-privacy-filter
|
||||||
|
BACKEND_IMAGE=local-ai-backend:privacy-filter \
|
||||||
|
BACKEND_TEST_MODEL_URL=https://huggingface.co/LocalAI-io/privacy-filter-multilingual-GGUF/resolve/main/privacy-filter-multilingual-f16.gguf \
|
||||||
|
BACKEND_TEST_CAPS=health,load,token_classify \
|
||||||
|
$(MAKE) test-extra-backend
|
||||||
|
|
||||||
## vllm is resolved from a HuggingFace model id (no file download) and
|
## vllm is resolved from a HuggingFace model id (no file download) and
|
||||||
## exercises Predict + streaming + tool-call extraction via the hermes parser.
|
## exercises Predict + streaming + tool-call extraction via the hermes parser.
|
||||||
## Requires a host CPU with the SIMD instructions the prebuilt vllm CPU
|
## Requires a host CPU with the SIMD instructions the prebuilt vllm CPU
|
||||||
@@ -1160,6 +1174,10 @@ BACKEND_TURBOQUANT = turboquant|turboquant|.|false|false
|
|||||||
# Single-model; hardware-only validation lives at tests/e2e-backends/
|
# Single-model; hardware-only validation lives at tests/e2e-backends/
|
||||||
# (BACKEND_BINARY mode); see docs/superpowers/plans/2026-05-11-ds4-backend.md.
|
# (BACKEND_BINARY mode); see docs/superpowers/plans/2026-05-11-ds4-backend.md.
|
||||||
BACKEND_DS4 = ds4|ds4|.|false|false
|
BACKEND_DS4 = ds4|ds4|.|false|false
|
||||||
|
# privacy-filter wraps the standalone privacy-filter.cpp GGML engine (the
|
||||||
|
# openai-privacy-filter PII/NER token classifier) — the TokenClassify RPC for
|
||||||
|
# the PII redactor tier, on stock ggml with no llama.cpp carry-patches.
|
||||||
|
BACKEND_PRIVACY_FILTER = privacy-filter|privacy-filter|.|false|false
|
||||||
|
|
||||||
# Golang backends
|
# Golang backends
|
||||||
BACKEND_PIPER = piper|golang|.|false|true
|
BACKEND_PIPER = piper|golang|.|false|true
|
||||||
@@ -1171,13 +1189,16 @@ BACKEND_STABLEDIFFUSION_GGML = stablediffusion-ggml|golang|.|--progress=plain|tr
|
|||||||
BACKEND_WHISPER = whisper|golang|.|false|true
|
BACKEND_WHISPER = whisper|golang|.|false|true
|
||||||
BACKEND_CRISPASR = crispasr|golang|.|false|true
|
BACKEND_CRISPASR = crispasr|golang|.|false|true
|
||||||
BACKEND_PARAKEET_CPP = parakeet-cpp|golang|.|false|true
|
BACKEND_PARAKEET_CPP = parakeet-cpp|golang|.|false|true
|
||||||
|
BACKEND_DEPTH_ANYTHING_CPP = depth-anything-cpp|golang|.|false|true
|
||||||
BACKEND_VOXTRAL = voxtral|golang|.|false|true
|
BACKEND_VOXTRAL = voxtral|golang|.|false|true
|
||||||
BACKEND_ACESTEP_CPP = acestep-cpp|golang|.|false|true
|
BACKEND_ACESTEP_CPP = acestep-cpp|golang|.|false|true
|
||||||
BACKEND_QWEN3_TTS_CPP = qwen3-tts-cpp|golang|.|false|true
|
BACKEND_QWEN3_TTS_CPP = qwen3-tts-cpp|golang|.|false|true
|
||||||
|
BACKEND_OMNIVOICE_CPP = omnivoice-cpp|golang|.|false|true
|
||||||
BACKEND_VIBEVOICE_CPP = vibevoice-cpp|golang|.|false|true
|
BACKEND_VIBEVOICE_CPP = vibevoice-cpp|golang|.|false|true
|
||||||
BACKEND_LOCALVQE = localvqe|golang|.|false|true
|
BACKEND_LOCALVQE = localvqe|golang|.|false|true
|
||||||
BACKEND_OPUS = opus|golang|.|false|true
|
BACKEND_OPUS = opus|golang|.|false|true
|
||||||
BACKEND_SHERPA_ONNX = sherpa-onnx|golang|.|false|true
|
BACKEND_SHERPA_ONNX = sherpa-onnx|golang|.|false|true
|
||||||
|
BACKEND_SUPERTONIC = supertonic|golang|.|false|true
|
||||||
|
|
||||||
# Python backends with root context
|
# Python backends with root context
|
||||||
BACKEND_RERANKERS = rerankers|python|.|false|true
|
BACKEND_RERANKERS = rerankers|python|.|false|true
|
||||||
@@ -1251,6 +1272,7 @@ $(eval $(call generate-docker-build-target,$(BACKEND_LLAMA_CPP)))
|
|||||||
$(eval $(call generate-docker-build-target,$(BACKEND_IK_LLAMA_CPP)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_IK_LLAMA_CPP)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_TURBOQUANT)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_TURBOQUANT)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_DS4)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_DS4)))
|
||||||
|
$(eval $(call generate-docker-build-target,$(BACKEND_PRIVACY_FILTER)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_PIPER)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_PIPER)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_LOCAL_STORE)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_LOCAL_STORE)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_CLOUD_PROXY)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_CLOUD_PROXY)))
|
||||||
@@ -1260,6 +1282,7 @@ $(eval $(call generate-docker-build-target,$(BACKEND_STABLEDIFFUSION_GGML)))
|
|||||||
$(eval $(call generate-docker-build-target,$(BACKEND_WHISPER)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_WHISPER)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_CRISPASR)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_CRISPASR)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_PARAKEET_CPP)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_PARAKEET_CPP)))
|
||||||
|
$(eval $(call generate-docker-build-target,$(BACKEND_DEPTH_ANYTHING_CPP)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_VOXTRAL)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_VOXTRAL)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_OPUS)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_OPUS)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_RERANKERS)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_RERANKERS)))
|
||||||
@@ -1292,6 +1315,7 @@ $(eval $(call generate-docker-build-target,$(BACKEND_WHISPERX)))
|
|||||||
$(eval $(call generate-docker-build-target,$(BACKEND_ACE_STEP)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_ACE_STEP)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_ACESTEP_CPP)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_ACESTEP_CPP)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_QWEN3_TTS_CPP)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_QWEN3_TTS_CPP)))
|
||||||
|
$(eval $(call generate-docker-build-target,$(BACKEND_OMNIVOICE_CPP)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_VIBEVOICE_CPP)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_VIBEVOICE_CPP)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_LOCALVQE)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_LOCALVQE)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_MLX)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_MLX)))
|
||||||
@@ -1304,12 +1328,13 @@ $(eval $(call generate-docker-build-target,$(BACKEND_KOKOROS)))
|
|||||||
$(eval $(call generate-docker-build-target,$(BACKEND_SAM3_CPP)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_SAM3_CPP)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_RFDETR_CPP)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_RFDETR_CPP)))
|
||||||
$(eval $(call generate-docker-build-target,$(BACKEND_SHERPA_ONNX)))
|
$(eval $(call generate-docker-build-target,$(BACKEND_SHERPA_ONNX)))
|
||||||
|
$(eval $(call generate-docker-build-target,$(BACKEND_SUPERTONIC)))
|
||||||
|
|
||||||
# Pattern rule for docker-save targets
|
# Pattern rule for docker-save targets
|
||||||
docker-save-%: backend-images
|
docker-save-%: backend-images
|
||||||
docker save local-ai-backend:$* -o backend-images/$*.tar
|
docker save local-ai-backend:$* -o backend-images/$*.tar
|
||||||
|
|
||||||
docker-build-backends: docker-build-llama-cpp docker-build-ik-llama-cpp docker-build-turboquant docker-build-ds4 docker-build-rerankers docker-build-vllm docker-build-vllm-omni docker-build-sglang docker-build-transformers docker-build-outetts docker-build-diffusers docker-build-kokoro docker-build-faster-whisper docker-build-crispasr docker-build-coqui docker-build-chatterbox docker-build-vibevoice docker-build-liquid-audio docker-build-moonshine docker-build-pocket-tts docker-build-qwen-tts docker-build-fish-speech docker-build-faster-qwen3-tts docker-build-qwen-asr docker-build-nemo docker-build-voxcpm docker-build-whisperx docker-build-ace-step docker-build-acestep-cpp docker-build-voxtral docker-build-mlx-distributed docker-build-trl docker-build-llama-cpp-quantization docker-build-tinygrad docker-build-kokoros docker-build-sam3-cpp docker-build-rfdetr-cpp docker-build-qwen3-tts-cpp docker-build-vibevoice-cpp docker-build-localvqe docker-build-insightface docker-build-speaker-recognition docker-build-sherpa-onnx docker-build-cloud-proxy
|
docker-build-backends: docker-build-llama-cpp docker-build-ik-llama-cpp docker-build-turboquant docker-build-ds4 docker-build-rerankers docker-build-vllm docker-build-vllm-omni docker-build-sglang docker-build-transformers docker-build-outetts docker-build-diffusers docker-build-kokoro docker-build-faster-whisper docker-build-crispasr docker-build-coqui docker-build-chatterbox docker-build-vibevoice docker-build-liquid-audio docker-build-moonshine docker-build-pocket-tts docker-build-qwen-tts docker-build-fish-speech docker-build-faster-qwen3-tts docker-build-qwen-asr docker-build-nemo docker-build-voxcpm docker-build-whisperx docker-build-ace-step docker-build-acestep-cpp docker-build-voxtral docker-build-mlx-distributed docker-build-trl docker-build-llama-cpp-quantization docker-build-tinygrad docker-build-kokoros docker-build-sam3-cpp docker-build-rfdetr-cpp docker-build-qwen3-tts-cpp docker-build-omnivoice-cpp docker-build-vibevoice-cpp docker-build-localvqe docker-build-insightface docker-build-speaker-recognition docker-build-sherpa-onnx docker-build-cloud-proxy docker-build-supertonic docker-build-depth-anything-cpp docker-build-privacy-filter
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
### Mock Backend for E2E Tests
|
### Mock Backend for E2E Tests
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -29,6 +29,18 @@
|
|||||||
<a href="https://trendshift.io/repositories/5539" target="_blank"><img src="https://trendshift.io/api/badge/repositories/5539" alt="mudler%2FLocalAI | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/5539" target="_blank"><img src="https://trendshift.io/api/badge/repositories/5539" alt="mudler%2FLocalAI | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<!-- Keep these links, translations synced daily. -->
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://zdoc.app/de/mudler/LocalAI">Deutsch</a> |
|
||||||
|
<a href="https://zdoc.app/es/mudler/LocalAI">Español</a> |
|
||||||
|
<a href="https://zdoc.app/fr/mudler/LocalAI">français</a> |
|
||||||
|
<a href="https://zdoc.app/ja/mudler/LocalAI">日本語</a> |
|
||||||
|
<a href="https://zdoc.app/ko/mudler/LocalAI">한국어</a> |
|
||||||
|
<a href="https://zdoc.app/pt/mudler/LocalAI">Português</a> |
|
||||||
|
<a href="https://zdoc.app/ru/mudler/LocalAI">Русский</a> |
|
||||||
|
<a href="https://zdoc.app/zh/mudler/LocalAI">中文</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
**LocalAI** is the open-source AI engine. Run any model - LLMs, vision, voice, image, video - on any hardware. No GPU required.
|
**LocalAI** is the open-source AI engine. Run any model - LLMs, vision, voice, image, video - on any hardware. No GPU required.
|
||||||
|
|
||||||
**A small core, not a bundle.** Each backend wraps a best-in-class engine (llama.cpp, vLLM, whisper.cpp, stable-diffusion, MLX...) in its own image, pulled only when a model needs it. You install nothing you don't use.
|
**A small core, not a bundle.** Each backend wraps a best-in-class engine (llama.cpp, vLLM, whisper.cpp, stable-diffusion, MLX...) in its own image, pulled only when a model needs it. You install nothing you don't use.
|
||||||
@@ -165,6 +177,10 @@ For more details, see the [Getting Started guide](https://localai.io/basics/gett
|
|||||||
|
|
||||||
## Latest News
|
## Latest News
|
||||||
|
|
||||||
|
- **June 2026**: New [realtime voice assistant demo](https://github.com/localai-org/localai-realtime-demo) (a tiny Go client for the Realtime API with a full talk-back voice loop and tool calling), plus [streaming of the realtime LLM / TTS / transcription pipeline stages](https://github.com/mudler/LocalAI/pull/10176) and [configurable WebRTC ICE candidates](https://github.com/mudler/LocalAI/pull/10231).
|
||||||
|
- **June 2026**: Big speech push: the [parakeet.cpp](https://github.com/mudler/parakeet.cpp) ASR engine gains [NeMo-faithful segment timestamps](https://github.com/mudler/LocalAI/pull/10207), a [multilingual streaming Nemotron-3.5 model](https://github.com/mudler/LocalAI/pull/10199), [dynamic batching for concurrent transcription](https://github.com/mudler/LocalAI/pull/10112) and [CUDA graphs](https://github.com/mudler/LocalAI/pull/10273); the new [CrispASR backend](https://github.com/mudler/LocalAI/pull/10099) adds multi-architecture ASR + TTS, and [60 Piper TTS voices across 42 languages](https://github.com/mudler/LocalAI/pull/10296) land in the gallery (plus [per-request TTS instructions and params](https://github.com/mudler/LocalAI/pull/10172)).
|
||||||
|
- **June 2026**: New backends and models: [locate-anything.cpp](https://github.com/mudler/LocalAI/pull/10264) for open-vocabulary object detection via ggml, [Ideogram4 image generation](https://github.com/mudler/LocalAI/pull/10201) in stablediffusion-ggml, [llama.cpp video input](https://github.com/mudler/LocalAI/pull/10216), and the [Gemma 4 QAT family with MTP speculative-decoding pairs](https://github.com/mudler/LocalAI/pull/10215). Plus an [interactive CLI chat mode](https://github.com/mudler/LocalAI/pull/10226) and [RAG source citations in agent responses](https://github.com/mudler/LocalAI/pull/10228).
|
||||||
|
- **June 2026**: Distributed mode hardening: [prefix-cache-aware routing](https://github.com/mudler/LocalAI/pull/10071), a [production-ready request router with auto-sized embedding/rerank batches](https://github.com/mudler/LocalAI/pull/10104), [ds4 layer-split distributed inference](https://github.com/mudler/LocalAI/pull/10098), [NATS JWT auth + TLS/mTLS](https://github.com/mudler/LocalAI/pull/10159), and [resumable file uploads](https://github.com/mudler/LocalAI/pull/10109).
|
||||||
- **May 2026**: **LocalAI 4.3.0** - `llama.cpp` [prompt cache on by default](https://github.com/mudler/LocalAI/pull/9925) (repeated system prompts collapse from minutes to seconds), [keyless cosign signing of backend OCI images](https://github.com/mudler/LocalAI/pull/9823), [per-API-key + per-user usage attribution](https://github.com/mudler/LocalAI/pull/9920), Distributed v3 with [per-request replica routing](https://github.com/mudler/LocalAI/pull/9968). [Release notes](https://github.com/mudler/LocalAI/releases/tag/v4.3.0)
|
- **May 2026**: **LocalAI 4.3.0** - `llama.cpp` [prompt cache on by default](https://github.com/mudler/LocalAI/pull/9925) (repeated system prompts collapse from minutes to seconds), [keyless cosign signing of backend OCI images](https://github.com/mudler/LocalAI/pull/9823), [per-API-key + per-user usage attribution](https://github.com/mudler/LocalAI/pull/9920), Distributed v3 with [per-request replica routing](https://github.com/mudler/LocalAI/pull/9968). [Release notes](https://github.com/mudler/LocalAI/releases/tag/v4.3.0)
|
||||||
- **May 2026**: **LocalAI 4.2.0** - LocalAI sees and hears: [voice recognition](https://github.com/mudler/LocalAI/pull/9500), [face recognition + antispoofing liveness](https://github.com/mudler/LocalAI/pull/9480), speaker diarization. Plus [drop-in Ollama API](https://github.com/mudler/LocalAI/pull/9284), [video generation](https://github.com/mudler/LocalAI/pull/9420), redesigned UI with i18n + admin-configurable branding, vLLM at feature parity with llama.cpp, and 11 new backends. [Release notes](https://github.com/mudler/LocalAI/releases/tag/v4.2.0)
|
- **May 2026**: **LocalAI 4.2.0** - LocalAI sees and hears: [voice recognition](https://github.com/mudler/LocalAI/pull/9500), [face recognition + antispoofing liveness](https://github.com/mudler/LocalAI/pull/9480), speaker diarization. Plus [drop-in Ollama API](https://github.com/mudler/LocalAI/pull/9284), [video generation](https://github.com/mudler/LocalAI/pull/9420), redesigned UI with i18n + admin-configurable branding, vLLM at feature parity with llama.cpp, and 11 new backends. [Release notes](https://github.com/mudler/LocalAI/releases/tag/v4.2.0)
|
||||||
- **April 2026**: **LocalAI 4.1.0** - LocalAI becomes a control tower: distributed cluster mode with VRAM-aware smart routing + autoscaling, multi-user platform with OIDC and API keys, per-user quotas with predictive analytics, in-UI fine-tuning with TRL (auto-export to GGUF), on-the-fly quantization backend, visual pipeline editor. [Release notes](https://github.com/mudler/LocalAI/releases/tag/v4.1.0)
|
- **April 2026**: **LocalAI 4.1.0** - LocalAI becomes a control tower: distributed cluster mode with VRAM-aware smart routing + autoscaling, multi-user platform with OIDC and API keys, per-user quotas with predictive analytics, in-UI fine-tuning with TRL (auto-export to GGUF), on-the-fly quantization backend, visual pipeline editor. [Release notes](https://github.com/mudler/LocalAI/releases/tag/v4.1.0)
|
||||||
@@ -204,10 +220,29 @@ For older news and full release notes, see [GitHub Releases](https://github.com/
|
|||||||
|
|
||||||
## Supported Backends & Acceleration
|
## Supported Backends & Acceleration
|
||||||
|
|
||||||
LocalAI supports **36+ backends** including llama.cpp, vLLM, transformers, whisper.cpp, diffusers, MLX, MLX-VLM, and many more. Hardware acceleration is available for **NVIDIA** (CUDA 12/13), **AMD** (ROCm), **Intel** (oneAPI/SYCL), **Apple Silicon** (Metal), **Vulkan**, and **NVIDIA Jetson** (L4T). All backends can be installed on-the-fly from the [Backend Gallery](https://localai.io/backends/).
|
LocalAI supports **60+ backends** including llama.cpp, vLLM, SGLang, transformers, whisper.cpp, diffusers, MLX, MLX-VLM, and many more. Hardware acceleration is available for **NVIDIA** (CUDA 12/13), **AMD** (ROCm), **Intel** (oneAPI/SYCL), **Apple Silicon** (Metal), **Vulkan**, and **NVIDIA Jetson** (L4T). All backends can be installed on-the-fly from the [Backend Gallery](https://localai.io/backends/).
|
||||||
|
|
||||||
See the full [Backend & Model Compatibility Table](https://localai.io/model-compatibility/) and [GPU Acceleration guide](https://localai.io/features/gpu-acceleration/).
|
See the full [Backend & Model Compatibility Table](https://localai.io/model-compatibility/) and [GPU Acceleration guide](https://localai.io/features/gpu-acceleration/).
|
||||||
|
|
||||||
|
### Backends built by us
|
||||||
|
|
||||||
|
Most backends wrap a best-in-class upstream engine. A handful of them are native C/C++/GGML engines (no Python at inference) developed and maintained by the LocalAI project itself:
|
||||||
|
|
||||||
|
| Backend | What it does |
|
||||||
|
|---------|-------------|
|
||||||
|
| [parakeet.cpp](https://github.com/mudler/parakeet.cpp) | C++/GGML port of NVIDIA NeMo Parakeet ASR (tdt/ctc/rnnt/hybrid), with cache-aware streaming transcription |
|
||||||
|
| [ced.cpp](https://github.com/mudler/ced.cpp) | C++/GGML port of the CED audio-tagging models: sound-event classification (527-class AudioSet) over REST and the realtime API for live recognition |
|
||||||
|
| [voxtral.c](https://github.com/mudler/voxtral.c) | Voxtral Realtime 4B speech-to-text in pure C |
|
||||||
|
| [vibevoice.cpp](https://github.com/mudler/vibevoice.cpp) | Native port of Microsoft VibeVoice for TTS (voice cloning) and long-form ASR with speaker diarization |
|
||||||
|
| [rf-detr.cpp](https://github.com/mudler/rf-detr.cpp) | Native RF-DETR object detection and instance segmentation |
|
||||||
|
| [locate-anything.cpp](https://github.com/mudler/locate-anything.cpp) | Open-vocabulary object detection and visual grounding (LocateAnything-3B) |
|
||||||
|
| [depth-anything.cpp](https://github.com/mudler/depth-anything.cpp) | Depth Anything 3 monocular metric depth + camera pose estimation |
|
||||||
|
| [privacy-filter.cpp](https://github.com/localai-org/privacy-filter.cpp) | Standalone GGML PII/NER token-classification engine powering LocalAI's PII redaction tier |
|
||||||
|
| [LocalVQE](https://github.com/localai-org/LocalVQE) | Joint acoustic echo cancellation, noise suppression, and dereverberation |
|
||||||
|
| [local-store](https://github.com/mudler/LocalAI) | Local-first vector database for embeddings (shipped in-tree) |
|
||||||
|
|
||||||
|
We also maintain [apex-quant](https://github.com/localai-org/apex-quant), a per-tensor, per-layer quantization recipe for Mixture-of-Experts models that exploits their structural sparsity to produce GGUFs matching or beating Q8_0 quality - and they run out of the box on stock llama.cpp.
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
- [Documentation](https://localai.io/)
|
- [Documentation](https://localai.io/)
|
||||||
@@ -217,7 +252,7 @@ See the full [Backend & Model Compatibility Table](https://localai.io/model-comp
|
|||||||
- [Integrations & community projects](https://localai.io/docs/integrations/)
|
- [Integrations & community projects](https://localai.io/docs/integrations/)
|
||||||
- [Installation video walkthrough](https://www.youtube.com/watch?v=cMVNnlqwfw4)
|
- [Installation video walkthrough](https://www.youtube.com/watch?v=cMVNnlqwfw4)
|
||||||
- [Media & blog posts](https://localai.io/basics/news/#media-blogs-social)
|
- [Media & blog posts](https://localai.io/basics/news/#media-blogs-social)
|
||||||
- [Examples](https://github.com/mudler/LocalAI-examples)
|
- [Examples](https://github.com/mudler/LocalAI-examples) — including the [realtime voice assistant demo](https://github.com/localai-org/localai-realtime-demo) (Go client for the Realtime API with tool calling)
|
||||||
|
|
||||||
## Team
|
## Team
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,12 @@ RUN <<EOT bash
|
|||||||
libwayland-dev libxrandr-dev libxcb-randr0-dev libxcb-ewmh-dev \
|
libwayland-dev libxrandr-dev libxcb-randr0-dev libxcb-ewmh-dev \
|
||||||
git python-is-python3 bison libx11-xcb-dev liblz4-dev libzstd-dev \
|
git python-is-python3 bison libx11-xcb-dev liblz4-dev libzstd-dev \
|
||||||
ocaml-core ninja-build pkg-config libxml2-dev wayland-protocols python3-jsonschema \
|
ocaml-core ninja-build pkg-config libxml2-dev wayland-protocols python3-jsonschema \
|
||||||
clang-format qtbase5-dev qt6-base-dev libxcb-glx0-dev sudo xz-utils
|
clang-format qtbase5-dev qt6-base-dev libxcb-glx0-dev sudo xz-utils && \
|
||||||
|
apt-get install -y mesa-vulkan-drivers libdrm2
|
||||||
|
# Mesa Vulkan ICD drivers (ANV/RADV/lavapipe) + their manifests. The
|
||||||
|
# LunarG SDK below only provides the loader and shader tooling, not
|
||||||
|
# hardware drivers — without Mesa, package-gpu-libs.sh has no ICD to
|
||||||
|
# bundle and the packaged backend finds no GPU at runtime.
|
||||||
if [ "amd64" = "$TARGETARCH" ]; then
|
if [ "amd64" = "$TARGETARCH" ]; then
|
||||||
wget "https://sdk.lunarg.com/sdk/download/1.4.335.0/linux/vulkansdk-linux-x86_64-1.4.335.0.tar.xz" && \
|
wget "https://sdk.lunarg.com/sdk/download/1.4.335.0/linux/vulkansdk-linux-x86_64-1.4.335.0.tar.xz" && \
|
||||||
tar -xf vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
tar -xf vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
||||||
@@ -206,6 +211,16 @@ RUN if [ "${BACKEND}" = "opus" ]; then \
|
|||||||
apt-get clean && rm -rf /var/lib/apt/lists/*; \
|
apt-get clean && rm -rf /var/lib/apt/lists/*; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# CrispASR's piper TTS backend dlopens libespeak-ng at runtime to phonemize
|
||||||
|
# non-English text (the MIT-clean path; English uses a built-in G2P). Install
|
||||||
|
# the espeak-ng runtime + its libpcaudio/libsonic deps + voice data so
|
||||||
|
# package.sh can bundle them into the FROM scratch image.
|
||||||
|
RUN if [ "${BACKEND}" = "crispasr" ]; then \
|
||||||
|
apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
espeak-ng-data libespeak-ng1 libpcaudio0 libsonic0 && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/*; \
|
||||||
|
fi
|
||||||
|
|
||||||
COPY . /LocalAI
|
COPY . /LocalAI
|
||||||
|
|
||||||
RUN git config --global --add safe.directory /LocalAI
|
RUN git config --global --add safe.directory /LocalAI
|
||||||
|
|||||||
109
backend/Dockerfile.privacy-filter
Normal file
109
backend/Dockerfile.privacy-filter
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
ARG BASE_IMAGE=ubuntu:24.04
|
||||||
|
# BUILDER_BASE_IMAGE defaults to BASE_IMAGE so the Dockerfile parses when no
|
||||||
|
# prebuilt base is supplied; the builder-prebuilt stage is only entered when
|
||||||
|
# BUILDER_TARGET=builder-prebuilt, so the fallback content is harmless
|
||||||
|
# (BuildKit prunes the unreferenced builder).
|
||||||
|
ARG BUILDER_BASE_IMAGE=${BASE_IMAGE}
|
||||||
|
# BUILDER_TARGET selects which builder stage the scratch image copies from.
|
||||||
|
# Declared before any FROM so it is usable in `FROM ${BUILDER_TARGET}`. The
|
||||||
|
# backend_build workflow sets it to builder-prebuilt when the matrix entry
|
||||||
|
# provides builder-base-image, else builder-fromsource (the local default).
|
||||||
|
ARG BUILDER_TARGET=builder-fromsource
|
||||||
|
ARG APT_MIRROR=""
|
||||||
|
ARG APT_PORTS_MIRROR=""
|
||||||
|
|
||||||
|
# privacy-filter: standalone GGML engine for the openai-privacy-filter PII/NER
|
||||||
|
# token classifier, wrapped as a LocalAI gRPC backend.
|
||||||
|
#
|
||||||
|
# Mirrors backend/Dockerfile.llama-cpp: the build toolchain (gRPC + cmake +
|
||||||
|
# protoc + conditional CUDA/Vulkan) comes from the shared
|
||||||
|
# .docker/install-base-deps.sh (from-source path) or a prebuilt
|
||||||
|
# quay.io/go-skynet/ci-cache:base-grpc-* image (CI path) — nothing GPU-specific
|
||||||
|
# is hand-rolled here. BUILD_TYPE selects the engine backend in the Makefile:
|
||||||
|
# "" = cpu, "cublas" -> -DPF_CUDA=ON, "vulkan" -> -DPF_VULKAN=ON.
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Stage: builder-fromsource — self-contained build. Runs the same install
|
||||||
|
# script backend/Dockerfile.base-grpc-builder runs, so this path is
|
||||||
|
# bit-equivalent to the prebuilt base. Used when BUILDER_TARGET=builder-fromsource
|
||||||
|
# (the default; local `make backends/privacy-filter`).
|
||||||
|
# ============================================================================
|
||||||
|
FROM ${BASE_IMAGE} AS builder-fromsource
|
||||||
|
ARG BUILD_TYPE
|
||||||
|
ARG CUDA_MAJOR_VERSION
|
||||||
|
ARG CUDA_MINOR_VERSION
|
||||||
|
ARG CMAKE_FROM_SOURCE=false
|
||||||
|
# CUDA Toolkit 13.x needs CMake 3.31.9+ for correct toolchain/arch detection.
|
||||||
|
ARG CMAKE_VERSION=3.31.10
|
||||||
|
ARG GRPC_VERSION=v1.65.0
|
||||||
|
ARG GRPC_MAKEFLAGS="-j4 -Otarget"
|
||||||
|
ARG SKIP_DRIVERS=false
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG UBUNTU_VERSION=2404
|
||||||
|
ARG APT_MIRROR
|
||||||
|
ARG APT_PORTS_MIRROR
|
||||||
|
|
||||||
|
ENV BUILD_TYPE=${BUILD_TYPE} \
|
||||||
|
CUDA_MAJOR_VERSION=${CUDA_MAJOR_VERSION} \
|
||||||
|
CUDA_MINOR_VERSION=${CUDA_MINOR_VERSION} \
|
||||||
|
CMAKE_FROM_SOURCE=${CMAKE_FROM_SOURCE} \
|
||||||
|
CMAKE_VERSION=${CMAKE_VERSION} \
|
||||||
|
GRPC_VERSION=${GRPC_VERSION} \
|
||||||
|
GRPC_MAKEFLAGS=${GRPC_MAKEFLAGS} \
|
||||||
|
SKIP_DRIVERS=${SKIP_DRIVERS} \
|
||||||
|
TARGETARCH=${TARGETARCH} \
|
||||||
|
UBUNTU_VERSION=${UBUNTU_VERSION} \
|
||||||
|
APT_MIRROR=${APT_MIRROR} \
|
||||||
|
APT_PORTS_MIRROR=${APT_PORTS_MIRROR} \
|
||||||
|
DEBIAN_FRONTEND=noninteractive
|
||||||
|
# CUDA on PATH (a no-op when CUDA is not installed, e.g. cpu/vulkan builds).
|
||||||
|
ENV PATH=/usr/local/cuda/bin:${PATH}
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# apt deps + cmake + protoc + gRPC + conditional CUDA/Vulkan, all from the
|
||||||
|
# shared script (the source of truth that base-grpc-builder also runs).
|
||||||
|
RUN --mount=type=bind,source=.docker/install-base-deps.sh,target=/usr/local/sbin/install-base-deps \
|
||||||
|
--mount=type=bind,source=.docker/apt-mirror.sh,target=/usr/local/sbin/apt-mirror \
|
||||||
|
bash /usr/local/sbin/install-base-deps
|
||||||
|
|
||||||
|
# install-base-deps installs gRPC under /opt/grpc; copy it to /usr/local so the
|
||||||
|
# backend's find_package(gRPC CONFIG) resolves it at the canonical prefix.
|
||||||
|
RUN cp -a /opt/grpc/. /usr/local/
|
||||||
|
|
||||||
|
COPY . /LocalAI
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/root/.ccache,id=privacy-filter-ccache-${TARGETARCH}-${BUILD_TYPE},sharing=locked \
|
||||||
|
make -C /LocalAI/backend/cpp/privacy-filter BUILD_TYPE=${BUILD_TYPE} NATIVE=false grpc-server package
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Stage: builder-prebuilt — FROM a prebuilt
|
||||||
|
# quay.io/go-skynet/ci-cache:base-grpc-* image (gRPC at /opt/grpc + apt deps +
|
||||||
|
# CUDA/Vulkan already installed). Used in CI when the matrix entry sets
|
||||||
|
# builder-base-image.
|
||||||
|
# ============================================================================
|
||||||
|
FROM ${BUILDER_BASE_IMAGE} AS builder-prebuilt
|
||||||
|
ARG BUILD_TYPE
|
||||||
|
ARG TARGETARCH
|
||||||
|
ENV BUILD_TYPE=${BUILD_TYPE}
|
||||||
|
# CUDA on PATH (a no-op for the cpu/vulkan base images).
|
||||||
|
ENV PATH=/usr/local/cuda/bin:${PATH}
|
||||||
|
|
||||||
|
# Mirror builder-fromsource: the base-grpc image installs gRPC to /opt/grpc but
|
||||||
|
# does not copy it to /usr/local.
|
||||||
|
RUN cp -a /opt/grpc/. /usr/local/
|
||||||
|
|
||||||
|
COPY . /LocalAI
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/root/.ccache,id=privacy-filter-ccache-${TARGETARCH}-${BUILD_TYPE},sharing=locked \
|
||||||
|
make -C /LocalAI/backend/cpp/privacy-filter BUILD_TYPE=${BUILD_TYPE} NATIVE=false grpc-server package
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Final stage — copy the package output from the selected builder. BuildKit
|
||||||
|
# does not expand variables in `COPY --from=`, so alias the chosen builder to a
|
||||||
|
# fixed stage name first.
|
||||||
|
# ============================================================================
|
||||||
|
FROM ${BUILDER_TARGET} AS builder
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=builder /LocalAI/backend/cpp/privacy-filter/package/. ./
|
||||||
@@ -66,7 +66,12 @@ RUN <<EOT bash
|
|||||||
libwayland-dev libxrandr-dev libxcb-randr0-dev libxcb-ewmh-dev \
|
libwayland-dev libxrandr-dev libxcb-randr0-dev libxcb-ewmh-dev \
|
||||||
git python-is-python3 bison libx11-xcb-dev liblz4-dev libzstd-dev \
|
git python-is-python3 bison libx11-xcb-dev liblz4-dev libzstd-dev \
|
||||||
ocaml-core ninja-build pkg-config libxml2-dev wayland-protocols python3-jsonschema \
|
ocaml-core ninja-build pkg-config libxml2-dev wayland-protocols python3-jsonschema \
|
||||||
clang-format qtbase5-dev qt6-base-dev libxcb-glx0-dev sudo xz-utils
|
clang-format qtbase5-dev qt6-base-dev libxcb-glx0-dev sudo xz-utils && \
|
||||||
|
apt-get install -y mesa-vulkan-drivers libdrm2
|
||||||
|
# Mesa Vulkan ICD drivers (ANV/RADV/lavapipe) + their manifests. The
|
||||||
|
# LunarG SDK below only provides the loader and shader tooling, not
|
||||||
|
# hardware drivers — without Mesa, package-gpu-libs.sh has no ICD to
|
||||||
|
# bundle and the packaged backend finds no GPU at runtime.
|
||||||
if [ "amd64" = "$TARGETARCH" ]; then
|
if [ "amd64" = "$TARGETARCH" ]; then
|
||||||
wget "https://sdk.lunarg.com/sdk/download/1.4.335.0/linux/vulkansdk-linux-x86_64-1.4.335.0.tar.xz" && \
|
wget "https://sdk.lunarg.com/sdk/download/1.4.335.0/linux/vulkansdk-linux-x86_64-1.4.335.0.tar.xz" && \
|
||||||
tar -xf vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
tar -xf vulkansdk-linux-x86_64-1.4.335.0.tar.xz && \
|
||||||
@@ -126,6 +131,7 @@ RUN <<EOT bash
|
|||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
cuda-nvcc-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
cuda-nvcc-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
||||||
|
cuda-nvrtc-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
||||||
libcufft-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
libcufft-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
||||||
libcurand-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
libcurand-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
||||||
libcublas-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
libcublas-dev-${CUDA_MAJOR_VERSION}-${CUDA_MINOR_VERSION} \
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ service Backend {
|
|||||||
rpc TokenizeString(PredictOptions) returns (TokenizationResponse) {}
|
rpc TokenizeString(PredictOptions) returns (TokenizationResponse) {}
|
||||||
rpc Status(HealthMessage) returns (StatusResponse) {}
|
rpc Status(HealthMessage) returns (StatusResponse) {}
|
||||||
rpc Detect(DetectOptions) returns (DetectResponse) {}
|
rpc Detect(DetectOptions) returns (DetectResponse) {}
|
||||||
|
// SoundDetection runs an audio-tagging / sound-event-classification model
|
||||||
|
// (e.g. CED over the AudioSet ontology) on a clip and returns scored labels.
|
||||||
|
rpc SoundDetection(SoundDetectionRequest) returns (SoundDetectionResponse) {}
|
||||||
|
rpc Depth(DepthRequest) returns (DepthResponse) {}
|
||||||
rpc FaceVerify(FaceVerifyRequest) returns (FaceVerifyResponse) {}
|
rpc FaceVerify(FaceVerifyRequest) returns (FaceVerifyResponse) {}
|
||||||
rpc FaceAnalyze(FaceAnalyzeRequest) returns (FaceAnalyzeResponse) {}
|
rpc FaceAnalyze(FaceAnalyzeRequest) returns (FaceAnalyzeResponse) {}
|
||||||
rpc VoiceVerify(VoiceVerifyRequest) returns (VoiceVerifyResponse) {}
|
rpc VoiceVerify(VoiceVerifyRequest) returns (VoiceVerifyResponse) {}
|
||||||
@@ -670,6 +674,53 @@ message DetectResponse {
|
|||||||
repeated Detection Detections = 1;
|
repeated Detection Detections = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Sound-event classification / audio tagging messages (CED) ---
|
||||||
|
|
||||||
|
message SoundDetectionRequest {
|
||||||
|
string src = 1; // audio file path (LocalAI writes the upload to disk)
|
||||||
|
int32 top_k = 2; // number of top tags to return (0 = all classes)
|
||||||
|
float threshold = 3; // optional: drop tags scoring below this
|
||||||
|
}
|
||||||
|
|
||||||
|
message SoundClass {
|
||||||
|
string label = 1; // AudioSet class name, e.g. "Baby cry, infant cry"
|
||||||
|
float score = 2; // per-class probability (multi-label, independent)
|
||||||
|
int32 index = 3; // class index in the model ontology
|
||||||
|
}
|
||||||
|
|
||||||
|
message SoundDetectionResponse {
|
||||||
|
repeated SoundClass detections = 1; // score-descending
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Depth estimation messages (Depth Anything 3) ---
|
||||||
|
|
||||||
|
message DepthRequest {
|
||||||
|
string src = 1; // input image (filesystem path or base64-encoded payload)
|
||||||
|
string dst = 2; // optional output directory for exports (glb/colmap)
|
||||||
|
bool include_depth = 3; // return the per-pixel metric depth map
|
||||||
|
bool include_confidence = 4; // return the per-pixel confidence map (DualDPT)
|
||||||
|
bool include_pose = 5; // return camera extrinsics/intrinsics (DualDPT)
|
||||||
|
bool include_sky = 6; // return the per-pixel sky map (mono models)
|
||||||
|
bool include_points = 7; // back-project to a 3D point cloud (DualDPT)
|
||||||
|
float points_conf_thresh = 8; // keep points with confidence >= this threshold
|
||||||
|
repeated string exports = 9; // requested exports: "glb", "colmap"
|
||||||
|
}
|
||||||
|
|
||||||
|
message DepthResponse {
|
||||||
|
int32 width = 1; // processed depth-map width
|
||||||
|
int32 height = 2; // processed depth-map height
|
||||||
|
repeated float depth = 3; // width*height row-major metric depth
|
||||||
|
repeated float confidence = 4; // width*height row-major confidence (DualDPT)
|
||||||
|
repeated float sky = 5; // width*height row-major sky map (mono)
|
||||||
|
repeated float extrinsics = 6; // 12 floats, 3x4 row-major (world-to-camera)
|
||||||
|
repeated float intrinsics = 7; // 9 floats, 3x3 row-major
|
||||||
|
int32 num_points = 8; // number of 3D points
|
||||||
|
repeated float points = 9; // num_points*3 xyz, world space
|
||||||
|
bytes point_colors = 10; // num_points*3 uint8 rgb
|
||||||
|
repeated string export_paths = 11; // paths written for the requested exports
|
||||||
|
bool is_metric = 12; // depth is in metric units
|
||||||
|
}
|
||||||
|
|
||||||
// --- Face recognition messages ---
|
// --- Face recognition messages ---
|
||||||
|
|
||||||
message FacialArea {
|
message FacialArea {
|
||||||
|
|||||||
@@ -9,6 +9,22 @@ option(DS4_NATIVE "Compile with -march=native / -mcpu=native" ON)
|
|||||||
set(DS4_GPU "cpu" CACHE STRING "GPU backend: cpu, cuda, or metal")
|
set(DS4_GPU "cpu" CACHE STRING "GPU backend: cpu, cuda, or metal")
|
||||||
set(DS4_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ds4" CACHE PATH "Path to cloned ds4 source")
|
set(DS4_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ds4" CACHE PATH "Path to cloned ds4 source")
|
||||||
|
|
||||||
|
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||||
|
# Homebrew installs protobuf/grpc under a non-default prefix. The generated
|
||||||
|
# backend.pb.cc / backend.grpc.pb.cc pull in google/protobuf and grpcpp
|
||||||
|
# headers, but the hw_grpc_proto library links neither target, so on macOS
|
||||||
|
# the headers (e.g. google/protobuf/runtime_version.h) are never on the
|
||||||
|
# compiler's include path. Add the Homebrew prefix globally, matching the
|
||||||
|
# llama-cpp backend which builds on Darwin CI.
|
||||||
|
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "arm64")
|
||||||
|
set(HOMEBREW_DEFAULT_PREFIX "/opt/homebrew")
|
||||||
|
else()
|
||||||
|
set(HOMEBREW_DEFAULT_PREFIX "/usr/local")
|
||||||
|
endif()
|
||||||
|
link_directories("${HOMEBREW_DEFAULT_PREFIX}/lib")
|
||||||
|
include_directories("${HOMEBREW_DEFAULT_PREFIX}/include")
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
find_package(Protobuf CONFIG QUIET)
|
find_package(Protobuf CONFIG QUIET)
|
||||||
if(NOT Protobuf_FOUND)
|
if(NOT Protobuf_FOUND)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# ds4 backend Makefile.
|
# ds4 backend Makefile.
|
||||||
#
|
#
|
||||||
# Upstream pin lives below as DS4_VERSION?=8384adf0f9fa0f3bb342dd925372de778b95b263
|
# Upstream pin lives below as DS4_VERSION?=80ebbc396aee40eedc1d829222f3362d10fa4c6c
|
||||||
# (.github/bump_deps.sh) can find and update it - matches the
|
# (.github/bump_deps.sh) can find and update it - matches the
|
||||||
# llama-cpp / ik-llama-cpp / turboquant convention.
|
# llama-cpp / ik-llama-cpp / turboquant convention.
|
||||||
|
|
||||||
DS4_VERSION?=8384adf0f9fa0f3bb342dd925372de778b95b263
|
DS4_VERSION?=80ebbc396aee40eedc1d829222f3362d10fa4c6c
|
||||||
DS4_REPO?=https://github.com/antirez/ds4
|
DS4_REPO?=https://github.com/antirez/ds4
|
||||||
|
|
||||||
CURRENT_MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
CURRENT_MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ extern "C" {
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
@@ -105,6 +107,130 @@ static bool parse_layers_spec(const std::string &spec, ds4_distributed_layers *o
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse a boolean LoadModel option. An empty value (a bare flag-style option
|
||||||
|
// like "ssd_streaming" with no colon) means true so model YAMLs can write
|
||||||
|
// options: ["ssd_streaming"] to enable a switch.
|
||||||
|
static bool parse_bool_option(const std::string &s, bool *out) {
|
||||||
|
if (s.empty() || s == "true" || s == "1" || s == "yes" || s == "on") { *out = true; return true; }
|
||||||
|
if (s == "false" || s == "0" || s == "no" || s == "off") { *out = false; return true; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table-driven mapping from LoadModel option keys to ds4_engine_options fields.
|
||||||
|
// ds4_engine_options is a fixed C struct with no reflection, so the field set
|
||||||
|
// is enumerated once here; adding a future engine knob is a one-line table
|
||||||
|
// entry rather than a new branch in LoadModel. Two fields need ds4's own typed
|
||||||
|
// parsers (Gib, CacheExperts) so a plain string passthrough can't cover them.
|
||||||
|
enum class DsOptType { Bool, Int, Uint, Float, Str, Gib, CacheExperts };
|
||||||
|
|
||||||
|
struct DsOptSpec {
|
||||||
|
const char *key;
|
||||||
|
DsOptType type;
|
||||||
|
size_t off; // byte offset into ds4_engine_options
|
||||||
|
size_t off2; // second offset (CacheExperts writes experts + bytes)
|
||||||
|
bool is_path; // Str values: resolve a relative value against the model dir
|
||||||
|
};
|
||||||
|
|
||||||
|
static const DsOptSpec kEngineOptSpecs[] = {
|
||||||
|
{"mtp_path", DsOptType::Str, offsetof(ds4_engine_options, mtp_path), 0, true},
|
||||||
|
{"mtp_draft", DsOptType::Int, offsetof(ds4_engine_options, mtp_draft_tokens), 0},
|
||||||
|
{"mtp_margin", DsOptType::Float, offsetof(ds4_engine_options, mtp_margin), 0},
|
||||||
|
{"prefill_chunk", DsOptType::Uint, offsetof(ds4_engine_options, prefill_chunk), 0},
|
||||||
|
{"power_percent", DsOptType::Int, offsetof(ds4_engine_options, power_percent), 0},
|
||||||
|
{"warm_weights", DsOptType::Bool, offsetof(ds4_engine_options, warm_weights), 0},
|
||||||
|
{"quality", DsOptType::Bool, offsetof(ds4_engine_options, quality), 0},
|
||||||
|
{"ssd_streaming", DsOptType::Bool, offsetof(ds4_engine_options, ssd_streaming), 0},
|
||||||
|
{"ssd_streaming_cold", DsOptType::Bool, offsetof(ds4_engine_options, ssd_streaming_cold), 0},
|
||||||
|
{"ssd_streaming_preload_experts", DsOptType::Uint, offsetof(ds4_engine_options, ssd_streaming_preload_experts), 0},
|
||||||
|
{"ssd_streaming_cache_experts", DsOptType::CacheExperts, offsetof(ds4_engine_options, ssd_streaming_cache_experts),
|
||||||
|
offsetof(ds4_engine_options, ssd_streaming_cache_bytes)},
|
||||||
|
{"simulate_used_memory", DsOptType::Gib, offsetof(ds4_engine_options, simulate_used_memory_bytes), 0},
|
||||||
|
{"expert_profile_path", DsOptType::Str, offsetof(ds4_engine_options, expert_profile_path), 0, true},
|
||||||
|
{"directional_steering_file", DsOptType::Str, offsetof(ds4_engine_options, directional_steering_file), 0, true},
|
||||||
|
{"directional_steering_attn", DsOptType::Float, offsetof(ds4_engine_options, directional_steering_attn), 0},
|
||||||
|
{"directional_steering_ffn", DsOptType::Float, offsetof(ds4_engine_options, directional_steering_ffn), 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply a single key:value LoadModel option to the engine options struct.
|
||||||
|
// Unknown keys are ignored (back-compat: callers pass mixed option sets).
|
||||||
|
// String values are copied into `storage`, whose elements the engine reads by
|
||||||
|
// pointer during ds4_engine_open; `storage` MUST have reserved capacity so
|
||||||
|
// push_back never reallocates and dangles an earlier c_str(). Returns false
|
||||||
|
// with `err` set when a recognized key has an invalid value.
|
||||||
|
static bool apply_engine_option(ds4_engine_options *opt, const std::string &key,
|
||||||
|
const std::string &val, const std::string &model_dir,
|
||||||
|
std::vector<std::string> &storage, std::string &err) {
|
||||||
|
const DsOptSpec *spec = nullptr;
|
||||||
|
for (const auto &s : kEngineOptSpecs) {
|
||||||
|
if (key == s.key) { spec = &s; break; }
|
||||||
|
}
|
||||||
|
if (!spec) return true; // unknown key: ignore
|
||||||
|
|
||||||
|
char *base = reinterpret_cast<char *>(opt);
|
||||||
|
switch (spec->type) {
|
||||||
|
case DsOptType::Bool: {
|
||||||
|
bool b = false;
|
||||||
|
if (!parse_bool_option(val, &b)) { err = key + " must be true/false"; return false; }
|
||||||
|
*reinterpret_cast<bool *>(base + spec->off) = b;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case DsOptType::Int: {
|
||||||
|
char *end = nullptr;
|
||||||
|
long v = std::strtol(val.c_str(), &end, 10);
|
||||||
|
if (val.empty() || !end || *end != '\0') { err = key + " must be an integer"; return false; }
|
||||||
|
*reinterpret_cast<int *>(base + spec->off) = static_cast<int>(v);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case DsOptType::Uint: {
|
||||||
|
char *end = nullptr;
|
||||||
|
long v = std::strtol(val.c_str(), &end, 10);
|
||||||
|
if (val.empty() || !end || *end != '\0' || v < 0 || v > static_cast<long>(UINT32_MAX)) {
|
||||||
|
err = key + " must be a non-negative integer"; return false;
|
||||||
|
}
|
||||||
|
*reinterpret_cast<uint32_t *>(base + spec->off) = static_cast<uint32_t>(v);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case DsOptType::Float: {
|
||||||
|
char *end = nullptr;
|
||||||
|
float f = std::strtof(val.c_str(), &end);
|
||||||
|
if (val.empty() || !end || *end != '\0') { err = key + " must be a number"; return false; }
|
||||||
|
*reinterpret_cast<float *>(base + spec->off) = f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case DsOptType::Str: {
|
||||||
|
// Resolve a relative path option (e.g. mtp_path: a sibling GGUF the
|
||||||
|
// gallery downloaded next to the model) against the model directory, so
|
||||||
|
// YAMLs reference companion files by name. Absolute values pass through.
|
||||||
|
if (spec->is_path && !model_dir.empty() && !val.empty() && val.front() != '/') {
|
||||||
|
storage.push_back(model_dir + "/" + val);
|
||||||
|
} else {
|
||||||
|
storage.push_back(val);
|
||||||
|
}
|
||||||
|
*reinterpret_cast<const char **>(base + spec->off) = storage.back().c_str();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case DsOptType::Gib: {
|
||||||
|
uint64_t bytes = 0;
|
||||||
|
if (!ds4_parse_gib_arg(val.c_str(), &bytes)) {
|
||||||
|
err = key + " must be a GiB value, e.g. 64GB"; return false;
|
||||||
|
}
|
||||||
|
*reinterpret_cast<uint64_t *>(base + spec->off) = bytes;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case DsOptType::CacheExperts: {
|
||||||
|
uint32_t experts = 0;
|
||||||
|
uint64_t bytes = 0;
|
||||||
|
if (!ds4_parse_streaming_cache_experts_arg(val.c_str(), &experts, &bytes)) {
|
||||||
|
err = key + " must be a positive expert count or a <number>GB budget"; return false;
|
||||||
|
}
|
||||||
|
*reinterpret_cast<uint32_t *>(base + spec->off) = experts;
|
||||||
|
*reinterpret_cast<uint64_t *>(base + spec->off2) = bytes;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// When acting as a distributed coordinator, block until the worker route
|
// When acting as a distributed coordinator, block until the worker route
|
||||||
// covers all layers (ds4_session_distributed_route_ready == 1) or the timeout
|
// covers all layers (ds4_session_distributed_route_ready == 1) or the timeout
|
||||||
// elapses. Returns an empty string on success, or an error message to return
|
// elapses. Returns an empty string on success, or an error message to return
|
||||||
@@ -476,39 +602,10 @@ public:
|
|||||||
return GStatus::OK;
|
return GStatus::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string mtp_path;
|
|
||||||
int mtp_draft = 0;
|
|
||||||
float mtp_margin = 3.0f;
|
|
||||||
std::string ds4_role, ds4_layers, ds4_listen;
|
|
||||||
for (const auto &opt : request->options()) {
|
|
||||||
auto [k, v] = split_option(opt);
|
|
||||||
if (k == "mtp_path") mtp_path = v;
|
|
||||||
else if (k == "mtp_draft") mtp_draft = std::stoi(v);
|
|
||||||
else if (k == "mtp_margin") mtp_margin = std::stof(v);
|
|
||||||
else if (k == "kv_cache_dir") g_kv_cache_dir = v;
|
|
||||||
else if (k == "ds4_role") ds4_role = v;
|
|
||||||
else if (k == "ds4_layers") ds4_layers = v;
|
|
||||||
else if (k == "ds4_listen") ds4_listen = v;
|
|
||||||
else if (k == "ds4_route_timeout") {
|
|
||||||
if (!parse_positive_int(v, &g_route_timeout_sec)) {
|
|
||||||
result->set_success(false);
|
|
||||||
result->set_message("ds4: ds4_route_timeout must be a positive integer");
|
|
||||||
return GStatus::OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_kv_cache.SetDir(g_kv_cache_dir);
|
|
||||||
|
|
||||||
ds4_engine_options opt = {};
|
ds4_engine_options opt = {};
|
||||||
opt.model_path = model_path.c_str();
|
opt.model_path = model_path.c_str();
|
||||||
opt.mtp_path = mtp_path.empty() ? nullptr : mtp_path.c_str();
|
|
||||||
opt.n_threads = request->threads() > 0 ? request->threads() : 0;
|
opt.n_threads = request->threads() > 0 ? request->threads() : 0;
|
||||||
opt.mtp_draft_tokens = mtp_draft;
|
opt.mtp_margin = 3.0f; // ds4 default; overridable via the mtp_margin option
|
||||||
opt.mtp_margin = mtp_margin;
|
|
||||||
opt.directional_steering_file = nullptr;
|
|
||||||
opt.warm_weights = false;
|
|
||||||
opt.quality = false;
|
|
||||||
|
|
||||||
#if defined(DS4_NO_GPU)
|
#if defined(DS4_NO_GPU)
|
||||||
opt.backend = DS4_BACKEND_CPU;
|
opt.backend = DS4_BACKEND_CPU;
|
||||||
@@ -518,6 +615,46 @@ public:
|
|||||||
opt.backend = DS4_BACKEND_CUDA;
|
opt.backend = DS4_BACKEND_CUDA;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Stable storage for string-valued engine options. The engine reads
|
||||||
|
// these by pointer during ds4_engine_open, so the std::string backing
|
||||||
|
// store must outlive the call and not reallocate; reserve up front so
|
||||||
|
// push_back keeps every prior c_str() valid. Static + clear() reuses
|
||||||
|
// the buffer across LoadModel calls (the old engine is closed above).
|
||||||
|
static std::vector<std::string> s_opt_strings;
|
||||||
|
s_opt_strings.clear();
|
||||||
|
s_opt_strings.reserve(sizeof(kEngineOptSpecs) / sizeof(kEngineOptSpecs[0]));
|
||||||
|
|
||||||
|
// Directory of the main model, used to resolve relative path options.
|
||||||
|
std::string model_dir;
|
||||||
|
if (auto slash = model_path.find_last_of('/'); slash != std::string::npos) {
|
||||||
|
model_dir = model_path.substr(0, slash);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ds4_role, ds4_layers, ds4_listen;
|
||||||
|
for (const auto &o : request->options()) {
|
||||||
|
auto [k, v] = split_option(o);
|
||||||
|
if (k == "kv_cache_dir") { g_kv_cache_dir = v; continue; }
|
||||||
|
else if (k == "ds4_role") { ds4_role = v; continue; }
|
||||||
|
else if (k == "ds4_layers") { ds4_layers = v; continue; }
|
||||||
|
else if (k == "ds4_listen") { ds4_listen = v; continue; }
|
||||||
|
else if (k == "ds4_route_timeout") {
|
||||||
|
if (!parse_positive_int(v, &g_route_timeout_sec)) {
|
||||||
|
result->set_success(false);
|
||||||
|
result->set_message("ds4: ds4_route_timeout must be a positive integer");
|
||||||
|
return GStatus::OK;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string err;
|
||||||
|
if (!apply_engine_option(&opt, k, v, model_dir, s_opt_strings, err)) {
|
||||||
|
result->set_success(false);
|
||||||
|
result->set_message("ds4: " + err);
|
||||||
|
return GStatus::OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_kv_cache.SetDir(g_kv_cache_dir);
|
||||||
|
|
||||||
// Coordinator wiring. 'ds4_role:coordinator' enables layer-split
|
// Coordinator wiring. 'ds4_role:coordinator' enables layer-split
|
||||||
// distributed inference: this process listens on ds4_listen and owns
|
// distributed inference: this process listens on ds4_listen and owns
|
||||||
// the ds4_layers slice; workers dial in (see `local-ai worker
|
// the ds4_layers slice; workers dial in (see `local-ai worker
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
IK_LLAMA_VERSION?=e6f8112f3ba126eed3ff5b30cdd08085414a7516
|
IK_LLAMA_VERSION?=7ccf1d209588962b96eacca325b37e9b3e8faf5e
|
||||||
LLAMA_REPO?=https://github.com/ikawrakow/ik_llama.cpp
|
LLAMA_REPO?=https://github.com/ikawrakow/ik_llama.cpp
|
||||||
|
|
||||||
CMAKE_ARGS?=
|
CMAKE_ARGS?=
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
LLAMA_VERSION?=039e20a2db9e87b2477c76cc04905f3e1acad77f
|
LLAMA_VERSION?=be4a6a63eb2b848e19c277bdcf2bd399e8af76d9
|
||||||
LLAMA_REPO?=https://github.com/ggerganov/llama.cpp
|
LLAMA_REPO?=https://github.com/ggerganov/llama.cpp
|
||||||
|
|
||||||
CMAKE_ARGS?=
|
CMAKE_ARGS?=
|
||||||
|
|||||||
@@ -18,6 +18,18 @@
|
|||||||
#if __has_include("server-chat.cpp")
|
#if __has_include("server-chat.cpp")
|
||||||
#include "server-chat.cpp"
|
#include "server-chat.cpp"
|
||||||
#endif
|
#endif
|
||||||
|
// server-schema.cpp exists only in llama.cpp after the upstream refactor that
|
||||||
|
// extracted the JSON request-schema evaluation (previously the static
|
||||||
|
// server_task::params_from_json_cmpl) into server_schema::eval_llama_cmpl_schema.
|
||||||
|
// server-context.cpp and grpc-server.cpp both call into it, so its definitions
|
||||||
|
// must be part of this translation unit or the link fails. __has_include keeps
|
||||||
|
// the source compatible with older pins/forks (e.g. llama-cpp-turboquant) that
|
||||||
|
// predate the split and still expose params_from_json_cmpl (see the guarded
|
||||||
|
// call sites below).
|
||||||
|
#if __has_include("server-schema.cpp")
|
||||||
|
#define LOCALAI_HAS_SERVER_SCHEMA 1
|
||||||
|
#include "server-schema.cpp"
|
||||||
|
#endif
|
||||||
#include "server-context.cpp"
|
#include "server-context.cpp"
|
||||||
|
|
||||||
// LocalAI
|
// LocalAI
|
||||||
@@ -1922,25 +1934,27 @@ public:
|
|||||||
body_json["min_p"] = data["min_p"];
|
body_json["min_p"] = data["min_p"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass enable_thinking via chat_template_kwargs (where oaicompat_chat_params_parse reads it)
|
// Forward the chat_template_kwargs the Go layer resolved (model config
|
||||||
|
// chat_template_kwargs + per-request metadata: enable_thinking,
|
||||||
|
// reasoning_effort, preserve_thinking, ...). One generic merge replaces
|
||||||
|
// the previous per-key handling - new template levers need no C++ change.
|
||||||
|
// oaicompat_chat_params_parse reads these from body_json.
|
||||||
const auto& metadata = request->metadata();
|
const auto& metadata = request->metadata();
|
||||||
auto et_it = metadata.find("enable_thinking");
|
auto ctk_it = metadata.find("chat_template_kwargs");
|
||||||
if (et_it != metadata.end()) {
|
if (ctk_it != metadata.end() && !ctk_it->second.empty()) {
|
||||||
if (!body_json.contains("chat_template_kwargs")) {
|
try {
|
||||||
body_json["chat_template_kwargs"] = json::object();
|
json ctk = json::parse(ctk_it->second);
|
||||||
|
if (ctk.is_object()) {
|
||||||
|
if (!body_json.contains("chat_template_kwargs")) {
|
||||||
|
body_json["chat_template_kwargs"] = json::object();
|
||||||
|
}
|
||||||
|
for (auto& el : ctk.items()) {
|
||||||
|
body_json["chat_template_kwargs"][el.key()] = el.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const std::exception & e) {
|
||||||
|
SRV_WRN("failed to parse chat_template_kwargs metadata: %s\n", e.what());
|
||||||
}
|
}
|
||||||
body_json["chat_template_kwargs"]["enable_thinking"] = (et_it->second == "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass reasoning_effort via chat_template_kwargs too: the lever
|
|
||||||
// jinja templates like gpt-oss (Harmony) / LFM2.5 read, distinct
|
|
||||||
// from enable_thinking which those templates ignore.
|
|
||||||
auto re_it = metadata.find("reasoning_effort");
|
|
||||||
if (re_it != metadata.end() && !re_it->second.empty()) {
|
|
||||||
if (!body_json.contains("chat_template_kwargs")) {
|
|
||||||
body_json["chat_template_kwargs"] = json::object();
|
|
||||||
}
|
|
||||||
body_json["chat_template_kwargs"]["reasoning_effort"] = re_it->second;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: Print full body_json before template processing (includes messages, tools, tool_choice, etc.)
|
// Debug: Print full body_json before template processing (includes messages, tools, tool_choice, etc.)
|
||||||
@@ -2100,7 +2114,11 @@ public:
|
|||||||
task.index = i;
|
task.index = i;
|
||||||
|
|
||||||
task.tokens = std::move(inputs[i]);
|
task.tokens = std::move(inputs[i]);
|
||||||
|
#ifdef LOCALAI_HAS_SERVER_SCHEMA
|
||||||
|
task.params = server_schema::eval_llama_cmpl_schema(
|
||||||
|
#else
|
||||||
task.params = server_task::params_from_json_cmpl(
|
task.params = server_task::params_from_json_cmpl(
|
||||||
|
#endif
|
||||||
ctx_server.impl->vocab,
|
ctx_server.impl->vocab,
|
||||||
params_base,
|
params_base,
|
||||||
ctx_server.get_meta().slot_n_ctx,
|
ctx_server.get_meta().slot_n_ctx,
|
||||||
@@ -2114,7 +2132,7 @@ public:
|
|||||||
// cannot detect tool calls or separate reasoning from content.
|
// cannot detect tool calls or separate reasoning from content.
|
||||||
task.params.res_type = TASK_RESPONSE_TYPE_OAI_CHAT;
|
task.params.res_type = TASK_RESPONSE_TYPE_OAI_CHAT;
|
||||||
task.params.oaicompat_cmpl_id = completion_id;
|
task.params.oaicompat_cmpl_id = completion_id;
|
||||||
// oaicompat_model is already populated by params_from_json_cmpl
|
// oaicompat_model is already populated by eval_llama_cmpl_schema
|
||||||
|
|
||||||
tasks.push_back(std::move(task));
|
tasks.push_back(std::move(task));
|
||||||
}
|
}
|
||||||
@@ -2756,25 +2774,26 @@ public:
|
|||||||
body_json["min_p"] = data["min_p"];
|
body_json["min_p"] = data["min_p"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass enable_thinking via chat_template_kwargs (where oaicompat_chat_params_parse reads it)
|
// Forward the chat_template_kwargs the Go layer resolved (model config
|
||||||
|
// chat_template_kwargs + per-request metadata: enable_thinking,
|
||||||
|
// reasoning_effort, preserve_thinking, ...). One generic merge replaces
|
||||||
|
// the previous per-key handling - new template levers need no C++ change.
|
||||||
const auto& predict_metadata = request->metadata();
|
const auto& predict_metadata = request->metadata();
|
||||||
auto predict_et_it = predict_metadata.find("enable_thinking");
|
auto predict_ctk_it = predict_metadata.find("chat_template_kwargs");
|
||||||
if (predict_et_it != predict_metadata.end()) {
|
if (predict_ctk_it != predict_metadata.end() && !predict_ctk_it->second.empty()) {
|
||||||
if (!body_json.contains("chat_template_kwargs")) {
|
try {
|
||||||
body_json["chat_template_kwargs"] = json::object();
|
json ctk = json::parse(predict_ctk_it->second);
|
||||||
|
if (ctk.is_object()) {
|
||||||
|
if (!body_json.contains("chat_template_kwargs")) {
|
||||||
|
body_json["chat_template_kwargs"] = json::object();
|
||||||
|
}
|
||||||
|
for (auto& el : ctk.items()) {
|
||||||
|
body_json["chat_template_kwargs"][el.key()] = el.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const std::exception & e) {
|
||||||
|
SRV_WRN("failed to parse chat_template_kwargs metadata: %s\n", e.what());
|
||||||
}
|
}
|
||||||
body_json["chat_template_kwargs"]["enable_thinking"] = (predict_et_it->second == "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass reasoning_effort via chat_template_kwargs too: the lever
|
|
||||||
// jinja templates like gpt-oss (Harmony) / LFM2.5 read, distinct
|
|
||||||
// from enable_thinking which those templates ignore.
|
|
||||||
auto predict_re_it = predict_metadata.find("reasoning_effort");
|
|
||||||
if (predict_re_it != predict_metadata.end() && !predict_re_it->second.empty()) {
|
|
||||||
if (!body_json.contains("chat_template_kwargs")) {
|
|
||||||
body_json["chat_template_kwargs"] = json::object();
|
|
||||||
}
|
|
||||||
body_json["chat_template_kwargs"]["reasoning_effort"] = predict_re_it->second;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: Print full body_json before template processing (includes messages, tools, tool_choice, etc.)
|
// Debug: Print full body_json before template processing (includes messages, tools, tool_choice, etc.)
|
||||||
@@ -2937,7 +2956,11 @@ public:
|
|||||||
task.index = i;
|
task.index = i;
|
||||||
|
|
||||||
task.tokens = std::move(inputs[i]);
|
task.tokens = std::move(inputs[i]);
|
||||||
|
#ifdef LOCALAI_HAS_SERVER_SCHEMA
|
||||||
|
task.params = server_schema::eval_llama_cmpl_schema(
|
||||||
|
#else
|
||||||
task.params = server_task::params_from_json_cmpl(
|
task.params = server_task::params_from_json_cmpl(
|
||||||
|
#endif
|
||||||
ctx_server.impl->vocab,
|
ctx_server.impl->vocab,
|
||||||
params_base,
|
params_base,
|
||||||
ctx_server.get_meta().slot_n_ctx,
|
ctx_server.get_meta().slot_n_ctx,
|
||||||
@@ -2949,7 +2972,7 @@ public:
|
|||||||
// reasoning, tool calls, and content are classified into ChatDeltas.
|
// reasoning, tool calls, and content are classified into ChatDeltas.
|
||||||
task.params.res_type = TASK_RESPONSE_TYPE_OAI_CHAT;
|
task.params.res_type = TASK_RESPONSE_TYPE_OAI_CHAT;
|
||||||
task.params.oaicompat_cmpl_id = completion_id;
|
task.params.oaicompat_cmpl_id = completion_id;
|
||||||
// oaicompat_model is already populated by params_from_json_cmpl
|
// oaicompat_model is already populated by eval_llama_cmpl_schema
|
||||||
|
|
||||||
tasks.push_back(std::move(task));
|
tasks.push_back(std::move(task));
|
||||||
}
|
}
|
||||||
@@ -3486,7 +3509,7 @@ public:
|
|||||||
if (body.count("prompt") != 0) {
|
if (body.count("prompt") != 0) {
|
||||||
const bool add_special = json_value(body, "add_special", false);
|
const bool add_special = json_value(body, "add_special", false);
|
||||||
|
|
||||||
llama_tokens tokens = tokenize_mixed(ctx_server.impl->vocab, body.at("content"), add_special, true);
|
llama_tokens tokens = tokenize_mixed(ctx_server.impl->vocab, body.at("prompt"), add_special, true);
|
||||||
|
|
||||||
|
|
||||||
for (const auto& token : tokens) {
|
for (const auto& token : tokens) {
|
||||||
|
|||||||
9
backend/cpp/privacy-filter/.gitignore
vendored
Normal file
9
backend/cpp/privacy-filter/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/privacy-filter.cpp
|
||||||
|
build/
|
||||||
|
package/
|
||||||
|
grpc-server
|
||||||
|
*.o
|
||||||
|
backend.pb.cc
|
||||||
|
backend.pb.h
|
||||||
|
backend.grpc.pb.cc
|
||||||
|
backend.grpc.pb.h
|
||||||
69
backend/cpp/privacy-filter/CMakeLists.txt
Normal file
69
backend/cpp/privacy-filter/CMakeLists.txt
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.21)
|
||||||
|
project(privacy-filter-grpc-server LANGUAGES CXX C)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(TARGET grpc-server)
|
||||||
|
|
||||||
|
# Path to the privacy-filter.cpp engine sources. The Makefile arranges for this
|
||||||
|
# to exist (clone of a pinned commit, or a symlink to PRIVACY_FILTER_SRC).
|
||||||
|
set(PRIVACY_FILTER_DIR "${CMAKE_CURRENT_SOURCE_DIR}/privacy-filter.cpp"
|
||||||
|
CACHE PATH "Path to the privacy-filter.cpp engine source tree")
|
||||||
|
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
find_package(Protobuf CONFIG QUIET)
|
||||||
|
if(NOT Protobuf_FOUND)
|
||||||
|
find_package(Protobuf REQUIRED)
|
||||||
|
endif()
|
||||||
|
find_package(gRPC CONFIG QUIET)
|
||||||
|
if(NOT gRPC_FOUND)
|
||||||
|
# Ubuntu's apt-installed grpc++ does not ship a CMake config - fall back.
|
||||||
|
find_library(GRPCPP_LIB grpc++ REQUIRED)
|
||||||
|
find_library(GRPCPP_REFLECTION_LIB grpc++_reflection REQUIRED)
|
||||||
|
add_library(gRPC::grpc++ INTERFACE IMPORTED)
|
||||||
|
set_target_properties(gRPC::grpc++ PROPERTIES INTERFACE_LINK_LIBRARIES "${GRPCPP_LIB}")
|
||||||
|
add_library(gRPC::grpc++_reflection INTERFACE IMPORTED)
|
||||||
|
set_target_properties(gRPC::grpc++_reflection PROPERTIES INTERFACE_LINK_LIBRARIES "${GRPCPP_REFLECTION_LIB}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_program(_PROTOC NAMES protoc REQUIRED)
|
||||||
|
find_program(_GRPC_CPP_PLUGIN NAMES grpc_cpp_plugin REQUIRED)
|
||||||
|
|
||||||
|
get_filename_component(HW_PROTO "${CMAKE_CURRENT_SOURCE_DIR}/../../backend.proto" ABSOLUTE)
|
||||||
|
get_filename_component(HW_PROTO_PATH "${HW_PROTO}" PATH)
|
||||||
|
|
||||||
|
set(HW_PROTO_SRCS "${CMAKE_CURRENT_BINARY_DIR}/backend.pb.cc")
|
||||||
|
set(HW_PROTO_HDRS "${CMAKE_CURRENT_BINARY_DIR}/backend.pb.h")
|
||||||
|
set(HW_GRPC_SRCS "${CMAKE_CURRENT_BINARY_DIR}/backend.grpc.pb.cc")
|
||||||
|
set(HW_GRPC_HDRS "${CMAKE_CURRENT_BINARY_DIR}/backend.grpc.pb.h")
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${HW_PROTO_SRCS}" "${HW_PROTO_HDRS}" "${HW_GRPC_SRCS}" "${HW_GRPC_HDRS}"
|
||||||
|
COMMAND ${_PROTOC}
|
||||||
|
ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
|
||||||
|
--cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
|
||||||
|
-I "${HW_PROTO_PATH}"
|
||||||
|
--plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN}"
|
||||||
|
"${HW_PROTO}"
|
||||||
|
DEPENDS "${HW_PROTO}")
|
||||||
|
|
||||||
|
add_library(hw_grpc_proto STATIC
|
||||||
|
${HW_GRPC_SRCS} ${HW_GRPC_HDRS}
|
||||||
|
${HW_PROTO_SRCS} ${HW_PROTO_HDRS})
|
||||||
|
target_include_directories(hw_grpc_proto PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
|
# Build only the pf static lib (+ ggml) from the engine tree — no CLI/bench/tests.
|
||||||
|
# PF_VULKAN is honored when passed on the cmake command line (it lands in the
|
||||||
|
# shared cache the engine reads).
|
||||||
|
set(PF_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(PF_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
add_subdirectory(${PRIVACY_FILTER_DIR} ${CMAKE_CURRENT_BINARY_DIR}/privacy-filter.cpp)
|
||||||
|
|
||||||
|
add_executable(${TARGET} grpc-server.cpp)
|
||||||
|
target_link_libraries(${TARGET} PRIVATE
|
||||||
|
pf
|
||||||
|
hw_grpc_proto
|
||||||
|
gRPC::grpc++
|
||||||
|
gRPC::grpc++_reflection
|
||||||
|
protobuf::libprotobuf
|
||||||
|
Threads::Threads)
|
||||||
77
backend/cpp/privacy-filter/Makefile
Normal file
77
backend/cpp/privacy-filter/Makefile
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# privacy-filter backend Makefile.
|
||||||
|
#
|
||||||
|
# Wraps the standalone privacy-filter.cpp GGML engine (the openai-privacy-filter
|
||||||
|
# PII/NER token classifier) as a LocalAI gRPC backend. The engine source is
|
||||||
|
# fetched at the pin below — .github/workflows/bump_deps.yaml finds and updates
|
||||||
|
# PRIVACY_FILTER_VERSION, matching the llama-cpp / ds4 convention.
|
||||||
|
#
|
||||||
|
# Local development: point at a working checkout instead of cloning, e.g.
|
||||||
|
# make PRIVACY_FILTER_SRC=$HOME/c/privacy-filter.cpp grpc-server
|
||||||
|
|
||||||
|
PRIVACY_FILTER_VERSION?=98f52c5ef2250f207cc6b9a6aef05393a120cb7c
|
||||||
|
PRIVACY_FILTER_REPO?=https://github.com/localai-org/privacy-filter.cpp
|
||||||
|
PRIVACY_FILTER_SRC?=
|
||||||
|
|
||||||
|
CURRENT_MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||||
|
BUILD_DIR := build
|
||||||
|
|
||||||
|
BUILD_TYPE ?=
|
||||||
|
NATIVE ?= false
|
||||||
|
JOBS ?= $(shell nproc 2>/dev/null || echo 4)
|
||||||
|
|
||||||
|
CMAKE_ARGS ?= -DCMAKE_BUILD_TYPE=Release
|
||||||
|
|
||||||
|
# GPU backends; the default (cpu) needs no extra flags. 'cublas' is LocalAI's
|
||||||
|
# name for the CUDA build (matches llama-cpp / ds4), mapping to the engine's
|
||||||
|
# GGML_CUDA path; 'vulkan' selects the ggml Vulkan backend.
|
||||||
|
ifeq ($(BUILD_TYPE),cublas)
|
||||||
|
CMAKE_ARGS += -DPF_CUDA=ON
|
||||||
|
endif
|
||||||
|
ifeq ($(BUILD_TYPE),vulkan)
|
||||||
|
CMAKE_ARGS += -DPF_VULKAN=ON
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Portable binaries for distribution: disable -march=native unless asked.
|
||||||
|
ifneq ($(NATIVE),true)
|
||||||
|
CMAKE_ARGS += -DGGML_NATIVE=OFF
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: grpc-server package clean purge test all
|
||||||
|
all: grpc-server
|
||||||
|
|
||||||
|
# Provide the engine sources at ./privacy-filter.cpp. With PRIVACY_FILTER_SRC
|
||||||
|
# set we symlink a local checkout (instant, no network); otherwise we clone the
|
||||||
|
# pinned commit and its ggml submodule. The directory/symlink is the target, so
|
||||||
|
# make only does this once — run 'make purge && make' to refetch after a bump.
|
||||||
|
privacy-filter.cpp:
|
||||||
|
ifneq ($(PRIVACY_FILTER_SRC),)
|
||||||
|
ln -sfn $(abspath $(PRIVACY_FILTER_SRC)) privacy-filter.cpp
|
||||||
|
else
|
||||||
|
mkdir -p privacy-filter.cpp
|
||||||
|
cd privacy-filter.cpp && \
|
||||||
|
git init -q && \
|
||||||
|
git remote add origin $(PRIVACY_FILTER_REPO) && \
|
||||||
|
git fetch --depth 1 origin $(PRIVACY_FILTER_VERSION) && \
|
||||||
|
git checkout FETCH_HEAD && \
|
||||||
|
git submodule update --init --recursive --depth 1
|
||||||
|
endif
|
||||||
|
|
||||||
|
grpc-server: privacy-filter.cpp
|
||||||
|
@echo "Building privacy-filter grpc-server ($(BUILD_TYPE)) with $(CMAKE_ARGS)"
|
||||||
|
mkdir -p $(BUILD_DIR)
|
||||||
|
cd $(BUILD_DIR) && cmake $(CMAKE_ARGS) $(CURRENT_MAKEFILE_DIR) && cmake --build . --config Release -j $(JOBS)
|
||||||
|
cp $(BUILD_DIR)/grpc-server grpc-server
|
||||||
|
|
||||||
|
package: grpc-server
|
||||||
|
bash package.sh
|
||||||
|
|
||||||
|
test:
|
||||||
|
@echo "privacy-filter backend: parity/regression coverage lives in the engine repo"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR) grpc-server package
|
||||||
|
|
||||||
|
# 'privacy-filter.cpp' may be a symlink (PRIVACY_FILTER_SRC) — rm without a
|
||||||
|
# trailing slash removes the link, never the linked-to checkout.
|
||||||
|
purge: clean
|
||||||
|
rm -rf privacy-filter.cpp
|
||||||
210
backend/cpp/privacy-filter/grpc-server.cpp
Normal file
210
backend/cpp/privacy-filter/grpc-server.cpp
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
// privacy-filter LocalAI gRPC backend.
|
||||||
|
//
|
||||||
|
// Thin shim over privacy-filter.cpp's flat C API (include/pf.h): a standalone
|
||||||
|
// GGML engine for the openai-privacy-filter token-classification model family
|
||||||
|
// (PII NER). It replaces the llama.cpp-patched TokenClassify path for this one
|
||||||
|
// model family — same GGUF files, no llama.cpp carry-patches.
|
||||||
|
//
|
||||||
|
// Only the RPCs the PII tier needs are implemented: LoadModel, TokenClassify,
|
||||||
|
// plus Health / Status / Free. Everything else inherits the generated base
|
||||||
|
// class default (UNIMPLEMENTED).
|
||||||
|
|
||||||
|
#include "backend.pb.h"
|
||||||
|
#include "backend.grpc.pb.h"
|
||||||
|
|
||||||
|
#include "pf.h"
|
||||||
|
|
||||||
|
#include <grpcpp/grpcpp.h>
|
||||||
|
#include <grpcpp/server.h>
|
||||||
|
#include <grpcpp/server_builder.h>
|
||||||
|
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <csignal>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using grpc::Server;
|
||||||
|
using grpc::ServerBuilder;
|
||||||
|
using grpc::ServerContext;
|
||||||
|
// NOTE: do NOT alias grpc::Status as Status — the Status RPC method below would
|
||||||
|
// shadow the type and break the other method signatures. Use GStatus instead.
|
||||||
|
using GStatus = ::grpc::Status;
|
||||||
|
using grpc::StatusCode;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// The engine is single-model-per-process: LocalAI spawns one backend process
|
||||||
|
// per loaded model. g_mu guards (re)load against in-flight classification.
|
||||||
|
std::mutex g_mu;
|
||||||
|
pf_ctx * g_ctx = nullptr;
|
||||||
|
std::atomic<Server *> g_server{nullptr};
|
||||||
|
|
||||||
|
// Resolve the device string the engine expects ("cpu" / "gpu" / "cuda" /
|
||||||
|
// "vulkan", optionally ":N"). Priority: an explicit "device:..." in
|
||||||
|
// ModelOptions.Options, then a non-zero NGPULayers as a coarse "use the GPU"
|
||||||
|
// signal, else CPU. "gpu" lets the engine pick whichever GPU backend this
|
||||||
|
// binary was compiled with (CUDA or Vulkan), so the same config works on
|
||||||
|
// either build; pin "device:cuda"/"device:vulkan" to be explicit.
|
||||||
|
std::string resolve_device(const backend::ModelOptions * opts) {
|
||||||
|
for (const auto & o : opts->options()) {
|
||||||
|
const std::string prefix = "device:";
|
||||||
|
if (o.rfind(prefix, 0) == 0) {
|
||||||
|
return o.substr(prefix.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opts->ngpulayers() > 0) {
|
||||||
|
return "gpu";
|
||||||
|
}
|
||||||
|
return "cpu";
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrivacyFilterBackend final : public backend::Backend::Service {
|
||||||
|
public:
|
||||||
|
GStatus Health(ServerContext *, const backend::HealthMessage *,
|
||||||
|
backend::Reply * reply) override {
|
||||||
|
reply->set_message("OK");
|
||||||
|
return GStatus::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
GStatus Status(ServerContext *, const backend::HealthMessage *,
|
||||||
|
backend::StatusResponse * response) override {
|
||||||
|
std::lock_guard<std::mutex> lock(g_mu);
|
||||||
|
response->set_state(g_ctx ? backend::StatusResponse::READY
|
||||||
|
: backend::StatusResponse::UNINITIALIZED);
|
||||||
|
return GStatus::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
GStatus LoadModel(ServerContext *, const backend::ModelOptions * request,
|
||||||
|
backend::Result * result) override {
|
||||||
|
std::lock_guard<std::mutex> lock(g_mu);
|
||||||
|
|
||||||
|
// ModelFile is the absolute path LocalAI resolves; Model is the bare
|
||||||
|
// name. Prefer the former, fall back to the latter.
|
||||||
|
const std::string path =
|
||||||
|
!request->modelfile().empty() ? request->modelfile() : request->model();
|
||||||
|
if (path.empty()) {
|
||||||
|
result->set_success(false);
|
||||||
|
result->set_message("no model path supplied");
|
||||||
|
return GStatus::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string device = resolve_device(request);
|
||||||
|
|
||||||
|
if (g_ctx) { pf_free(g_ctx); g_ctx = nullptr; }
|
||||||
|
|
||||||
|
pf_ctx * ctx = pf_load(path.c_str(), device.c_str(), request->threads());
|
||||||
|
const char * err = pf_last_error(ctx);
|
||||||
|
if (err) {
|
||||||
|
result->set_success(false);
|
||||||
|
result->set_message(std::string("privacy-filter load failed: ") + err);
|
||||||
|
pf_free(ctx);
|
||||||
|
return GStatus::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextSize, when set, becomes the per-forward window. The engine
|
||||||
|
// ignores values that are too small to window (<= 2*halo) and just
|
||||||
|
// runs a single forward, so passing it through is always safe.
|
||||||
|
if (request->contextsize() > 0) {
|
||||||
|
pf_set_window(ctx, request->contextsize());
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ctx = ctx;
|
||||||
|
result->set_success(true);
|
||||||
|
result->set_message("privacy-filter loaded (" + device + ")");
|
||||||
|
return GStatus::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
GStatus TokenClassify(ServerContext *, const backend::TokenClassifyRequest * request,
|
||||||
|
backend::TokenClassifyResponse * response) override {
|
||||||
|
std::lock_guard<std::mutex> lock(g_mu);
|
||||||
|
if (!g_ctx) {
|
||||||
|
return GStatus(StatusCode::FAILED_PRECONDITION, "Model not loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string & text = request->text();
|
||||||
|
if (text.empty()) {
|
||||||
|
return GStatus::OK; // no text -> no entities
|
||||||
|
}
|
||||||
|
|
||||||
|
pf_entity * ents = nullptr;
|
||||||
|
size_t n = 0;
|
||||||
|
if (pf_classify(g_ctx, text.data(), text.size(), request->threshold(), &ents, &n) != 0) {
|
||||||
|
const char * err = pf_last_error(g_ctx);
|
||||||
|
return GStatus(StatusCode::INTERNAL,
|
||||||
|
std::string("TokenClassify failed: ") + (err ? err : "unknown"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Byte offsets are into the original UTF-8 text; the engine already
|
||||||
|
// applied the threshold and whitespace-trimmed span edges.
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
backend::TokenClassifyEntity * ent = response->add_entities();
|
||||||
|
ent->set_entity_group(ents[i].label ? ents[i].label : "");
|
||||||
|
ent->set_start(ents[i].start);
|
||||||
|
ent->set_end(ents[i].end);
|
||||||
|
ent->set_score(ents[i].score);
|
||||||
|
ent->set_text(text.substr((size_t) ents[i].start,
|
||||||
|
(size_t) (ents[i].end - ents[i].start)));
|
||||||
|
}
|
||||||
|
pf_entities_free(ents, n);
|
||||||
|
return GStatus::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
GStatus Free(ServerContext *, const backend::HealthMessage *,
|
||||||
|
backend::Result * result) override {
|
||||||
|
std::lock_guard<std::mutex> lock(g_mu);
|
||||||
|
if (g_ctx) { pf_free(g_ctx); g_ctx = nullptr; }
|
||||||
|
result->set_success(true);
|
||||||
|
return GStatus::OK;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void RunServer(const std::string & addr) {
|
||||||
|
PrivacyFilterBackend service;
|
||||||
|
grpc::EnableDefaultHealthCheckService(true);
|
||||||
|
grpc::reflection::InitProtoReflectionServerBuilderPlugin();
|
||||||
|
|
||||||
|
ServerBuilder builder;
|
||||||
|
builder.AddListeningPort(addr, grpc::InsecureServerCredentials());
|
||||||
|
builder.RegisterService(&service);
|
||||||
|
builder.SetMaxReceiveMessageSize(64 * 1024 * 1024);
|
||||||
|
builder.SetMaxSendMessageSize(64 * 1024 * 1024);
|
||||||
|
|
||||||
|
std::unique_ptr<Server> server(builder.BuildAndStart());
|
||||||
|
if (!server) {
|
||||||
|
std::cerr << "privacy-filter grpc-server: failed to bind " << addr << "\n";
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
g_server = server.get();
|
||||||
|
std::cerr << "privacy-filter grpc-server listening on " << addr << "\n";
|
||||||
|
server->Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
void signal_handler(int) {
|
||||||
|
if (auto * srv = g_server.load()) {
|
||||||
|
srv->Shutdown(std::chrono::system_clock::now() + std::chrono::seconds(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char * argv[]) {
|
||||||
|
std::string addr = "127.0.0.1:50051";
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
std::string a = argv[i];
|
||||||
|
const std::string addr_flag = "--addr=";
|
||||||
|
if (a.rfind(addr_flag, 0) == 0) addr = a.substr(addr_flag.size());
|
||||||
|
else if (a == "--addr" && i + 1 < argc) addr = argv[++i];
|
||||||
|
else if (a == "--help" || a == "-h") {
|
||||||
|
std::cout << "Usage: grpc-server --addr=HOST:PORT\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::signal(SIGINT, signal_handler);
|
||||||
|
std::signal(SIGTERM, signal_handler);
|
||||||
|
RunServer(addr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
39
backend/cpp/privacy-filter/package.sh
Executable file
39
backend/cpp/privacy-filter/package.sh
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Assemble package/ for the from-scratch backend image: the grpc-server binary,
|
||||||
|
# run.sh, the dynamic loader, and every shared library the binary needs.
|
||||||
|
set -e
|
||||||
|
CURDIR=$(dirname "$(realpath "$0")")
|
||||||
|
REPO_ROOT="${CURDIR}/../../.."
|
||||||
|
|
||||||
|
mkdir -p "$CURDIR/package/lib"
|
||||||
|
cp -avf "$CURDIR/grpc-server" "$CURDIR/package/"
|
||||||
|
cp -rfv "$CURDIR/run.sh" "$CURDIR/package/"
|
||||||
|
|
||||||
|
# The dynamic loader, renamed to lib/ld.so so run.sh can invoke it explicitly
|
||||||
|
# (makes the image independent of the host's glibc layout).
|
||||||
|
if [ -f "/lib64/ld-linux-x86-64.so.2" ]; then
|
||||||
|
cp -arfLv /lib64/ld-linux-x86-64.so.2 "$CURDIR/package/lib/ld.so"
|
||||||
|
elif [ -f "/lib/ld-linux-aarch64.so.1" ]; then
|
||||||
|
cp -arfLv /lib/ld-linux-aarch64.so.1 "$CURDIR/package/lib/ld.so"
|
||||||
|
else
|
||||||
|
echo "package.sh: unknown architecture" >&2; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Bundle the binary's transitive shared deps (libstdc++, libgomp, and the apt
|
||||||
|
# grpc++/protobuf/absl stack) by walking ldd — robust to whichever of those are
|
||||||
|
# linked shared vs static. The loader line (no "=>") is skipped; ld.so above
|
||||||
|
# already covers it.
|
||||||
|
ldd "$CURDIR/grpc-server" | awk '$2 == "=>" && $3 ~ /^\// { print $3 }' | sort -u | \
|
||||||
|
while read -r so; do
|
||||||
|
[ -f "$so" ] && cp -arfLv "$so" "$CURDIR/package/lib/"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Vulkan loader / GPU libs when building the GPU variant.
|
||||||
|
GPU_LIB_SCRIPT="${REPO_ROOT}/scripts/build/package-gpu-libs.sh"
|
||||||
|
if [ -f "$GPU_LIB_SCRIPT" ]; then
|
||||||
|
source "$GPU_LIB_SCRIPT" "$CURDIR/package/lib"
|
||||||
|
package_gpu_libs
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "privacy-filter package contents:"
|
||||||
|
ls -lah "$CURDIR/package/" "$CURDIR/package/lib/"
|
||||||
9
backend/cpp/privacy-filter/run.sh
Executable file
9
backend/cpp/privacy-filter/run.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Entry point for the privacy-filter backend image / BACKEND_BINARY mode.
|
||||||
|
set -e
|
||||||
|
CURDIR=$(dirname "$(realpath "$0")")
|
||||||
|
export LD_LIBRARY_PATH="$CURDIR/lib:$LD_LIBRARY_PATH"
|
||||||
|
if [ -f "$CURDIR/lib/ld.so" ]; then
|
||||||
|
exec "$CURDIR/lib/ld.so" "$CURDIR/grpc-server" "$@"
|
||||||
|
fi
|
||||||
|
exec "$CURDIR/grpc-server" "$@"
|
||||||
@@ -117,7 +117,8 @@ libgoacestepcpp-custom: CMakeLists.txt cpp/goacestepcpp.cpp cpp/goacestepcpp.h
|
|||||||
cmake .. $(CMAKE_ARGS) && \
|
cmake .. $(CMAKE_ARGS) && \
|
||||||
cmake --build . --config Release -j$(JOBS) --target goacestepcpp && \
|
cmake --build . --config Release -j$(JOBS) --target goacestepcpp && \
|
||||||
cd .. && \
|
cd .. && \
|
||||||
mv build-$(SO_TARGET)/libgoacestepcpp.so ./$(SO_TARGET)
|
(mv build-$(SO_TARGET)/libgoacestepcpp.so ./$(SO_TARGET) 2>/dev/null || \
|
||||||
|
mv build-$(SO_TARGET)/libgoacestepcpp.dylib ./$(SO_TARGET) 2>/dev/null)
|
||||||
|
|
||||||
test: acestep-cpp
|
test: acestep-cpp
|
||||||
@echo "Running acestep-cpp tests..."
|
@echo "Running acestep-cpp tests..."
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/ebitengine/purego"
|
"github.com/ebitengine/purego"
|
||||||
grpc "github.com/mudler/LocalAI/pkg/grpc"
|
grpc "github.com/mudler/LocalAI/pkg/grpc"
|
||||||
@@ -22,7 +23,11 @@ func main() {
|
|||||||
// Get library name from environment variable, default to fallback
|
// Get library name from environment variable, default to fallback
|
||||||
libName := os.Getenv("ACESTEP_LIBRARY")
|
libName := os.Getenv("ACESTEP_LIBRARY")
|
||||||
if libName == "" {
|
if libName == "" {
|
||||||
libName = "./libgoacestepcpp-fallback.so"
|
if runtime.GOOS == "darwin" {
|
||||||
|
libName = "./libgoacestepcpp-fallback.dylib"
|
||||||
|
} else {
|
||||||
|
libName = "./libgoacestepcpp-fallback.so"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gosd, err := purego.Dlopen(libName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
gosd, err := purego.Dlopen(libName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ mkdir -p $CURDIR/package/lib
|
|||||||
|
|
||||||
cp -avf $CURDIR/acestep-cpp $CURDIR/package/
|
cp -avf $CURDIR/acestep-cpp $CURDIR/package/
|
||||||
cp -fv $CURDIR/libgoacestepcpp-*.so $CURDIR/package/
|
cp -fv $CURDIR/libgoacestepcpp-*.so $CURDIR/package/
|
||||||
|
cp -fv $CURDIR/libgoacestepcpp-*.dylib $CURDIR/package/ 2>/dev/null || true
|
||||||
cp -fv $CURDIR/run.sh $CURDIR/package/
|
cp -fv $CURDIR/run.sh $CURDIR/package/
|
||||||
|
|
||||||
# Detect architecture and copy appropriate libraries
|
# Detect architecture and copy appropriate libraries
|
||||||
|
|||||||
@@ -12,9 +12,19 @@ if [ "$(uname)" != "Darwin" ]; then
|
|||||||
grep -e "flags" /proc/cpuinfo | head -1
|
grep -e "flags" /proc/cpuinfo | head -1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
LIBRARY="$CURDIR/libgoacestepcpp-fallback.so"
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
|
# macOS: single library variant (Metal or Accelerate). The goacestepcpp
|
||||||
|
# target is built as a CMake MODULE, which emits a .dylib for a SHARED
|
||||||
|
# build but a .so for a MODULE build on Apple, so prefer .dylib and fall
|
||||||
|
# back to .so.
|
||||||
|
LIBRARY="$CURDIR/libgoacestepcpp-fallback.dylib"
|
||||||
|
if [ ! -e "$LIBRARY" ]; then
|
||||||
|
LIBRARY="$CURDIR/libgoacestepcpp-fallback.so"
|
||||||
|
fi
|
||||||
|
export DYLD_LIBRARY_PATH=$CURDIR/lib:$DYLD_LIBRARY_PATH
|
||||||
|
else
|
||||||
|
LIBRARY="$CURDIR/libgoacestepcpp-fallback.so"
|
||||||
|
|
||||||
if [ "$(uname)" != "Darwin" ]; then
|
|
||||||
if grep -q -e "\savx\s" /proc/cpuinfo ; then
|
if grep -q -e "\savx\s" /proc/cpuinfo ; then
|
||||||
echo "CPU: AVX found OK"
|
echo "CPU: AVX found OK"
|
||||||
if [ -e $CURDIR/libgoacestepcpp-avx.so ]; then
|
if [ -e $CURDIR/libgoacestepcpp-avx.so ]; then
|
||||||
@@ -36,9 +46,10 @@ if [ "$(uname)" != "Darwin" ]; then
|
|||||||
LIBRARY="$CURDIR/libgoacestepcpp-avx512.so"
|
LIBRARY="$CURDIR/libgoacestepcpp-avx512.so"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH=$CURDIR/lib:$LD_LIBRARY_PATH
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export LD_LIBRARY_PATH=$CURDIR/lib:$LD_LIBRARY_PATH
|
|
||||||
export ACESTEP_LIBRARY=$LIBRARY
|
export ACESTEP_LIBRARY=$LIBRARY
|
||||||
|
|
||||||
# If there is a lib/ld.so, use it
|
# If there is a lib/ld.so, use it
|
||||||
|
|||||||
11
backend/go/ced/.gitignore
vendored
Normal file
11
backend/go/ced/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.cache/
|
||||||
|
sources/
|
||||||
|
build/
|
||||||
|
package/
|
||||||
|
ced-grpc
|
||||||
|
# build artifacts staged in-tree by the Makefile (cp from sources/) or
|
||||||
|
# symlinked for local dev; the real sources live in ced.cpp upstream.
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
ced_capi.h
|
||||||
|
compile_commands.json
|
||||||
78
backend/go/ced/Makefile
Normal file
78
backend/go/ced/Makefile
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# ced sound-classification backend Makefile.
|
||||||
|
#
|
||||||
|
# Upstream pin lives below as CED_VERSION?=<sha> so .github/bump_deps.sh can find
|
||||||
|
# and update it (matches the parakeet-cpp / whisper.cpp convention).
|
||||||
|
#
|
||||||
|
# Local dev shortcut: symlink an out-of-tree ced.cpp shared build + header and
|
||||||
|
# skip the clone/cmake steps entirely:
|
||||||
|
# ln -sf /path/to/ced.cpp/build-shared/libced.so .
|
||||||
|
# ln -sf /path/to/ced.cpp/include/ced_capi.h .
|
||||||
|
# go build -o ced-grpc .
|
||||||
|
|
||||||
|
CED_VERSION?=c04ac14b7992d00584d9e812c9bb6268598a6ce7
|
||||||
|
CED_REPO?=https://github.com/mudler/ced.cpp
|
||||||
|
|
||||||
|
GOCMD?=go
|
||||||
|
GO_TAGS?=
|
||||||
|
JOBS?=$(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
|
||||||
|
|
||||||
|
BUILD_TYPE?=
|
||||||
|
NATIVE?=false
|
||||||
|
|
||||||
|
# Static-link ggml into libced.so (PIC) so the shared lib is self-contained:
|
||||||
|
# dlopen needs no libggml*.so alongside it, only system libs the runtime image
|
||||||
|
# already provides.
|
||||||
|
CMAKE_ARGS?=-DCMAKE_BUILD_TYPE=Release -DCED_SHARED=ON -DCED_BUILD_CLI=OFF -DCED_BUILD_TESTS=OFF -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||||
|
|
||||||
|
ifeq ($(NATIVE),false)
|
||||||
|
CMAKE_ARGS+=-DGGML_NATIVE=OFF
|
||||||
|
endif
|
||||||
|
|
||||||
|
# ced.cpp gates its ggml backends behind CED_GGML_* options (set(... CACHE BOOL
|
||||||
|
# "" FORCE)), so forward those instead of a bare -DGGML_CUDA=ON.
|
||||||
|
ifeq ($(BUILD_TYPE),cublas)
|
||||||
|
CMAKE_ARGS+=-DCED_GGML_CUDA=ON -DGGML_CUDA_GRAPHS=ON
|
||||||
|
else ifeq ($(BUILD_TYPE),openblas)
|
||||||
|
CMAKE_ARGS+=-DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS
|
||||||
|
else ifeq ($(BUILD_TYPE),hipblas)
|
||||||
|
CMAKE_ARGS+=-DCED_GGML_HIP=ON
|
||||||
|
else ifeq ($(BUILD_TYPE),vulkan)
|
||||||
|
CMAKE_ARGS+=-DCED_GGML_VULKAN=ON
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: ced-grpc package build clean purge test all
|
||||||
|
|
||||||
|
all: ced-grpc
|
||||||
|
|
||||||
|
sources/ced.cpp:
|
||||||
|
mkdir -p sources/ced.cpp
|
||||||
|
cd sources/ced.cpp && \
|
||||||
|
git init -q && \
|
||||||
|
git remote add origin $(CED_REPO) && \
|
||||||
|
git fetch --depth 1 origin $(CED_VERSION) && \
|
||||||
|
git checkout FETCH_HEAD && \
|
||||||
|
git submodule update --init --recursive --depth 1 --single-branch
|
||||||
|
|
||||||
|
libced.so: sources/ced.cpp
|
||||||
|
cmake -B sources/ced.cpp/build-shared -S sources/ced.cpp $(CMAKE_ARGS)
|
||||||
|
cmake --build sources/ced.cpp/build-shared --config Release -j$(JOBS)
|
||||||
|
cp -fv sources/ced.cpp/build-shared/libced.so* ./ 2>/dev/null || true
|
||||||
|
cp -fv sources/ced.cpp/build-shared/libced.dylib ./ 2>/dev/null || true
|
||||||
|
cp -fv sources/ced.cpp/include/ced_capi.h ./
|
||||||
|
|
||||||
|
ced-grpc: libced.so main.go goced.go
|
||||||
|
CGO_ENABLED=0 $(GOCMD) build -tags "$(GO_TAGS)" -o ced-grpc .
|
||||||
|
|
||||||
|
package: ced-grpc
|
||||||
|
bash package.sh
|
||||||
|
|
||||||
|
build: package
|
||||||
|
|
||||||
|
test:
|
||||||
|
LD_LIBRARY_PATH=$(CURDIR):$$LD_LIBRARY_PATH $(GOCMD) test ./... -count=1
|
||||||
|
|
||||||
|
clean: purge
|
||||||
|
rm -rf libced.so* ced_capi.h package ced-grpc
|
||||||
|
|
||||||
|
purge:
|
||||||
|
rm -rf sources/ced.cpp
|
||||||
130
backend/go/ced/goced.go
Normal file
130
backend/go/ced/goced.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// Go side of the ced backend: purego bindings over ced_capi.h plus the gRPC
|
||||||
|
// SoundDetection implementation.
|
||||||
|
//
|
||||||
|
// SKETCH: the pb.SoundDetection* types come from backend.proto (regenerate with
|
||||||
|
// `make protogen-go`). The C side is single-threaded per ctx, so we guard the
|
||||||
|
// engine with engineMu; LocalAI also serializes via base.SingleThread.
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/mudler/LocalAI/pkg/grpc/base"
|
||||||
|
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// purego-bound entry points from libced.so. Names match ced_capi.h exactly.
|
||||||
|
var (
|
||||||
|
CppAbiVersion func() int32
|
||||||
|
CppLoad func(ggufPath string) uintptr
|
||||||
|
CppFree func(ctx uintptr)
|
||||||
|
CppLastError func(ctx uintptr) string
|
||||||
|
CppNumClasses func(ctx uintptr) int32
|
||||||
|
CppSampleRate func(ctx uintptr) int32
|
||||||
|
CppClassifyPathJSON func(ctx uintptr, wavPath string, topK int32) uintptr
|
||||||
|
CppClassifyPcmJSON func(ctx uintptr, pcm []float32, nSamples int32, sampleRate int32, topK int32) uintptr
|
||||||
|
CppFreeString func(s uintptr)
|
||||||
|
)
|
||||||
|
|
||||||
|
// cstr copies a malloc'd C string (returned as uintptr) into a Go string and
|
||||||
|
// frees the original via ced_capi_free_string. Empty/0 -> "".
|
||||||
|
func cstr(p uintptr) string {
|
||||||
|
if p == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer CppFreeString(p)
|
||||||
|
var b []byte
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
ch := *(*byte)(unsafe.Pointer(p + uintptr(i))) //nolint:govet // #nosec G103 -- C-owned NUL-terminated string from libced (not Go-GC memory)
|
||||||
|
if ch == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b = append(b, ch)
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ced is the gRPC backend. One loaded CED model per instance.
|
||||||
|
type Ced struct {
|
||||||
|
base.Base
|
||||||
|
ctxPtr uintptr
|
||||||
|
engineMu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load resolves the GGUF and opens the C-API context.
|
||||||
|
func (c *Ced) Load(opts *pb.ModelOptions) error {
|
||||||
|
if opts.ModelFile == "" {
|
||||||
|
return errors.New("ced: ModelFile is required")
|
||||||
|
}
|
||||||
|
ctx := CppLoad(opts.ModelFile)
|
||||||
|
if ctx == 0 {
|
||||||
|
return fmt.Errorf("ced: ced_capi_load failed for %q: %s", opts.ModelFile, CppLastError(0))
|
||||||
|
}
|
||||||
|
c.ctxPtr = ctx
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsonTag mirrors the ced_capi JSON tag objects.
|
||||||
|
type jsonTag struct {
|
||||||
|
Index int `json:"index"`
|
||||||
|
Score float32 `json:"score"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SoundDetection classifies the clip at req.Src and returns scored AudioSet tags.
|
||||||
|
func (c *Ced) SoundDetection(ctx context.Context, req *pb.SoundDetectionRequest) (*pb.SoundDetectionResponse, error) {
|
||||||
|
if c.ctxPtr == 0 {
|
||||||
|
return nil, errors.New("ced: model not loaded")
|
||||||
|
}
|
||||||
|
if req.GetSrc() == "" {
|
||||||
|
return nil, errors.New("ced: SoundDetectionRequest.src (audio path) is required")
|
||||||
|
}
|
||||||
|
topK := req.GetTopK()
|
||||||
|
if topK <= 0 {
|
||||||
|
topK = 10 // sensible default for a tagging response
|
||||||
|
}
|
||||||
|
|
||||||
|
c.engineMu.Lock()
|
||||||
|
out := cstr(CppClassifyPathJSON(c.ctxPtr, req.GetSrc(), topK))
|
||||||
|
lastErr := CppLastError(c.ctxPtr)
|
||||||
|
c.engineMu.Unlock()
|
||||||
|
|
||||||
|
if out == "" {
|
||||||
|
return nil, fmt.Errorf("ced: classification failed: %s", lastErr)
|
||||||
|
}
|
||||||
|
var tags []jsonTag
|
||||||
|
if err := json.Unmarshal([]byte(out), &tags); err != nil {
|
||||||
|
return nil, fmt.Errorf("ced: bad classifier JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
thr := req.GetThreshold()
|
||||||
|
resp := &pb.SoundDetectionResponse{}
|
||||||
|
for _, t := range tags {
|
||||||
|
if t.Score < thr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resp.Detections = append(resp.Detections, &pb.SoundClass{
|
||||||
|
Label: t.Label, Score: t.Score, Index: int32(t.Index),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Slice(resp.Detections, func(i, j int) bool {
|
||||||
|
return resp.Detections[i].Score > resp.Detections[j].Score
|
||||||
|
})
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ced) Free() error {
|
||||||
|
c.engineMu.Lock()
|
||||||
|
defer c.engineMu.Unlock()
|
||||||
|
if c.ctxPtr != 0 {
|
||||||
|
CppFree(c.ctxPtr)
|
||||||
|
c.ctxPtr = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
64
backend/go/ced/main.go
Normal file
64
backend/go/ced/main.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// ced sound-classification backend. Started internally by LocalAI: one gRPC
|
||||||
|
// server per loaded model. Loads libced.so via purego and registers the flat
|
||||||
|
// C-API declared in ced_capi.h. The library name can be overridden with
|
||||||
|
// CED_LIBRARY (mirrors PARAKEET_LIBRARY / WHISPER_LIBRARY); the default looks
|
||||||
|
// for the .so next to this binary.
|
||||||
|
//
|
||||||
|
// SKETCH: requires `make protogen-go` after the backend.proto SoundDetection
|
||||||
|
// addition, and a built libced.so (see Makefile). See DESIGN.md.
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/ebitengine/purego"
|
||||||
|
grpc "github.com/mudler/LocalAI/pkg/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||||
|
|
||||||
|
type libFunc struct {
|
||||||
|
ptr any
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
libName := os.Getenv("CED_LIBRARY")
|
||||||
|
if libName == "" {
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
libName = "libced.dylib"
|
||||||
|
} else {
|
||||||
|
libName = "libced.so"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lib, err := purego.Dlopen(libName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("ced: dlopen %q: %w", libName, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bound 1:1 to ced_capi.h. char*-returning functions are declared uintptr
|
||||||
|
// so we can free the same pointer with ced_capi_free_string after copying
|
||||||
|
// (purego's string return would copy and leak the original).
|
||||||
|
for _, lf := range []libFunc{
|
||||||
|
{&CppAbiVersion, "ced_capi_abi_version"},
|
||||||
|
{&CppLoad, "ced_capi_load"},
|
||||||
|
{&CppFree, "ced_capi_free"},
|
||||||
|
{&CppLastError, "ced_capi_last_error"},
|
||||||
|
{&CppNumClasses, "ced_capi_num_classes"},
|
||||||
|
{&CppSampleRate, "ced_capi_sample_rate"},
|
||||||
|
{&CppClassifyPathJSON, "ced_capi_classify_path_json"},
|
||||||
|
{&CppClassifyPcmJSON, "ced_capi_classify_pcm_json"},
|
||||||
|
{&CppFreeString, "ced_capi_free_string"},
|
||||||
|
} {
|
||||||
|
purego.RegisterLibFunc(lf.ptr, lib, lf.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[ced] ABI=%d\n", CppAbiVersion())
|
||||||
|
flag.Parse()
|
||||||
|
if err := grpc.StartServer(*addr, &Ced{}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
62
backend/go/ced/package.sh
Executable file
62
backend/go/ced/package.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Bundle the ced-grpc binary, libced.so, the core runtime libs (libc/libstdc++/
|
||||||
|
# libgomp + ld.so) and the GPU runtime for the active BUILD_TYPE so the package
|
||||||
|
# is self-contained. Mirrors backend/go/parakeet-cpp/package.sh; run.sh routes
|
||||||
|
# the (CGO_ENABLED=0) binary through lib/ld.so so the packaged libc is used.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CURDIR=$(dirname "$(realpath "$0")")
|
||||||
|
REPO_ROOT="${CURDIR}/../../.."
|
||||||
|
|
||||||
|
mkdir -p "$CURDIR/package/lib"
|
||||||
|
|
||||||
|
cp -avf "$CURDIR/ced-grpc" "$CURDIR/package/"
|
||||||
|
cp -avf "$CURDIR/run.sh" "$CURDIR/package/"
|
||||||
|
|
||||||
|
cp -avf "$CURDIR"/libced.so* "$CURDIR/package/lib/" 2>/dev/null || true
|
||||||
|
cp -avf "$CURDIR"/libced.dylib "$CURDIR/package/lib/" 2>/dev/null || true
|
||||||
|
if ! ls "$CURDIR"/package/lib/libced.* >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: libced shared library not found in $CURDIR, run 'make' first" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "/lib64/ld-linux-x86-64.so.2" ]; then
|
||||||
|
echo "Detected x86_64 architecture, copying x86_64 libraries..."
|
||||||
|
cp -arfLv /lib64/ld-linux-x86-64.so.2 "$CURDIR/package/lib/ld.so"
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libc.so.6 "$CURDIR/package/lib/libc.so.6"
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libgcc_s.so.1 "$CURDIR/package/lib/libgcc_s.so.1"
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libstdc++.so.6 "$CURDIR/package/lib/libstdc++.so.6"
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libm.so.6 "$CURDIR/package/lib/libm.so.6"
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libgomp.so.1 "$CURDIR/package/lib/libgomp.so.1"
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libdl.so.2 "$CURDIR/package/lib/libdl.so.2"
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/librt.so.1 "$CURDIR/package/lib/librt.so.1"
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libpthread.so.0 "$CURDIR/package/lib/libpthread.so.0"
|
||||||
|
elif [ -f "/lib/ld-linux-aarch64.so.1" ]; then
|
||||||
|
echo "Detected ARM64 architecture, copying ARM64 libraries..."
|
||||||
|
cp -arfLv /lib/ld-linux-aarch64.so.1 "$CURDIR/package/lib/ld.so"
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libc.so.6 "$CURDIR/package/lib/libc.so.6"
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libgcc_s.so.1 "$CURDIR/package/lib/libgcc_s.so.1"
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libstdc++.so.6 "$CURDIR/package/lib/libstdc++.so.6"
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libm.so.6 "$CURDIR/package/lib/libm.so.6"
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libgomp.so.1 "$CURDIR/package/lib/libgomp.so.1"
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libdl.so.2 "$CURDIR/package/lib/libdl.so.2"
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/librt.so.1 "$CURDIR/package/lib/librt.so.1"
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libpthread.so.0 "$CURDIR/package/lib/libpthread.so.0"
|
||||||
|
elif [ "$(uname -s)" = "Darwin" ]; then
|
||||||
|
echo "Detected Darwin"
|
||||||
|
else
|
||||||
|
echo "Error: Could not detect architecture"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
GPU_LIB_SCRIPT="${REPO_ROOT}/scripts/build/package-gpu-libs.sh"
|
||||||
|
if [ -f "$GPU_LIB_SCRIPT" ]; then
|
||||||
|
echo "Packaging GPU libraries for BUILD_TYPE=${BUILD_TYPE:-cpu}..."
|
||||||
|
source "$GPU_LIB_SCRIPT" "$CURDIR/package/lib"
|
||||||
|
package_gpu_libs
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Packaging completed successfully"
|
||||||
|
ls -liah "$CURDIR/package/" "$CURDIR/package/lib/"
|
||||||
20
backend/go/ced/run.sh
Executable file
20
backend/go/ced/run.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CURDIR=$(dirname "$(realpath "$0")")
|
||||||
|
|
||||||
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
|
export DYLD_LIBRARY_PATH="$CURDIR/lib:$CURDIR:${DYLD_LIBRARY_PATH:-}"
|
||||||
|
export CED_LIBRARY="$CURDIR/lib/libced.dylib"
|
||||||
|
else
|
||||||
|
export LD_LIBRARY_PATH="$CURDIR/lib:$CURDIR:${LD_LIBRARY_PATH:-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If a self-contained ld.so was packaged, route through it so the packaged
|
||||||
|
# libc / libstdc++ are used instead of the host's (matches the sibling backends).
|
||||||
|
if [ -f "$CURDIR/lib/ld.so" ]; then
|
||||||
|
echo "Using lib/ld.so"
|
||||||
|
exec "$CURDIR/lib/ld.so" "$CURDIR/ced-grpc" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$CURDIR/ced-grpc" "$@"
|
||||||
@@ -8,7 +8,7 @@ JOBS?=$(shell nproc --ignore=1)
|
|||||||
|
|
||||||
# CrispASR version (release tag)
|
# CrispASR version (release tag)
|
||||||
CRISPASR_REPO?=https://github.com/CrispStrobe/CrispASR
|
CRISPASR_REPO?=https://github.com/CrispStrobe/CrispASR
|
||||||
CRISPASR_VERSION?=c29f6653a516a3001d923944dad8892072cc7334
|
CRISPASR_VERSION?=96b2a6ee31d30389fed8a7ef1a54239b75231ddc
|
||||||
SO_TARGET?=libgocrispasr.so
|
SO_TARGET?=libgocrispasr.so
|
||||||
|
|
||||||
CMAKE_ARGS+=-DBUILD_SHARED_LIBS=OFF
|
CMAKE_ARGS+=-DBUILD_SHARED_LIBS=OFF
|
||||||
@@ -67,7 +67,7 @@ sources/CrispASR:
|
|||||||
# it, so ${CMAKE_SOURCE_DIR} is THIS backend dir and the talk-llama sources
|
# it, so ${CMAKE_SOURCE_DIR} is THIS backend dir and the talk-llama sources
|
||||||
# aren't found. Rewrite to ${PROJECT_SOURCE_DIR} (the crispasr project root),
|
# aren't found. Rewrite to ${PROJECT_SOURCE_DIR} (the crispasr project root),
|
||||||
# which is correct both standalone and as a subproject. Idempotent.
|
# which is correct both standalone and as a subproject. Idempotent.
|
||||||
sed -i 's#\$${CMAKE_SOURCE_DIR}/examples/talk-llama#\$${PROJECT_SOURCE_DIR}/examples/talk-llama#' sources/CrispASR/src/CMakeLists.txt
|
sed -i.bak 's#\$${CMAKE_SOURCE_DIR}/examples/talk-llama#\$${PROJECT_SOURCE_DIR}/examples/talk-llama#' sources/CrispASR/src/CMakeLists.txt && rm -f sources/CrispASR/src/CMakeLists.txt.bak
|
||||||
|
|
||||||
# Detect OS
|
# Detect OS
|
||||||
UNAME_S := $(shell uname -s)
|
UNAME_S := $(shell uname -s)
|
||||||
@@ -75,7 +75,8 @@ UNAME_S := $(shell uname -s)
|
|||||||
ifeq ($(UNAME_S),Linux)
|
ifeq ($(UNAME_S),Linux)
|
||||||
VARIANT_TARGETS = libgocrispasr-avx.so libgocrispasr-avx2.so libgocrispasr-avx512.so libgocrispasr-fallback.so
|
VARIANT_TARGETS = libgocrispasr-avx.so libgocrispasr-avx2.so libgocrispasr-avx512.so libgocrispasr-fallback.so
|
||||||
else
|
else
|
||||||
VARIANT_TARGETS = libgocrispasr-fallback.so
|
# On non-Linux (e.g., Darwin), build only fallback variant (as a dylib)
|
||||||
|
VARIANT_TARGETS = libgocrispasr-fallback.dylib
|
||||||
endif
|
endif
|
||||||
|
|
||||||
crispasr: main.go gocrispasr.go $(VARIANT_TARGETS)
|
crispasr: main.go gocrispasr.go $(VARIANT_TARGETS)
|
||||||
@@ -87,7 +88,7 @@ package: crispasr
|
|||||||
build: package
|
build: package
|
||||||
|
|
||||||
clean: purge
|
clean: purge
|
||||||
rm -rf libgocrispasr*.so package sources/CrispASR crispasr
|
rm -rf libgocrispasr*.so libgocrispasr*.dylib package sources/CrispASR crispasr
|
||||||
|
|
||||||
purge:
|
purge:
|
||||||
rm -rf build*
|
rm -rf build*
|
||||||
@@ -118,13 +119,21 @@ libgocrispasr-fallback.so: sources/CrispASR
|
|||||||
SO_TARGET=libgocrispasr-fallback.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libgocrispasr-custom
|
SO_TARGET=libgocrispasr-fallback.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libgocrispasr-custom
|
||||||
rm -rfv build*
|
rm -rfv build*
|
||||||
|
|
||||||
|
# Build fallback variant as a dylib (Darwin)
|
||||||
|
libgocrispasr-fallback.dylib: sources/CrispASR
|
||||||
|
$(MAKE) purge
|
||||||
|
$(info ${GREEN}I crispasr build info:fallback (dylib)${RESET})
|
||||||
|
SO_TARGET=libgocrispasr-fallback.dylib CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libgocrispasr-custom
|
||||||
|
rm -rfv build*
|
||||||
|
|
||||||
libgocrispasr-custom: CMakeLists.txt cpp/crispasr_shim.cpp cpp/crispasr_shim.h
|
libgocrispasr-custom: CMakeLists.txt cpp/crispasr_shim.cpp cpp/crispasr_shim.h
|
||||||
mkdir -p build-$(SO_TARGET) && \
|
mkdir -p build-$(SO_TARGET) && \
|
||||||
cd build-$(SO_TARGET) && \
|
cd build-$(SO_TARGET) && \
|
||||||
cmake .. $(CMAKE_ARGS) && \
|
cmake .. $(CMAKE_ARGS) && \
|
||||||
cmake --build . --config Release -j$(JOBS) && \
|
cmake --build . --config Release -j$(JOBS) && \
|
||||||
cd .. && \
|
cd .. && \
|
||||||
mv build-$(SO_TARGET)/libgocrispasr.so ./$(SO_TARGET)
|
(mv build-$(SO_TARGET)/libgocrispasr.so ./$(SO_TARGET) 2>/dev/null || \
|
||||||
|
mv build-$(SO_TARGET)/libgocrispasr.dylib ./$(SO_TARGET) 2>/dev/null)
|
||||||
|
|
||||||
test: crispasr
|
test: crispasr
|
||||||
CGO_ENABLED=0 $(GOCMD) test -v ./...
|
CGO_ENABLED=0 $(GOCMD) test -v ./...
|
||||||
|
|||||||
@@ -47,6 +47,74 @@ extern "C" void set_abort(int v) {
|
|||||||
g_abort.store(v, std::memory_order_relaxed);
|
g_abort.store(v, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- word-level timestamp accessors ---
|
||||||
|
extern "C" {
|
||||||
|
int crispasr_session_result_n_words(crispasr_session_result *r, int seg_i);
|
||||||
|
const char *crispasr_session_result_word_text(crispasr_session_result *r,
|
||||||
|
int seg_i, int word_i);
|
||||||
|
int64_t crispasr_session_result_word_t0(crispasr_session_result *r, int seg_i,
|
||||||
|
int word_i);
|
||||||
|
int64_t crispasr_session_result_word_t1(crispasr_session_result *r, int seg_i,
|
||||||
|
int word_i);
|
||||||
|
|
||||||
|
// Parakeet-specific word accessors
|
||||||
|
int crispasr_parakeet_result_n_words(void *r);
|
||||||
|
const char *crispasr_parakeet_result_word_text(void *r, int word_i);
|
||||||
|
int64_t crispasr_parakeet_result_word_t0(void *r, int word_i);
|
||||||
|
int64_t crispasr_parakeet_result_word_t1(void *r, int word_i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *get_result(void) { return g_result; }
|
||||||
|
|
||||||
|
int get_word_count(int seg_i) {
|
||||||
|
if (!g_result)
|
||||||
|
return 0;
|
||||||
|
return crispasr_session_result_n_words(g_result, seg_i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *get_word_text(int seg_i, int word_i) {
|
||||||
|
if (!g_result)
|
||||||
|
return "";
|
||||||
|
return crispasr_session_result_word_text(g_result, seg_i, word_i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t get_word_t0(int seg_i, int word_i) {
|
||||||
|
if (!g_result)
|
||||||
|
return 0;
|
||||||
|
return crispasr_session_result_word_t0(g_result, seg_i, word_i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t get_word_t1(int seg_i, int word_i) {
|
||||||
|
if (!g_result)
|
||||||
|
return 0;
|
||||||
|
return crispasr_session_result_word_t1(g_result, seg_i, word_i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parakeet-specific word accessors
|
||||||
|
int get_parakeet_word_count(void) {
|
||||||
|
if (!g_result)
|
||||||
|
return 0;
|
||||||
|
return crispasr_parakeet_result_n_words(g_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *get_parakeet_word_text(int word_i) {
|
||||||
|
if (!g_result)
|
||||||
|
return "";
|
||||||
|
return crispasr_parakeet_result_word_text(g_result, word_i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t get_parakeet_word_t0(int word_i) {
|
||||||
|
if (!g_result)
|
||||||
|
return 0;
|
||||||
|
return crispasr_parakeet_result_word_t0(g_result, word_i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t get_parakeet_word_t1(int word_i) {
|
||||||
|
if (!g_result)
|
||||||
|
return 0;
|
||||||
|
return crispasr_parakeet_result_word_t1(g_result, word_i);
|
||||||
|
}
|
||||||
|
|
||||||
static void ggml_log_cb(enum ggml_log_level level, const char *log,
|
static void ggml_log_cb(enum ggml_log_level level, const char *log,
|
||||||
void *data) {
|
void *data) {
|
||||||
const char *level_str;
|
const char *level_str;
|
||||||
|
|||||||
@@ -20,4 +20,18 @@ float *tts_synthesize(const char *text, int *out_n_samples); // 24kHz mono float
|
|||||||
void tts_free(float *pcm);
|
void tts_free(float *pcm);
|
||||||
int tts_set_voice(const char *name); // best-effort speaker selection; 0 ok
|
int tts_set_voice(const char *name); // best-effort speaker selection; 0 ok
|
||||||
int tts_set_voice_file(const char *path, const char *ref_text); // load voice pack (.gguf) or zero-shot clone (.wav + ref_text)
|
int tts_set_voice_file(const char *path, const char *ref_text); // load voice pack (.gguf) or zero-shot clone (.wav + ref_text)
|
||||||
|
|
||||||
|
// --- word-level timestamp accessors ---
|
||||||
|
// Session-based (works for whisper-like backends)
|
||||||
|
void *get_result(void);
|
||||||
|
int get_word_count(int seg_i);
|
||||||
|
const char *get_word_text(int seg_i, int word_i);
|
||||||
|
int64_t get_word_t0(int seg_i, int word_i);
|
||||||
|
int64_t get_word_t1(int seg_i, int word_i);
|
||||||
|
|
||||||
|
// Parakeet-specific (global word list, no segment index)
|
||||||
|
int get_parakeet_word_count(void);
|
||||||
|
const char *get_parakeet_word_text(int word_i);
|
||||||
|
int64_t get_parakeet_word_t0(int word_i);
|
||||||
|
int64_t get_parakeet_word_t1(int word_i);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-audio/audio"
|
"github.com/go-audio/audio"
|
||||||
"github.com/go-audio/wav"
|
"github.com/go-audio/wav"
|
||||||
|
gguf "github.com/gpustack/gguf-parser-go"
|
||||||
"github.com/mudler/LocalAI/pkg/grpc/base"
|
"github.com/mudler/LocalAI/pkg/grpc/base"
|
||||||
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
||||||
"github.com/mudler/LocalAI/pkg/utils"
|
"github.com/mudler/LocalAI/pkg/utils"
|
||||||
@@ -33,10 +34,55 @@ var (
|
|||||||
CppTTSFree func(ptr uintptr)
|
CppTTSFree func(ptr uintptr)
|
||||||
CppTTSSetVoice func(name string) int
|
CppTTSSetVoice func(name string) int
|
||||||
CppTTSSetVoiceFile func(path string, refText string) int
|
CppTTSSetVoiceFile func(path string, refText string) int
|
||||||
|
|
||||||
|
// Word-level timestamp accessors (session-based, per-segment)
|
||||||
|
CppGetWordCount func(segI int) int
|
||||||
|
CppGetWordText func(segI int, wordI int) string
|
||||||
|
CppGetWordT0 func(segI int, wordI int) int64
|
||||||
|
CppGetWordT1 func(segI int, wordI int) int64
|
||||||
|
|
||||||
|
// Parakeet-specific word accessors (global, no segment index)
|
||||||
|
CppGetParakeetWordCount func() int
|
||||||
|
CppGetParakeetWordText func(wordI int) string
|
||||||
|
CppGetParakeetWordT0 func(wordI int) int64
|
||||||
|
CppGetParakeetWordT1 func(wordI int) int64
|
||||||
)
|
)
|
||||||
|
|
||||||
type CrispASR struct {
|
type CrispASR struct {
|
||||||
base.SingleThread
|
base.SingleThread
|
||||||
|
// sampleRate is the output rate (Hz) of the loaded TTS engine's PCM, used to
|
||||||
|
// write a correct WAV header. Most CrispASR TTS backends emit 24 kHz, but
|
||||||
|
// piper returns its model's native rate (16 kHz for x_low/low voices,
|
||||||
|
// 22.05 kHz for medium/high), so it is read from the GGUF metadata at Load.
|
||||||
|
sampleRate int
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultTTSSampleRate is the output rate assumed for CrispASR TTS engines that
|
||||||
|
// don't advertise one in GGUF metadata (vibevoice/orpheus/chatterbox/qwen3-tts
|
||||||
|
// all emit 24 kHz). piper is the exception and carries piper.sample_rate.
|
||||||
|
const defaultTTSSampleRate = 24000
|
||||||
|
|
||||||
|
// piperSampleRate reads the piper.sample_rate metadata key from a GGUF model.
|
||||||
|
// CrispASR's piper backend returns PCM at the model's native rate without
|
||||||
|
// resampling, so the WAV header must match it. Returns ok=false for non-piper
|
||||||
|
// models (key absent) or an unreadable file, letting the caller fall back to
|
||||||
|
// defaultTTSSampleRate.
|
||||||
|
func piperSampleRate(modelPath string) (int, bool) {
|
||||||
|
// Only scalar architecture keys are read, so skip the large array metadata
|
||||||
|
// (phoneme map) and mmap the header - same rationale as pkg/vram's reader.
|
||||||
|
f, err := gguf.ParseGGUFFile(modelPath, gguf.UseMMap(), gguf.SkipLargeMetadata())
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
kv, ok := f.Header.MetadataKV.Get("piper.sample_rate")
|
||||||
|
if !ok || kv.ValueType != gguf.GGUFMetadataValueTypeUint32 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
rate := int(kv.ValueUint32())
|
||||||
|
if rate <= 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return rate, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitOption splits a "prefix:value" model option into its key and value,
|
// splitOption splits a "prefix:value" model option into its key and value,
|
||||||
@@ -103,6 +149,14 @@ func (w *CrispASR) Load(opts *pb.ModelOptions) error {
|
|||||||
return fmt.Errorf("Failed to load CrispASR transcription model")
|
return fmt.Errorf("Failed to load CrispASR transcription model")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the TTS output sample rate for the WAV header. piper voices
|
||||||
|
// carry their native rate in GGUF metadata and CrispASR does not resample;
|
||||||
|
// every other engine emits the 24 kHz default.
|
||||||
|
w.sampleRate = defaultTTSSampleRate
|
||||||
|
if rate, ok := piperSampleRate(opts.ModelFile); ok {
|
||||||
|
w.sampleRate = rate
|
||||||
|
}
|
||||||
|
|
||||||
// Load the companion file (codec/tokenizer/s3gen) after the session is open.
|
// Load the companion file (codec/tokenizer/s3gen) after the session is open.
|
||||||
// rc==0 means success or "not applicable" for the active backend; only a
|
// rc==0 means success or "not applicable" for the active backend; only a
|
||||||
// negative code is fatal.
|
// negative code is fatal.
|
||||||
@@ -170,6 +224,28 @@ func (w *CrispASR) VAD(req *pb.VADRequest) (pb.VADResponse, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isValidWord reports whether a TranscriptWord contains recognisable speech
|
||||||
|
// content. The parakeet-specific word accessors can return stale initialisation
|
||||||
|
// data (model name, binary blobs) when a segment has no real speech. A word is
|
||||||
|
// considered valid only when:
|
||||||
|
// - the text is non-empty after trimming,
|
||||||
|
// - it contains no U+FFFD replacement characters (from binary data scrubbing),
|
||||||
|
// - both timestamps are non-negative,
|
||||||
|
// - the word has positive duration (end > start).
|
||||||
|
func isValidWord(w *pb.TranscriptWord) bool {
|
||||||
|
txt := strings.TrimSpace(w.Text)
|
||||||
|
if txt == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.ContainsRune(txt, '\uFFFD') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if w.Start < 0 || w.End < 0 || w.End <= w.Start {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (w *CrispASR) AudioTranscription(ctx context.Context, opts *pb.TranscriptRequest) (pb.TranscriptResult, error) {
|
func (w *CrispASR) AudioTranscription(ctx context.Context, opts *pb.TranscriptRequest) (pb.TranscriptResult, error) {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return pb.TranscriptResult{}, status.Error(codes.Canceled, "transcription cancelled")
|
return pb.TranscriptResult{}, status.Error(codes.Canceled, "transcription cancelled")
|
||||||
@@ -248,15 +324,54 @@ func (w *CrispASR) AudioTranscription(ctx context.Context, opts *pb.TranscriptRe
|
|||||||
// IDs, so Tokens is left empty.
|
// IDs, so Tokens is left empty.
|
||||||
txt := strings.ToValidUTF8(strings.Clone(CppGetSegmentText(i)), "<22>")
|
txt := strings.ToValidUTF8(strings.Clone(CppGetSegmentText(i)), "<22>")
|
||||||
|
|
||||||
|
// Populate word-level timestamps. Try session-based functions first
|
||||||
|
// (per-segment); fall back to parakeet-specific functions (global word
|
||||||
|
// list with no segment index — only populated on the first segment to
|
||||||
|
// avoid duplication).
|
||||||
|
words := []*pb.TranscriptWord{}
|
||||||
|
wordCount := CppGetWordCount(i)
|
||||||
|
if wordCount == 0 && i == 0 {
|
||||||
|
wordCount = CppGetParakeetWordCount()
|
||||||
|
for j := 0; j < wordCount; j++ {
|
||||||
|
w := &pb.TranscriptWord{
|
||||||
|
Start: CppGetParakeetWordT0(j) * (10000000),
|
||||||
|
End: CppGetParakeetWordT1(j) * (10000000),
|
||||||
|
Text: strings.ToValidUTF8(strings.Clone(CppGetParakeetWordText(j)), "<22>"),
|
||||||
|
}
|
||||||
|
if isValidWord(w) {
|
||||||
|
words = append(words, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for j := 0; j < wordCount; j++ {
|
||||||
|
w := &pb.TranscriptWord{
|
||||||
|
Start: CppGetWordT0(i, j) * (10000000),
|
||||||
|
End: CppGetWordT1(i, j) * (10000000),
|
||||||
|
Text: strings.ToValidUTF8(strings.Clone(CppGetWordText(i, j)), "<22>"),
|
||||||
|
}
|
||||||
|
if isValidWord(w) {
|
||||||
|
words = append(words, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip empty segments with no recognisable content (e.g. trailing
|
||||||
|
// silence segments that parakeet emits with stale init data).
|
||||||
|
trimmed := strings.TrimSpace(txt)
|
||||||
|
if trimmed == "" && len(words) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
segment := &pb.TranscriptSegment{
|
segment := &pb.TranscriptSegment{
|
||||||
Id: int32(i),
|
Id: int32(i),
|
||||||
Text: txt,
|
Text: txt,
|
||||||
Start: s, End: t,
|
Start: s, End: t,
|
||||||
|
Words: words,
|
||||||
}
|
}
|
||||||
|
|
||||||
segments = append(segments, segment)
|
segments = append(segments, segment)
|
||||||
|
|
||||||
text += " " + strings.TrimSpace(txt)
|
text += " " + trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
return pb.TranscriptResult{
|
return pb.TranscriptResult{
|
||||||
@@ -348,13 +463,20 @@ func (w *CrispASR) AudioTranscriptionStream(ctx context.Context, opts *pb.Transc
|
|||||||
s := CppGetSegmentStart(i) * 10000000
|
s := CppGetSegmentStart(i) * 10000000
|
||||||
t := CppGetSegmentEnd(i) * 10000000
|
t := CppGetSegmentEnd(i) * 10000000
|
||||||
txt := strings.ToValidUTF8(strings.Clone(CppGetSegmentText(i)), "<22>")
|
txt := strings.ToValidUTF8(strings.Clone(CppGetSegmentText(i)), "<22>")
|
||||||
|
|
||||||
|
// Skip empty segments (e.g. trailing silence that parakeet emits
|
||||||
|
// with stale init data).
|
||||||
|
trimmed := strings.TrimSpace(txt)
|
||||||
|
if trimmed == "" && s == t {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
segments = append(segments, &pb.TranscriptSegment{
|
segments = append(segments, &pb.TranscriptSegment{
|
||||||
Id: int32(i),
|
Id: int32(i),
|
||||||
Text: txt,
|
Text: txt,
|
||||||
Start: s, End: t,
|
Start: s, End: t,
|
||||||
})
|
})
|
||||||
|
|
||||||
trimmed := strings.TrimSpace(txt)
|
|
||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -390,7 +512,7 @@ func (w *CrispASR) synthesize(text string) ([]float32, error) {
|
|||||||
}
|
}
|
||||||
defer CppTTSFree(ptr)
|
defer CppTTSFree(ptr)
|
||||||
src := unsafe.Slice((*float32)(unsafe.Pointer(ptr)), int(n)) //nolint:govet // ptr addresses C-allocated PCM returned across the purego boundary; copied out immediately below, before tts_free.
|
src := unsafe.Slice((*float32)(unsafe.Pointer(ptr)), int(n)) //nolint:govet // ptr addresses C-allocated PCM returned across the purego boundary; copied out immediately below, before tts_free.
|
||||||
out := make([]float32, int(n)) // copy out of C memory before free
|
out := make([]float32, int(n)) // copy out of C memory before free
|
||||||
copy(out, src)
|
copy(out, src)
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
@@ -417,7 +539,7 @@ func (w *CrispASR) TTS(req *pb.TTSRequest) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return writeWAV24k(req.Dst, pcm)
|
return writeWAV(req.Dst, pcm, w.sampleRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TTSStream is the streaming counterpart to TTS. CrispASR has no progressive
|
// TTSStream is the streaming counterpart to TTS. CrispASR has no progressive
|
||||||
@@ -447,7 +569,7 @@ func (w *CrispASR) TTSStream(req *pb.TTSRequest, results chan []byte) error {
|
|||||||
}
|
}
|
||||||
defer func() { _ = os.Remove(dst) }()
|
defer func() { _ = os.Remove(dst) }()
|
||||||
|
|
||||||
if err := writeWAV24k(dst, pcm); err != nil {
|
if err := writeWAV(dst, pcm, w.sampleRate); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,14 +581,14 @@ func (w *CrispASR) TTSStream(req *pb.TTSRequest, results chan []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeWAV24k writes pcm as a 24000 Hz, mono, 16-bit PCM WAV at dst.
|
// writeWAV writes pcm as a sampleRate Hz, mono, 16-bit PCM WAV at dst.
|
||||||
func writeWAV24k(dst string, pcm []float32) error {
|
func writeWAV(dst string, pcm []float32, sampleRate int) error {
|
||||||
f, err := os.Create(dst)
|
f, err := os.Create(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("crispasr: create %q: %w", dst, err)
|
return fmt.Errorf("crispasr: create %q: %w", dst, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
enc := wav.NewEncoder(f, 24000, 16, 1, 1)
|
enc := wav.NewEncoder(f, sampleRate, 16, 1, 1)
|
||||||
ints := make([]int, len(pcm))
|
ints := make([]int, len(pcm))
|
||||||
for i, s := range pcm {
|
for i, s := range pcm {
|
||||||
if s > 1 {
|
if s > 1 {
|
||||||
@@ -477,7 +599,7 @@ func writeWAV24k(dst string, pcm []float32) error {
|
|||||||
ints[i] = int(s * 32767)
|
ints[i] = int(s * 32767)
|
||||||
}
|
}
|
||||||
buf := &audio.IntBuffer{
|
buf := &audio.IntBuffer{
|
||||||
Format: &audio.Format{NumChannels: 1, SampleRate: 24000},
|
Format: &audio.Format{NumChannels: 1, SampleRate: sampleRate},
|
||||||
Data: ints,
|
Data: ints,
|
||||||
SourceBitDepth: 16,
|
SourceBitDepth: 16,
|
||||||
}
|
}
|
||||||
|
|||||||
164
backend/go/crispasr/gocrispasr_samplerate_test.go
Normal file
164
backend/go/crispasr/gocrispasr_samplerate_test.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/go-audio/wav"
|
||||||
|
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GGUF metadata value type tags (subset) from the GGUF spec.
|
||||||
|
const (
|
||||||
|
ggufTypeUint32 uint32 = 4
|
||||||
|
ggufTypeString uint32 = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
type ggufKV struct {
|
||||||
|
key string
|
||||||
|
vtype uint32
|
||||||
|
val any
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeMinimalGGUF emits a valid, tensor-less GGUF file carrying only the given
|
||||||
|
// metadata key-values. Enough for the header-only parse path piperSampleRate
|
||||||
|
// uses; avoids pulling a real multi-MB voice into the test.
|
||||||
|
func writeMinimalGGUF(path string, kvs []ggufKV) error {
|
||||||
|
var b bytes.Buffer
|
||||||
|
b.WriteString("GGUF") // magic
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, uint32(3)) // version
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, uint64(0)) // tensor count
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, uint64(len(kvs)))
|
||||||
|
for _, kv := range kvs {
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, uint64(len(kv.key)))
|
||||||
|
b.WriteString(kv.key)
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, kv.vtype)
|
||||||
|
switch v := kv.val.(type) {
|
||||||
|
case uint32:
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, v)
|
||||||
|
case string:
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, uint64(len(v)))
|
||||||
|
b.WriteString(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, b.Bytes(), 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wavSampleRate decodes the WAV header at path and returns its sample rate.
|
||||||
|
func wavSampleRate(path string) (int, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
dec := wav.NewDecoder(f)
|
||||||
|
dec.ReadInfo()
|
||||||
|
return int(dec.SampleRate), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("piper sample rate", func() {
|
||||||
|
Context("piperSampleRate", func() {
|
||||||
|
It("reads piper.sample_rate from a piper GGUF (medium = 22050)", func() {
|
||||||
|
p := filepath.Join(GinkgoT().TempDir(), "voice.gguf")
|
||||||
|
Expect(writeMinimalGGUF(p, []ggufKV{
|
||||||
|
{key: "general.architecture", vtype: ggufTypeString, val: "piper"},
|
||||||
|
{key: "piper.sample_rate", vtype: ggufTypeUint32, val: uint32(22050)},
|
||||||
|
})).To(Succeed())
|
||||||
|
|
||||||
|
rate, ok := piperSampleRate(p)
|
||||||
|
Expect(ok).To(BeTrue(), "piper.sample_rate should be found")
|
||||||
|
Expect(rate).To(Equal(22050))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("reads the low-quality rate (16000)", func() {
|
||||||
|
p := filepath.Join(GinkgoT().TempDir(), "voice.gguf")
|
||||||
|
Expect(writeMinimalGGUF(p, []ggufKV{
|
||||||
|
{key: "piper.sample_rate", vtype: ggufTypeUint32, val: uint32(16000)},
|
||||||
|
})).To(Succeed())
|
||||||
|
|
||||||
|
rate, ok := piperSampleRate(p)
|
||||||
|
Expect(ok).To(BeTrue())
|
||||||
|
Expect(rate).To(Equal(16000))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns ok=false for a non-piper GGUF (no piper.sample_rate key)", func() {
|
||||||
|
p := filepath.Join(GinkgoT().TempDir(), "other.gguf")
|
||||||
|
Expect(writeMinimalGGUF(p, []ggufKV{
|
||||||
|
{key: "general.architecture", vtype: ggufTypeString, val: "vibevoice"},
|
||||||
|
})).To(Succeed())
|
||||||
|
|
||||||
|
_, ok := piperSampleRate(p)
|
||||||
|
Expect(ok).To(BeFalse())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns ok=false for an unreadable/non-GGUF file", func() {
|
||||||
|
p := filepath.Join(GinkgoT().TempDir(), "garbage.gguf")
|
||||||
|
Expect(os.WriteFile(p, []byte("not a gguf"), 0o644)).To(Succeed())
|
||||||
|
|
||||||
|
_, ok := piperSampleRate(p)
|
||||||
|
Expect(ok).To(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// End-to-end through the built .so. Gated on CRISPASR_PIPER_MODEL_PATH (a
|
||||||
|
// real piper voice GGUF) like the other model-backed specs; never runs in
|
||||||
|
// default CI. Proves CrispASR's piper backend output rate flows into the
|
||||||
|
// WAV header instead of the hardcoded 24 kHz default.
|
||||||
|
Context("piper TTS end-to-end", func() {
|
||||||
|
It("writes the WAV at the model's native piper.sample_rate", func() {
|
||||||
|
model := os.Getenv("CRISPASR_PIPER_MODEL_PATH")
|
||||||
|
if model == "" {
|
||||||
|
Skip("set CRISPASR_PIPER_MODEL_PATH to run the piper e2e spec")
|
||||||
|
}
|
||||||
|
ensureLibLoaded()
|
||||||
|
|
||||||
|
expected, ok := piperSampleRate(model)
|
||||||
|
Expect(ok).To(BeTrue(), "model should carry piper.sample_rate metadata")
|
||||||
|
|
||||||
|
w := &CrispASR{}
|
||||||
|
Expect(w.Load(&pb.ModelOptions{
|
||||||
|
ModelFile: model,
|
||||||
|
Options: []string{"backend:piper"},
|
||||||
|
Threads: 4,
|
||||||
|
})).To(Succeed())
|
||||||
|
|
||||||
|
dst := filepath.Join(GinkgoT().TempDir(), "piper.wav")
|
||||||
|
Expect(w.TTS(&pb.TTSRequest{Text: "Hello from CrispASR piper.", Dst: dst})).To(Succeed())
|
||||||
|
|
||||||
|
info, err := os.Stat(dst)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(info.Size()).To(BeNumerically(">", 1024), "expected a non-trivial WAV")
|
||||||
|
|
||||||
|
rate, err := wavSampleRate(dst)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(rate).To(Equal(expected),
|
||||||
|
"WAV header rate must equal the model's native piper.sample_rate, not the 24k default")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("writeWAV", func() {
|
||||||
|
It("writes the WAV header at the given sample rate (22050 for piper, not the 24k default)", func() {
|
||||||
|
dst := filepath.Join(GinkgoT().TempDir(), "out.wav")
|
||||||
|
pcm := make([]float32, 220) // 10 ms of silence is enough for a header
|
||||||
|
Expect(writeWAV(dst, pcm, 22050)).To(Succeed())
|
||||||
|
|
||||||
|
rate, err := wavSampleRate(dst)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(rate).To(Equal(22050))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("writes a 16000 Hz header for low-quality piper voices", func() {
|
||||||
|
dst := filepath.Join(GinkgoT().TempDir(), "out.wav")
|
||||||
|
pcm := make([]float32, 160)
|
||||||
|
Expect(writeWAV(dst, pcm, 16000)).To(Succeed())
|
||||||
|
|
||||||
|
rate, err := wavSampleRate(dst)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(rate).To(Equal(16000))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -4,6 +4,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/ebitengine/purego"
|
"github.com/ebitengine/purego"
|
||||||
grpc "github.com/mudler/LocalAI/pkg/grpc"
|
grpc "github.com/mudler/LocalAI/pkg/grpc"
|
||||||
@@ -21,7 +22,11 @@ type LibFuncs struct {
|
|||||||
func main() {
|
func main() {
|
||||||
libName := os.Getenv("CRISPASR_LIBRARY")
|
libName := os.Getenv("CRISPASR_LIBRARY")
|
||||||
if libName == "" {
|
if libName == "" {
|
||||||
libName = "./libgocrispasr-fallback.so"
|
if runtime.GOOS == "darwin" {
|
||||||
|
libName = "./libgocrispasr-fallback.dylib"
|
||||||
|
} else {
|
||||||
|
libName = "./libgocrispasr-fallback.so"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lib, err := purego.Dlopen(libName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
lib, err := purego.Dlopen(libName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||||
@@ -44,6 +49,14 @@ func main() {
|
|||||||
{&CppTTSFree, "tts_free"},
|
{&CppTTSFree, "tts_free"},
|
||||||
{&CppTTSSetVoice, "tts_set_voice"},
|
{&CppTTSSetVoice, "tts_set_voice"},
|
||||||
{&CppTTSSetVoiceFile, "tts_set_voice_file"},
|
{&CppTTSSetVoiceFile, "tts_set_voice_file"},
|
||||||
|
{&CppGetWordCount, "get_word_count"},
|
||||||
|
{&CppGetWordText, "get_word_text"},
|
||||||
|
{&CppGetWordT0, "get_word_t0"},
|
||||||
|
{&CppGetWordT1, "get_word_t1"},
|
||||||
|
{&CppGetParakeetWordCount, "get_parakeet_word_count"},
|
||||||
|
{&CppGetParakeetWordText, "get_parakeet_word_text"},
|
||||||
|
{&CppGetParakeetWordT0, "get_parakeet_word_t0"},
|
||||||
|
{&CppGetParakeetWordT1, "get_parakeet_word_t1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, lf := range libFuncs {
|
for _, lf := range libFuncs {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ REPO_ROOT="${CURDIR}/../../.."
|
|||||||
mkdir -p $CURDIR/package/lib
|
mkdir -p $CURDIR/package/lib
|
||||||
|
|
||||||
cp -avf $CURDIR/crispasr $CURDIR/package/
|
cp -avf $CURDIR/crispasr $CURDIR/package/
|
||||||
cp -fv $CURDIR/libgocrispasr-*.so $CURDIR/package/
|
cp -fv $CURDIR/libgocrispasr-*.so $CURDIR/package/ 2>/dev/null || true
|
||||||
|
cp -fv $CURDIR/libgocrispasr-*.dylib $CURDIR/package/ 2>/dev/null || true
|
||||||
cp -fv $CURDIR/run.sh $CURDIR/package/
|
cp -fv $CURDIR/run.sh $CURDIR/package/
|
||||||
|
|
||||||
# Detect architecture and copy appropriate libraries
|
# Detect architecture and copy appropriate libraries
|
||||||
@@ -51,6 +52,32 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Bundle espeak-ng (+ its libpcaudio/libsonic runtime deps) and its voice data so
|
||||||
|
# the piper TTS backend can phonemize non-English text. CrispASR dlopens
|
||||||
|
# libespeak-ng.so.1 at runtime (the MIT-clean path); the dlopen succeeds loading
|
||||||
|
# libespeak-ng but FAILS if libpcaudio/libsonic are absent, so all three .so are
|
||||||
|
# required. run.sh points CRISPASR_ESPEAK_DATA_PATH at the bundled data dir.
|
||||||
|
# Best-effort: only copied when present, so a local dev build without espeak-ng
|
||||||
|
# installed still packages the rest (English voices keep working).
|
||||||
|
ESPEAK_LIBDIR=""
|
||||||
|
for d in /usr/lib/x86_64-linux-gnu /usr/lib/aarch64-linux-gnu; do
|
||||||
|
if [ -f "$d/libespeak-ng.so.1" ]; then
|
||||||
|
ESPEAK_LIBDIR="$d"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -n "$ESPEAK_LIBDIR" ]; then
|
||||||
|
echo "Bundling espeak-ng from $ESPEAK_LIBDIR ..."
|
||||||
|
cp -arfLv "$ESPEAK_LIBDIR/libespeak-ng.so.1" $CURDIR/package/lib/
|
||||||
|
cp -arfLv "$ESPEAK_LIBDIR/libpcaudio.so.0" $CURDIR/package/lib/
|
||||||
|
cp -arfLv "$ESPEAK_LIBDIR/libsonic.so.0" $CURDIR/package/lib/
|
||||||
|
if [ -d "$ESPEAK_LIBDIR/espeak-ng-data" ]; then
|
||||||
|
cp -arfLv "$ESPEAK_LIBDIR/espeak-ng-data" $CURDIR/package/
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "espeak-ng not found; non-English piper voices will not phonemize"
|
||||||
|
fi
|
||||||
|
|
||||||
# Package GPU libraries based on BUILD_TYPE
|
# Package GPU libraries based on BUILD_TYPE
|
||||||
# The GPU library packaging script will detect BUILD_TYPE and copy appropriate GPU libraries
|
# The GPU library packaging script will detect BUILD_TYPE and copy appropriate GPU libraries
|
||||||
GPU_LIB_SCRIPT="${REPO_ROOT}/scripts/build/package-gpu-libs.sh"
|
GPU_LIB_SCRIPT="${REPO_ROOT}/scripts/build/package-gpu-libs.sh"
|
||||||
|
|||||||
@@ -12,9 +12,13 @@ if [ "$(uname)" != "Darwin" ]; then
|
|||||||
grep -e "flags" /proc/cpuinfo | head -1
|
grep -e "flags" /proc/cpuinfo | head -1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
LIBRARY="$CURDIR/libgocrispasr-fallback.so"
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
|
# macOS: single dylib variant (Metal or Accelerate)
|
||||||
|
LIBRARY="$CURDIR/libgocrispasr-fallback.dylib"
|
||||||
|
export DYLD_LIBRARY_PATH=$CURDIR/lib:$DYLD_LIBRARY_PATH
|
||||||
|
else
|
||||||
|
LIBRARY="$CURDIR/libgocrispasr-fallback.so"
|
||||||
|
|
||||||
if [ "$(uname)" != "Darwin" ]; then
|
|
||||||
if grep -q -e "\savx\s" /proc/cpuinfo ; then
|
if grep -q -e "\savx\s" /proc/cpuinfo ; then
|
||||||
echo "CPU: AVX found OK"
|
echo "CPU: AVX found OK"
|
||||||
if [ -e $CURDIR/libgocrispasr-avx.so ]; then
|
if [ -e $CURDIR/libgocrispasr-avx.so ]; then
|
||||||
@@ -36,11 +40,17 @@ if [ "$(uname)" != "Darwin" ]; then
|
|||||||
LIBRARY="$CURDIR/libgocrispasr-avx512.so"
|
LIBRARY="$CURDIR/libgocrispasr-avx512.so"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH=$CURDIR/lib:$LD_LIBRARY_PATH
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export LD_LIBRARY_PATH=$CURDIR/lib:$LD_LIBRARY_PATH
|
|
||||||
export CRISPASR_LIBRARY=$LIBRARY
|
export CRISPASR_LIBRARY=$LIBRARY
|
||||||
|
|
||||||
|
# Point piper's espeak-ng phonemizer at the bundled voice data. The variable
|
||||||
|
# names the directory CONTAINING espeak-ng-data (package.sh drops it next to
|
||||||
|
# this script). Harmless when espeak-ng wasn't bundled.
|
||||||
|
export CRISPASR_ESPEAK_DATA_PATH=$CURDIR
|
||||||
|
|
||||||
# If there is a lib/ld.so, use it
|
# If there is a lib/ld.so, use it
|
||||||
if [ -f $CURDIR/lib/ld.so ]; then
|
if [ -f $CURDIR/lib/ld.so ]; then
|
||||||
echo "Using lib/ld.so"
|
echo "Using lib/ld.so"
|
||||||
|
|||||||
7
backend/go/depth-anything-cpp/.gitignore
vendored
Normal file
7
backend/go/depth-anything-cpp/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
sources/
|
||||||
|
build*/
|
||||||
|
package/
|
||||||
|
libdepthanythingcpp*.so
|
||||||
|
depth-anything-cpp
|
||||||
|
test-models/
|
||||||
|
test-data/
|
||||||
28
backend/go/depth-anything-cpp/CMakeLists.txt
Normal file
28
backend/go/depth-anything-cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.18)
|
||||||
|
project(libdepthanythingcpp LANGUAGES C CXX)
|
||||||
|
|
||||||
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# Static-link ggml into the depth-anything shared library so the resulting .so
|
||||||
|
# has no runtime dependency on an external libggml — only on
|
||||||
|
# libc/libstdc++/libgomp, which the LocalAI package step bundles into the
|
||||||
|
# docker image.
|
||||||
|
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static libraries" FORCE)
|
||||||
|
|
||||||
|
# depth-anything.cpp build switches: skip CLI/tests, but build libdepthanything
|
||||||
|
# itself as a SHARED library (DA_SHARED) while ggml stays static
|
||||||
|
# (BUILD_SHARED_LIBS OFF above). The da_capi_* C ABI is compiled into
|
||||||
|
# src/da_capi.cpp and re-exported by that shared library, so no extra MODULE
|
||||||
|
# wrapper is needed (unlike locate-anything.cpp).
|
||||||
|
set(DA_BUILD_CLI OFF CACHE BOOL "Disable depth-anything CLI" FORCE)
|
||||||
|
set(DA_BUILD_TESTS OFF CACHE BOOL "Disable depth-anything tests" FORCE)
|
||||||
|
set(DA_SHARED ON CACHE BOOL "Build libdepthanything as a shared lib" FORCE)
|
||||||
|
|
||||||
|
add_subdirectory(./sources/depth-anything.cpp)
|
||||||
|
|
||||||
|
# Emit libdepthanything.so into the top-level build dir so the Makefile can
|
||||||
|
# rename it to the per-variant libdepthanythingcpp-<variant>.so.
|
||||||
|
set_target_properties(depthanything PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
150
backend/go/depth-anything-cpp/Makefile
Normal file
150
backend/go/depth-anything-cpp/Makefile
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
CMAKE_ARGS?=
|
||||||
|
BUILD_TYPE?=
|
||||||
|
NATIVE?=false
|
||||||
|
|
||||||
|
GOCMD?=go
|
||||||
|
GO_TAGS?=
|
||||||
|
JOBS?=$(shell nproc --ignore=1)
|
||||||
|
|
||||||
|
# depth-anything.cpp. Pin to a specific commit for a stable build; a squash
|
||||||
|
# merge upstream can orphan a branch, so the native version is pinned by SHA.
|
||||||
|
# This SHA adds the Depth Anything V2 engine + C-API routing (depth-only,
|
||||||
|
# relative + metric) on top of the nested two-file metric C-API (abi_version 4,
|
||||||
|
# da_capi_load_nested) required by the depth-anything-3-nested gallery model.
|
||||||
|
# It is kept alive by the upstream tag da2-support (survives a squash-merge);
|
||||||
|
# repoint to the master merge commit once mudler/depth-anything.cpp PR #1 lands.
|
||||||
|
DEPTHANYTHING_REPO?=https://github.com/mudler/depth-anything.cpp.git
|
||||||
|
DEPTHANYTHING_VERSION?=f4e17dea695dd12ae76bea98ba58030996b98118
|
||||||
|
|
||||||
|
ifeq ($(NATIVE),false)
|
||||||
|
CMAKE_ARGS+=-DGGML_NATIVE=OFF
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Forward LocalAI's BUILD_TYPE to the matching ggml backend switch. depth-anything.cpp
|
||||||
|
# force-sets GGML_CUDA/GGML_VULKAN/GGML_METAL from its own DA_GGML_* options, so
|
||||||
|
# those must be toggled via the DA_GGML_* names (a bare -DGGML_CUDA=ON would be
|
||||||
|
# overridden); the remaining ggml switches pass straight through.
|
||||||
|
ifeq ($(BUILD_TYPE),cublas)
|
||||||
|
CMAKE_ARGS+=-DGGML_CUDA=ON -DDA_GGML_CUDA=ON
|
||||||
|
else ifeq ($(BUILD_TYPE),openblas)
|
||||||
|
CMAKE_ARGS+=-DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS
|
||||||
|
else ifeq ($(BUILD_TYPE),clblas)
|
||||||
|
CMAKE_ARGS+=-DGGML_CLBLAST=ON
|
||||||
|
else ifeq ($(BUILD_TYPE),hipblas)
|
||||||
|
ROCM_HOME ?= /opt/rocm
|
||||||
|
ROCM_PATH ?= /opt/rocm
|
||||||
|
export CXX=$(ROCM_HOME)/llvm/bin/clang++
|
||||||
|
export CC=$(ROCM_HOME)/llvm/bin/clang
|
||||||
|
AMDGPU_TARGETS?=gfx908,gfx90a,gfx942,gfx950,gfx1030,gfx1100,gfx1101,gfx1102,gfx1200,gfx1201
|
||||||
|
CMAKE_ARGS+=-DGGML_HIPBLAS=ON -DAMDGPU_TARGETS=$(AMDGPU_TARGETS)
|
||||||
|
else ifeq ($(BUILD_TYPE),vulkan)
|
||||||
|
CMAKE_ARGS+=-DGGML_VULKAN=ON -DDA_GGML_VULKAN=ON
|
||||||
|
else ifeq ($(OS),Darwin)
|
||||||
|
ifneq ($(BUILD_TYPE),metal)
|
||||||
|
CMAKE_ARGS+=-DGGML_METAL=OFF
|
||||||
|
else
|
||||||
|
CMAKE_ARGS+=-DGGML_METAL=ON
|
||||||
|
CMAKE_ARGS+=-DGGML_METAL_EMBED_LIBRARY=ON
|
||||||
|
CMAKE_ARGS+=-DDA_GGML_METAL=ON
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(BUILD_TYPE),sycl_f16)
|
||||||
|
CMAKE_ARGS+=-DGGML_SYCL=ON \
|
||||||
|
-DCMAKE_C_COMPILER=icx \
|
||||||
|
-DCMAKE_CXX_COMPILER=icpx \
|
||||||
|
-DGGML_SYCL_F16=ON
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(BUILD_TYPE),sycl_f32)
|
||||||
|
CMAKE_ARGS+=-DGGML_SYCL=ON \
|
||||||
|
-DCMAKE_C_COMPILER=icx \
|
||||||
|
-DCMAKE_CXX_COMPILER=icpx
|
||||||
|
endif
|
||||||
|
|
||||||
|
sources/depth-anything.cpp:
|
||||||
|
mkdir -p sources && \
|
||||||
|
git clone --recursive $(DEPTHANYTHING_REPO) sources/depth-anything.cpp && \
|
||||||
|
cd sources/depth-anything.cpp && \
|
||||||
|
git checkout $(DEPTHANYTHING_VERSION) && \
|
||||||
|
git submodule update --init --recursive --depth 1 --single-branch
|
||||||
|
|
||||||
|
# Detect OS
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
|
||||||
|
# Only build CPU variants on Linux
|
||||||
|
ifeq ($(UNAME_S),Linux)
|
||||||
|
VARIANT_TARGETS = libdepthanythingcpp-avx.so libdepthanythingcpp-avx2.so libdepthanythingcpp-avx512.so libdepthanythingcpp-fallback.so
|
||||||
|
else
|
||||||
|
# On non-Linux (e.g., Darwin), build only fallback variant
|
||||||
|
VARIANT_TARGETS = libdepthanythingcpp-fallback.dylib
|
||||||
|
endif
|
||||||
|
|
||||||
|
depth-anything-cpp: main.go godepthanythingcpp.go $(VARIANT_TARGETS)
|
||||||
|
CGO_ENABLED=0 $(GOCMD) build -tags "$(GO_TAGS)" -o depth-anything-cpp ./
|
||||||
|
|
||||||
|
package: depth-anything-cpp
|
||||||
|
bash package.sh
|
||||||
|
|
||||||
|
build: package
|
||||||
|
|
||||||
|
clean: purge
|
||||||
|
rm -rf libdepthanythingcpp*.so libdepthanythingcpp*.dylib depth-anything-cpp package sources
|
||||||
|
|
||||||
|
purge:
|
||||||
|
rm -rf build*
|
||||||
|
|
||||||
|
# Build all variants (Linux only)
|
||||||
|
ifeq ($(UNAME_S),Linux)
|
||||||
|
libdepthanythingcpp-avx.so: sources/depth-anything.cpp
|
||||||
|
rm -rfv build-$@
|
||||||
|
$(info ${GREEN}I depth-anything-cpp build info:avx${RESET})
|
||||||
|
SO_TARGET=$@ CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libdepthanythingcpp-custom
|
||||||
|
rm -rfv build-$@
|
||||||
|
|
||||||
|
libdepthanythingcpp-avx2.so: sources/depth-anything.cpp
|
||||||
|
rm -rfv build-$@
|
||||||
|
$(info ${GREEN}I depth-anything-cpp build info:avx2${RESET})
|
||||||
|
SO_TARGET=$@ CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=on -DGGML_AVX512=off -DGGML_FMA=on -DGGML_F16C=on -DGGML_BMI2=on" $(MAKE) libdepthanythingcpp-custom
|
||||||
|
rm -rfv build-$@
|
||||||
|
|
||||||
|
libdepthanythingcpp-avx512.so: sources/depth-anything.cpp
|
||||||
|
rm -rfv build-$@
|
||||||
|
$(info ${GREEN}I depth-anything-cpp build info:avx512${RESET})
|
||||||
|
SO_TARGET=$@ CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=on -DGGML_AVX512=on -DGGML_FMA=on -DGGML_F16C=on -DGGML_BMI2=on" $(MAKE) libdepthanythingcpp-custom
|
||||||
|
rm -rfv build-$@
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Build fallback variant (all platforms)
|
||||||
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
libdepthanythingcpp-fallback.dylib: sources/depth-anything.cpp
|
||||||
|
rm -rfv build-$@
|
||||||
|
$(info ${GREEN}I depth-anything-cpp build info:fallback${RESET})
|
||||||
|
SO_TARGET=$@ CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libdepthanythingcpp-custom
|
||||||
|
rm -rfv build-$@
|
||||||
|
else
|
||||||
|
libdepthanythingcpp-fallback.so: sources/depth-anything.cpp
|
||||||
|
rm -rfv build-$@
|
||||||
|
$(info ${GREEN}I depth-anything-cpp build info:fallback${RESET})
|
||||||
|
SO_TARGET=$@ CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libdepthanythingcpp-custom
|
||||||
|
rm -rfv build-$@
|
||||||
|
endif
|
||||||
|
|
||||||
|
libdepthanythingcpp-custom: CMakeLists.txt
|
||||||
|
mkdir -p build-$(SO_TARGET) && \
|
||||||
|
cd build-$(SO_TARGET) && \
|
||||||
|
cmake .. $(CMAKE_ARGS) && \
|
||||||
|
cmake --build . --config Release -j$(JOBS) && \
|
||||||
|
cd .. && \
|
||||||
|
(mv build-$(SO_TARGET)/libdepthanything.so ./$(SO_TARGET) 2>/dev/null || \
|
||||||
|
mv build-$(SO_TARGET)/libdepthanything.dylib ./$(SO_TARGET) 2>/dev/null)
|
||||||
|
|
||||||
|
all: depth-anything-cpp package
|
||||||
|
|
||||||
|
# `test` is invoked by the top-level Makefile's `test-extra` target. It builds
|
||||||
|
# the backend binary + the fallback shared library (needed for dlopen at
|
||||||
|
# runtime), then runs test.sh which downloads a small GGUF + a test image and
|
||||||
|
# exercises the gRPC Load/Predict wire path via the Go smoke test in
|
||||||
|
# main_test.go.
|
||||||
|
test: depth-anything-cpp libdepthanythingcpp-fallback.so
|
||||||
|
bash test.sh
|
||||||
556
backend/go/depth-anything-cpp/godepthanythingcpp.go
Normal file
556
backend/go/depth-anything-cpp/godepthanythingcpp.go
Normal file
@@ -0,0 +1,556 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// godepthanythingcpp.go - gRPC handlers (Load, Predict, GenerateImage) for the
|
||||||
|
// depth-anything-cpp backend, wrapping the Depth Anything 3 ggml C-API
|
||||||
|
// (libdepthanythingcpp-<variant>.so) via purego.
|
||||||
|
//
|
||||||
|
// Embeds base.SingleThread to default the unimplemented RPCs to "not supported"
|
||||||
|
// and to serialize calls — the C side shares a ggml graph allocator and is NOT
|
||||||
|
// reentrant, so all inference must run one-at-a-time.
|
||||||
|
//
|
||||||
|
// Depth has no native OpenAI endpoint, so the model is exposed two ways:
|
||||||
|
//
|
||||||
|
// - GenerateImage(src, dst): run depth on the src image and write a
|
||||||
|
// min-max-normalised grayscale depth PNG to dst.
|
||||||
|
// - Predict(images[0]): run depth+pose and return a JSON blob with the depth
|
||||||
|
// dimensions, depth stats and the camera extrinsics (3x4) / intrinsics (3x3).
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/mudler/LocalAI/pkg/grpc/base"
|
||||||
|
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// C-API function pointers, registered in main.go via purego. The da_capi_*
|
||||||
|
// symbols live inside libdepthanything (src/da_capi.cpp) and are re-exported by
|
||||||
|
// the DA_SHARED build.
|
||||||
|
var (
|
||||||
|
// da_capi_load(const char* gguf_path, int n_threads) -> da_ctx* (0 = fail)
|
||||||
|
CapiLoad func(gguf string, nThreads int32) uintptr
|
||||||
|
// da_capi_load_nested(const char* anyview_gguf, const char* metric_gguf,
|
||||||
|
// int n_threads) -> da_ctx* (0 = fail). The returned ctx serves the nested
|
||||||
|
// metric model: depth/pose calls produce final metric-scale depth + scaled pose.
|
||||||
|
CapiLoadNested func(anyview string, metric string, nThreads int32) uintptr
|
||||||
|
// da_capi_free(da_ctx* ctx) — safe on a 0 handle.
|
||||||
|
CapiFree func(handle uintptr)
|
||||||
|
// da_capi_last_error(da_ctx* ctx) -> const char* (owned by ctx, "" if none).
|
||||||
|
// purego marshals the returned C string into a Go string (a copy), so we
|
||||||
|
// never free it.
|
||||||
|
CapiLastError func(handle uintptr) string
|
||||||
|
// da_capi_depth_path(ctx, image_path, out_h*, out_w*) -> float* depth map
|
||||||
|
// (row-major H*W); nil on error. Caller frees via da_capi_free_floats.
|
||||||
|
CapiDepthPath func(handle uintptr, imagePath string, outH *int32, outW *int32) *float32
|
||||||
|
// da_capi_free_floats(float* p)
|
||||||
|
CapiFreeFloats func(p *float32)
|
||||||
|
// da_capi_pose_path(ctx, image_path, out_ext[12], out_intr[9]) -> 0 ok, -1 err
|
||||||
|
CapiPosePath func(handle uintptr, imagePath string, outExt *float32, outIntr *float32) int32
|
||||||
|
// da_capi_depth_dense(ctx, image_path, out_h*, out_w*, out_depth**, out_conf**,
|
||||||
|
// out_sky**, out_ext[12], out_intr[9], out_is_metric*) -> 0 ok, -1 err.
|
||||||
|
// Each non-NULL out_depth/out_conf/out_sky receives a malloc'd float[H*W] (free
|
||||||
|
// via da_capi_free_floats); buffers the model doesn't produce are set NULL.
|
||||||
|
CapiDepthDense func(handle uintptr, imagePath string,
|
||||||
|
outH, outW *int32,
|
||||||
|
outDepth, outConf, outSky **float32,
|
||||||
|
outExt, outIntr *float32,
|
||||||
|
outIsMetric *int32) int32
|
||||||
|
// da_capi_points(ctx, image_path, conf_thresh, out_n*, out_xyz**, out_rgb**) ->
|
||||||
|
// 0 ok, -1 err. *out_xyz = malloc'd float[3*N] (free via da_capi_free_floats),
|
||||||
|
// *out_rgb = malloc'd uint8[3*N] (free via da_capi_free_bytes).
|
||||||
|
CapiPoints func(handle uintptr, imagePath string, confThresh float32,
|
||||||
|
outN *int32, outXyz **float32, outRgb **byte) int32
|
||||||
|
// da_capi_free_bytes(unsigned char* p)
|
||||||
|
CapiFreeBytes func(p *byte)
|
||||||
|
// da_capi_export_glb(ctx, image_path, out_glb) -> 0 ok, -1 err
|
||||||
|
CapiExportGlb func(handle uintptr, imagePath string, outGlb string) int32
|
||||||
|
// da_capi_export_colmap(ctx, image_path, out_dir, binary) -> 0 ok, -1 err
|
||||||
|
CapiExportColmap func(handle uintptr, imagePath string, outDir string, binary int32) int32
|
||||||
|
)
|
||||||
|
|
||||||
|
type DepthAnythingCpp struct {
|
||||||
|
base.SingleThread
|
||||||
|
handle uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads the GGUF model at opts.ModelFile (joined with opts.ModelPath if
|
||||||
|
// relative) and stores the da_ctx handle for later inference calls.
|
||||||
|
func (r *DepthAnythingCpp) Load(opts *pb.ModelOptions) error {
|
||||||
|
modelFile := opts.ModelFile
|
||||||
|
if modelFile == "" {
|
||||||
|
modelFile = opts.Model
|
||||||
|
}
|
||||||
|
if modelFile == "" {
|
||||||
|
return fmt.Errorf("depth-anything-cpp: ModelFile is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve := func(name string) string {
|
||||||
|
if filepath.IsAbs(name) {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return filepath.Join(opts.ModelPath, name)
|
||||||
|
}
|
||||||
|
modelPath := resolve(modelFile)
|
||||||
|
|
||||||
|
if _, err := os.Stat(modelPath); err != nil {
|
||||||
|
return fmt.Errorf("depth-anything-cpp: model file not found: %s: %w", modelPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nested metric models are a two-file pair: the main model is the anyview
|
||||||
|
// (GIANT) branch and the metric (ViT-L + DPT/sky) branch is named via a
|
||||||
|
// "metric_model:<filename>" entry in opts.Options. When present we load both
|
||||||
|
// branches so the engine runs the nested metric alignment.
|
||||||
|
metricFile := optionValue(opts.Options, "metric_model")
|
||||||
|
|
||||||
|
threads := opts.Threads
|
||||||
|
if threads <= 0 {
|
||||||
|
threads = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release previous model if any (re-Load).
|
||||||
|
if r.handle != 0 {
|
||||||
|
CapiFree(r.handle)
|
||||||
|
r.handle = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var h uintptr
|
||||||
|
if metricFile != "" {
|
||||||
|
metricPath := resolve(metricFile)
|
||||||
|
if _, err := os.Stat(metricPath); err != nil {
|
||||||
|
return fmt.Errorf("depth-anything-cpp: metric_model file not found: %s: %w", metricPath, err)
|
||||||
|
}
|
||||||
|
h = CapiLoadNested(modelPath, metricPath, threads)
|
||||||
|
if h == 0 {
|
||||||
|
if msg := CapiLastError(0); msg != "" {
|
||||||
|
return fmt.Errorf("depth-anything-cpp: da_capi_load_nested failed for %s + %s: %s", modelPath, metricPath, msg)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("depth-anything-cpp: da_capi_load_nested failed for %s + %s", modelPath, metricPath)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h = CapiLoad(modelPath, threads)
|
||||||
|
if h == 0 {
|
||||||
|
// da_capi_last_error needs a ctx; on a failed load we have none (it
|
||||||
|
// returns "" for a null ctx), so the text is best-effort.
|
||||||
|
if msg := CapiLastError(0); msg != "" {
|
||||||
|
return fmt.Errorf("depth-anything-cpp: da_capi_load failed for %s: %s", modelPath, msg)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("depth-anything-cpp: da_capi_load failed for %s", modelPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.handle = h
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// optionValue returns the value of the first "key:value" entry in opts whose key
|
||||||
|
// matches (case-sensitive), or "" if absent. Mirrors how other LocalAI backends
|
||||||
|
// read ModelOptions.Options.
|
||||||
|
func optionValue(opts []string, key string) string {
|
||||||
|
prefix := key + ":"
|
||||||
|
for _, o := range opts {
|
||||||
|
if strings.HasPrefix(o, prefix) {
|
||||||
|
return strings.TrimSpace(o[len(prefix):])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// depthResult is the JSON payload returned by Predict.
|
||||||
|
type depthResult struct {
|
||||||
|
DepthW int `json:"depth_w"`
|
||||||
|
DepthH int `json:"depth_h"`
|
||||||
|
DepthMin float32 `json:"depth_min"`
|
||||||
|
DepthMax float32 `json:"depth_max"`
|
||||||
|
Extrinsics [12]float32 `json:"extrinsics"` // 3x4 row-major
|
||||||
|
Intrinsics [9]float32 `json:"intrinsics"` // 3x3 row-major
|
||||||
|
}
|
||||||
|
|
||||||
|
// Predict runs depth+pose on the first supplied image and returns depth
|
||||||
|
// statistics + camera pose as a JSON string. LocalAI wraps the string into the
|
||||||
|
// Reply.Message of the gRPC response. The image in Images[0] may be a
|
||||||
|
// filesystem path or a base64-encoded payload.
|
||||||
|
func (r *DepthAnythingCpp) Predict(opts *pb.PredictOptions) (string, error) {
|
||||||
|
imgs := opts.GetImages()
|
||||||
|
if len(imgs) == 0 {
|
||||||
|
return "", fmt.Errorf("depth-anything-cpp: Predict requires an image in Images[]")
|
||||||
|
}
|
||||||
|
|
||||||
|
imgPath, cleanup, err := materializeImage(imgs[0])
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("depth-anything-cpp: %w", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
depth, h, w, ext, intr, err := r.runDepthPose(imgPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
dmin, dmax := minMax(depth)
|
||||||
|
payload, err := json.Marshal(depthResult{
|
||||||
|
DepthW: w, DepthH: h,
|
||||||
|
DepthMin: dmin, DepthMax: dmax,
|
||||||
|
Extrinsics: ext, Intrinsics: intr,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("depth-anything-cpp: marshal: %w", err)
|
||||||
|
}
|
||||||
|
return string(payload), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateImage runs depth on req.Src and writes a normalised grayscale depth
|
||||||
|
// PNG to req.Dst.
|
||||||
|
func (r *DepthAnythingCpp) GenerateImage(req *pb.GenerateImageRequest) error {
|
||||||
|
if req.GetSrc() == "" {
|
||||||
|
return fmt.Errorf("depth-anything-cpp: GenerateImage requires src")
|
||||||
|
}
|
||||||
|
if req.GetDst() == "" {
|
||||||
|
return fmt.Errorf("depth-anything-cpp: GenerateImage requires dst")
|
||||||
|
}
|
||||||
|
|
||||||
|
imgPath, cleanup, err := materializeImage(req.GetSrc())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("depth-anything-cpp: %w", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
depth, h, w, _, _, err := r.runDepthPose(imgPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return writeDepthPNG(req.GetDst(), depth, h, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Depth is the typed Depth RPC. It runs the Depth Anything 3 pipeline on the
|
||||||
|
// request's src image and fills a DepthResponse honoring the include_* flags and
|
||||||
|
// exports: per-pixel metric depth + confidence (DualDPT) or depth + sky (mono),
|
||||||
|
// camera extrinsics/intrinsics, an optional back-projected 3D point cloud and
|
||||||
|
// glb/COLMAP exports. The src may be a filesystem path or a base64 payload.
|
||||||
|
func (r *DepthAnythingCpp) Depth(in *pb.DepthRequest) (pb.DepthResponse, error) {
|
||||||
|
// Accumulate into locals and return a single composite literal at the end:
|
||||||
|
// returning a named pb.DepthResponse value would copy its embedded mutex
|
||||||
|
// (go vet copylocks).
|
||||||
|
if r.handle == 0 {
|
||||||
|
return pb.DepthResponse{}, fmt.Errorf("depth-anything-cpp: model not loaded")
|
||||||
|
}
|
||||||
|
if in.GetSrc() == "" {
|
||||||
|
return pb.DepthResponse{}, fmt.Errorf("depth-anything-cpp: Depth requires src")
|
||||||
|
}
|
||||||
|
|
||||||
|
imgPath, cleanup, err := materializeImage(in.GetSrc())
|
||||||
|
if err != nil {
|
||||||
|
return pb.DepthResponse{}, fmt.Errorf("depth-anything-cpp: %w", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
// Dense per-pixel output + pose. Pass buffer pointers only for the
|
||||||
|
// requested maps so the native side can skip unrequested work; ext/intr
|
||||||
|
// must always point at 12/9 floats per the C ABI.
|
||||||
|
var (
|
||||||
|
h, w, isMetric int32
|
||||||
|
depthPtr, confPtr *float32
|
||||||
|
skyPtr *float32
|
||||||
|
ext [12]float32
|
||||||
|
intr [9]float32
|
||||||
|
pDepth, pConf, pSky **float32
|
||||||
|
)
|
||||||
|
if in.GetIncludeDepth() {
|
||||||
|
pDepth = &depthPtr
|
||||||
|
}
|
||||||
|
if in.GetIncludeConfidence() {
|
||||||
|
pConf = &confPtr
|
||||||
|
}
|
||||||
|
if in.GetIncludeSky() {
|
||||||
|
pSky = &skyPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
rc := CapiDepthDense(r.handle, imgPath, &h, &w, pDepth, pConf, pSky, &ext[0], &intr[0], &isMetric)
|
||||||
|
if rc != 0 {
|
||||||
|
return pb.DepthResponse{}, fmt.Errorf("depth-anything-cpp: da_capi_depth_dense failed (rc=%d): %s", rc, r.lastError())
|
||||||
|
}
|
||||||
|
|
||||||
|
n := int(h) * int(w)
|
||||||
|
var (
|
||||||
|
depth, conf, sky []float32
|
||||||
|
extrinsics, intrinsic []float32
|
||||||
|
numPoints int32
|
||||||
|
points []float32
|
||||||
|
pointColors []byte
|
||||||
|
exportPaths []string
|
||||||
|
)
|
||||||
|
|
||||||
|
if depthPtr != nil {
|
||||||
|
depth = copyFloats(depthPtr, n)
|
||||||
|
CapiFreeFloats(depthPtr)
|
||||||
|
}
|
||||||
|
if confPtr != nil {
|
||||||
|
conf = copyFloats(confPtr, n)
|
||||||
|
CapiFreeFloats(confPtr)
|
||||||
|
}
|
||||||
|
if skyPtr != nil {
|
||||||
|
sky = copyFloats(skyPtr, n)
|
||||||
|
CapiFreeFloats(skyPtr)
|
||||||
|
}
|
||||||
|
if in.GetIncludePose() {
|
||||||
|
extrinsics = append([]float32(nil), ext[:]...)
|
||||||
|
intrinsic = append([]float32(nil), intr[:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3D point cloud (DualDPT / pose-capable models only).
|
||||||
|
if in.GetIncludePoints() {
|
||||||
|
var (
|
||||||
|
np int32
|
||||||
|
xyzPtr *float32
|
||||||
|
rgbPtr *byte
|
||||||
|
)
|
||||||
|
if rc := CapiPoints(r.handle, imgPath, in.GetPointsConfThresh(), &np, &xyzPtr, &rgbPtr); rc != 0 {
|
||||||
|
return pb.DepthResponse{}, fmt.Errorf("depth-anything-cpp: da_capi_points failed (rc=%d): %s", rc, r.lastError())
|
||||||
|
}
|
||||||
|
numPoints = np
|
||||||
|
if xyzPtr != nil {
|
||||||
|
points = copyFloats(xyzPtr, int(np)*3)
|
||||||
|
CapiFreeFloats(xyzPtr)
|
||||||
|
}
|
||||||
|
if rgbPtr != nil {
|
||||||
|
pointColors = copyBytes(rgbPtr, int(np)*3)
|
||||||
|
CapiFreeBytes(rgbPtr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exports (glb / colmap). They are written under in.Dst (a directory); a
|
||||||
|
// temp dir is used when Dst is empty.
|
||||||
|
if len(in.GetExports()) > 0 {
|
||||||
|
exportPaths, err = r.runExports(imgPath, in.GetDst(), in.GetExports())
|
||||||
|
if err != nil {
|
||||||
|
return pb.DepthResponse{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pb.DepthResponse{
|
||||||
|
Width: w,
|
||||||
|
Height: h,
|
||||||
|
Depth: depth,
|
||||||
|
Confidence: conf,
|
||||||
|
Sky: sky,
|
||||||
|
Extrinsics: extrinsics,
|
||||||
|
Intrinsics: intrinsic,
|
||||||
|
NumPoints: numPoints,
|
||||||
|
Points: points,
|
||||||
|
PointColors: pointColors,
|
||||||
|
ExportPaths: exportPaths,
|
||||||
|
IsMetric: isMetric != 0,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runExports writes the requested exports for imgPath into dstDir and returns
|
||||||
|
// the written paths. Supported exports: "glb", "colmap".
|
||||||
|
func (r *DepthAnythingCpp) runExports(imgPath, dstDir string, exports []string) ([]string, error) {
|
||||||
|
if dstDir == "" {
|
||||||
|
tmp, err := os.MkdirTemp("", "depth-anything-export-*")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("depth-anything-cpp: mkdir export dir: %w", err)
|
||||||
|
}
|
||||||
|
dstDir = tmp
|
||||||
|
} else if err := os.MkdirAll(dstDir, 0o750); err != nil {
|
||||||
|
return nil, fmt.Errorf("depth-anything-cpp: mkdir %s: %w", dstDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var paths []string
|
||||||
|
for _, exp := range exports {
|
||||||
|
switch exp {
|
||||||
|
case "glb":
|
||||||
|
out := filepath.Join(dstDir, "pointcloud.glb")
|
||||||
|
if rc := CapiExportGlb(r.handle, imgPath, out); rc != 0 {
|
||||||
|
return nil, fmt.Errorf("depth-anything-cpp: da_capi_export_glb failed (rc=%d): %s", rc, r.lastError())
|
||||||
|
}
|
||||||
|
paths = append(paths, out)
|
||||||
|
case "colmap":
|
||||||
|
out := filepath.Join(dstDir, "colmap")
|
||||||
|
if err := os.MkdirAll(out, 0o750); err != nil {
|
||||||
|
return nil, fmt.Errorf("depth-anything-cpp: mkdir %s: %w", out, err)
|
||||||
|
}
|
||||||
|
if rc := CapiExportColmap(r.handle, imgPath, out, 1); rc != 0 {
|
||||||
|
return nil, fmt.Errorf("depth-anything-cpp: da_capi_export_colmap failed (rc=%d): %s", rc, r.lastError())
|
||||||
|
}
|
||||||
|
paths = append(paths, out)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("depth-anything-cpp: unknown export %q (want glb|colmap)", exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyFloats copies n float32 values from a C heap pointer into a fresh Go
|
||||||
|
// slice so the C buffer can be freed afterwards.
|
||||||
|
func copyFloats(p *float32, n int) []float32 {
|
||||||
|
if p == nil || n <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
src := unsafe.Slice(p, n)
|
||||||
|
out := make([]float32, n)
|
||||||
|
copy(out, src)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyBytes copies n bytes from a C heap pointer into a fresh Go slice.
|
||||||
|
func copyBytes(p *byte, n int) []byte {
|
||||||
|
if p == nil || n <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
src := unsafe.Slice(p, n)
|
||||||
|
out := make([]byte, n)
|
||||||
|
copy(out, src)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// runDepthPose runs depth estimation then pose recovery on an image file. It
|
||||||
|
// returns the row-major depth map (length h*w), its dimensions, the 3x4
|
||||||
|
// extrinsics (12 floats) and 3x3 intrinsics (9 floats).
|
||||||
|
// runDepthPose returns depth + camera pose via two C-API calls (depth then pose).
|
||||||
|
// For a nested metric model both calls run the full two-branch pipeline, so this
|
||||||
|
// path infers twice; the typed Depth RPC (single da_capi_depth_dense call) is the
|
||||||
|
// efficient path for nested models.
|
||||||
|
func (r *DepthAnythingCpp) runDepthPose(imagePath string) (depth []float32, h, w int, ext [12]float32, intr [9]float32, err error) {
|
||||||
|
if r.handle == 0 {
|
||||||
|
err = fmt.Errorf("depth-anything-cpp: model not loaded")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ch, cw int32
|
||||||
|
ptr := CapiDepthPath(r.handle, imagePath, &ch, &cw)
|
||||||
|
if ptr == nil {
|
||||||
|
err = fmt.Errorf("depth-anything-cpp: da_capi_depth_path failed: %s", r.lastError())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h, w = int(ch), int(cw)
|
||||||
|
n := h * w
|
||||||
|
if n > 0 {
|
||||||
|
src := unsafe.Slice(ptr, n)
|
||||||
|
depth = make([]float32, n)
|
||||||
|
copy(depth, src)
|
||||||
|
}
|
||||||
|
CapiFreeFloats(ptr)
|
||||||
|
|
||||||
|
if rc := CapiPosePath(r.handle, imagePath, &ext[0], &intr[0]); rc != 0 {
|
||||||
|
err = fmt.Errorf("depth-anything-cpp: da_capi_pose_path failed (rc=%d): %s", rc, r.lastError())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// lastError returns the context's last error string, or "" if none.
|
||||||
|
func (r *DepthAnythingCpp) lastError() string {
|
||||||
|
if CapiLastError == nil || r.handle == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return CapiLastError(r.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// materializeImage returns a filesystem path for an image argument that may be
|
||||||
|
// either an existing path or a base64-encoded payload. When the input is
|
||||||
|
// base64 it is decoded into a temp file; cleanup removes it (no-op for a path).
|
||||||
|
func materializeImage(arg string) (path string, cleanup func(), err error) {
|
||||||
|
cleanup = func() {}
|
||||||
|
if _, statErr := os.Stat(arg); statErr == nil {
|
||||||
|
return arg, cleanup, nil
|
||||||
|
}
|
||||||
|
// Strip an optional data URL prefix (data:image/...;base64,<payload>).
|
||||||
|
b64 := arg
|
||||||
|
if i := indexComma(b64); i >= 0 && hasDataPrefix(b64) {
|
||||||
|
b64 = b64[i+1:]
|
||||||
|
}
|
||||||
|
data, decErr := base64.StdEncoding.DecodeString(b64)
|
||||||
|
if decErr != nil {
|
||||||
|
return "", cleanup, fmt.Errorf("image is neither an existing path nor valid base64: %v", decErr)
|
||||||
|
}
|
||||||
|
f, tErr := os.CreateTemp("", "depth-anything-*.img")
|
||||||
|
if tErr != nil {
|
||||||
|
return "", cleanup, tErr
|
||||||
|
}
|
||||||
|
if _, wErr := f.Write(data); wErr != nil {
|
||||||
|
_ = f.Close()
|
||||||
|
_ = os.Remove(f.Name())
|
||||||
|
return "", cleanup, wErr
|
||||||
|
}
|
||||||
|
_ = f.Close()
|
||||||
|
name := f.Name()
|
||||||
|
return name, func() { _ = os.Remove(name) }, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasDataPrefix(s string) bool {
|
||||||
|
return len(s) >= 5 && s[:5] == "data:"
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexComma(s string) int {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] == ',' {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeDepthPNG min-max normalises a depth map and writes it as an 8-bit
|
||||||
|
// grayscale PNG. Near = bright (255), far = dark (0), matching the usual
|
||||||
|
// depth-map convention for inverse-depth-like outputs.
|
||||||
|
func writeDepthPNG(dst string, depth []float32, h, w int) error {
|
||||||
|
if h <= 0 || w <= 0 || len(depth) < h*w {
|
||||||
|
return fmt.Errorf("depth-anything-cpp: writeDepthPNG: bad dims h=%d w=%d len=%d", h, w, len(depth))
|
||||||
|
}
|
||||||
|
dmin, dmax := minMax(depth)
|
||||||
|
span := dmax - dmin
|
||||||
|
if span <= 0 || math.IsNaN(float64(span)) {
|
||||||
|
span = 1
|
||||||
|
}
|
||||||
|
img := image.NewGray(image.Rect(0, 0, w, h))
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
v := depth[y*w+x]
|
||||||
|
n := (v - dmin) / span // 0..1
|
||||||
|
if math.IsNaN(float64(n)) {
|
||||||
|
n = 0
|
||||||
|
}
|
||||||
|
if n < 0 {
|
||||||
|
n = 0
|
||||||
|
} else if n > 1 {
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
img.Pix[y*img.Stride+x] = uint8(n * 255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// dst is the gRPC-provided output path chosen by the LocalAI core (the
|
||||||
|
// intended write destination for the rendered depth map), not
|
||||||
|
// attacker-controlled input, so the variable path is expected here.
|
||||||
|
f, err := os.Create(dst) // #nosec G304
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
return png.Encode(f, img)
|
||||||
|
}
|
||||||
|
|
||||||
|
func minMax(v []float32) (mn, mx float32) {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
mn, mx = v[0], v[0]
|
||||||
|
for _, x := range v {
|
||||||
|
if math.IsNaN(float64(x)) || math.IsInf(float64(x), 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if x < mn {
|
||||||
|
mn = x
|
||||||
|
}
|
||||||
|
if x > mx {
|
||||||
|
mx = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mn, mx
|
||||||
|
}
|
||||||
67
backend/go/depth-anything-cpp/main.go
Normal file
67
backend/go/depth-anything-cpp/main.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// main.go - entry point for the depth-anything-cpp gRPC backend.
|
||||||
|
//
|
||||||
|
// Dlopens libdepthanythingcpp-<variant>.so via purego at the path in
|
||||||
|
// DEPTHANYTHING_LIBRARY (set by run.sh based on /proc/cpuinfo), registers the
|
||||||
|
// da_capi_* C ABI symbols, then starts the gRPC server.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/ebitengine/purego"
|
||||||
|
grpc "github.com/mudler/LocalAI/pkg/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||||
|
)
|
||||||
|
|
||||||
|
type LibFuncs struct {
|
||||||
|
FuncPtr any
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Get library name from environment variable, default to fallback
|
||||||
|
libName := os.Getenv("DEPTHANYTHING_LIBRARY")
|
||||||
|
if libName == "" {
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
libName = "./libdepthanythingcpp-fallback.dylib"
|
||||||
|
} else {
|
||||||
|
libName = "./libdepthanythingcpp-fallback.so"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lib, err := purego.Dlopen(libName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
libFuncs := []LibFuncs{
|
||||||
|
{&CapiLoad, "da_capi_load"},
|
||||||
|
{&CapiLoadNested, "da_capi_load_nested"},
|
||||||
|
{&CapiFree, "da_capi_free"},
|
||||||
|
{&CapiLastError, "da_capi_last_error"},
|
||||||
|
{&CapiDepthPath, "da_capi_depth_path"},
|
||||||
|
{&CapiFreeFloats, "da_capi_free_floats"},
|
||||||
|
{&CapiPosePath, "da_capi_pose_path"},
|
||||||
|
{&CapiDepthDense, "da_capi_depth_dense"},
|
||||||
|
{&CapiPoints, "da_capi_points"},
|
||||||
|
{&CapiFreeBytes, "da_capi_free_bytes"},
|
||||||
|
{&CapiExportGlb, "da_capi_export_glb"},
|
||||||
|
{&CapiExportColmap, "da_capi_export_colmap"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lf := range libFuncs {
|
||||||
|
purego.RegisterLibFunc(lf.FuncPtr, lib, lf.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if err := grpc.StartServer(*addr, &DepthAnythingCpp{}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
167
backend/go/depth-anything-cpp/main_test.go
Normal file
167
backend/go/depth-anything-cpp/main_test.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// main_test.go - end-to-end smoke test for the depth-anything-cpp gRPC backend.
|
||||||
|
//
|
||||||
|
// Spawns the compiled depth-anything-cpp binary on a free local port, dials it
|
||||||
|
// via gRPC, and exercises LoadModel + Predict against the test fixtures
|
||||||
|
// downloaded by test.sh: the small (vits) f32 GGUF of Depth Anything 3 and a
|
||||||
|
// real photo. Asserts that Predict returns a JSON payload with a positive
|
||||||
|
// depth-map width/height.
|
||||||
|
//
|
||||||
|
// The spec Skip()s cleanly if its fixtures (the model, the test image, the
|
||||||
|
// built binary, or the fallback .so) are missing, so the test target stays
|
||||||
|
// usable on a fresh checkout / on CI runners where the model hasn't been
|
||||||
|
// downloaded.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDepth(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "depth-anything-cpp backend smoke suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
// freePort grabs an ephemeral TCP port and immediately releases it so the
|
||||||
|
// spawned backend can bind to it. There is a tiny TOCTOU window here but in
|
||||||
|
// practice it's adequate for a smoke test on a quiet runner.
|
||||||
|
func freePort() int {
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "freePort listen")
|
||||||
|
port := l.Addr().(*net.TCPAddr).Port
|
||||||
|
Expect(l.Close()).To(Succeed())
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
// startBackend spawns the depth-anything-cpp binary on the given port and waits
|
||||||
|
// until it accepts TCP connections (up to 10s). It mirrors how main.go resolves
|
||||||
|
// the purego library: the DEPTHANYTHING_LIBRARY env var points the dlopen at the
|
||||||
|
// freshly built fallback .so. The returned cleanup func kills the process.
|
||||||
|
func startBackend(port int) func() {
|
||||||
|
binary, err := filepath.Abs("./depth-anything-cpp")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
if _, err := os.Stat(binary); err != nil {
|
||||||
|
Skip(fmt.Sprintf("backend binary not built: %s (run `make depth-anything-cpp` first)", binary))
|
||||||
|
}
|
||||||
|
|
||||||
|
libPath, err := filepath.Abs("./libdepthanythingcpp-fallback.so")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
if _, err := os.Stat(libPath); err != nil {
|
||||||
|
Skip(fmt.Sprintf("fallback library not built: %s (run `make libdepthanythingcpp-fallback.so` first)", libPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||||
|
cmd := exec.Command(binary, "--addr", addr)
|
||||||
|
cmd.Env = append(os.Environ(), "DEPTHANYTHING_LIBRARY="+libPath)
|
||||||
|
cmd.Stdout = os.Stderr
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
Expect(cmd.Start()).To(Succeed())
|
||||||
|
|
||||||
|
cleanup := func() {
|
||||||
|
if cmd.Process != nil {
|
||||||
|
_ = cmd.Process.Kill()
|
||||||
|
_, _ = cmd.Process.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deadline := time.Now().Add(10 * time.Second)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
c, err := net.DialTimeout("tcp", addr, 200*time.Millisecond)
|
||||||
|
if err == nil {
|
||||||
|
_ = c.Close()
|
||||||
|
return cleanup
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
Fail(fmt.Sprintf("backend did not become ready on %s within 10s", addr))
|
||||||
|
return func() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTestImage reads the test image downloaded by test.sh and returns its
|
||||||
|
// base64-encoded content (one of the wire formats accepted by Predict).
|
||||||
|
func loadTestImage() string {
|
||||||
|
imgPath, err := filepath.Abs("test-data/test.jpg")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
imgBytes, err := os.ReadFile(imgPath)
|
||||||
|
if err != nil {
|
||||||
|
Skip(fmt.Sprintf("test image not present: %s (run test.sh first)", imgPath))
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(imgBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialBackend opens a gRPC client connection to the spawned backend.
|
||||||
|
func dialBackend(port int) (pb.BackendClient, func()) {
|
||||||
|
addr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||||
|
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
return pb.NewBackendClient(conn), func() { _ = conn.Close() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// modelPathOrSkip resolves the model file under ./test-models/ and Skip()s the
|
||||||
|
// current spec if it's missing (not present on a fresh checkout / on CI runners
|
||||||
|
// without the download).
|
||||||
|
func modelPathOrSkip(name string) string {
|
||||||
|
modelDir, err := filepath.Abs("test-models")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
modelPath := filepath.Join(modelDir, name)
|
||||||
|
if _, err := os.Stat(modelPath); err != nil {
|
||||||
|
Skip(fmt.Sprintf("model not present: %s (run test.sh first)", modelPath))
|
||||||
|
}
|
||||||
|
return modelPath
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("depth-anything-cpp backend", func() {
|
||||||
|
It("runs depth+pose against a known-good image", func() {
|
||||||
|
modelPath := modelPathOrSkip("depth-anything-small-f32.gguf")
|
||||||
|
imgB64 := loadTestImage()
|
||||||
|
|
||||||
|
port := freePort()
|
||||||
|
cleanup := startBackend(port)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
client, closeConn := dialBackend(port)
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
loadResp, err := client.LoadModel(ctx, &pb.ModelOptions{
|
||||||
|
Model: "depth-anything-small-f32.gguf",
|
||||||
|
ModelFile: modelPath,
|
||||||
|
Threads: 4,
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "LoadModel")
|
||||||
|
Expect(loadResp.GetSuccess()).To(BeTrue(), "LoadModel reported failure: %s", loadResp.GetMessage())
|
||||||
|
|
||||||
|
// Predict runs depth+pose and returns the JSON depthResult in Reply.Message.
|
||||||
|
reply, err := client.Predict(ctx, &pb.PredictOptions{
|
||||||
|
Images: []string{imgB64},
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "Predict")
|
||||||
|
|
||||||
|
var res depthResult
|
||||||
|
Expect(json.Unmarshal(reply.GetMessage(), &res)).To(Succeed(), "Predict returned non-JSON: %q", string(reply.GetMessage()))
|
||||||
|
Expect(res.DepthW).To(BeNumerically(">", 0), "depth width should be positive")
|
||||||
|
Expect(res.DepthH).To(BeNumerically(">", 0), "depth height should be positive")
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(GinkgoWriter, "depth OK: %dx%d min=%.3f max=%.3f\n",
|
||||||
|
res.DepthW, res.DepthH, res.DepthMin, res.DepthMax)
|
||||||
|
})
|
||||||
|
})
|
||||||
64
backend/go/depth-anything-cpp/nested_e2e_test.go
Normal file
64
backend/go/depth-anything-cpp/nested_e2e_test.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// nested_e2e_test.go - e2e smoke for the nested two-file metric model. Loads the
|
||||||
|
// anyview branch as the main model and points the metric branch via the
|
||||||
|
// "metric_model:<file>" option (exactly as the depth-anything-3-nested gallery
|
||||||
|
// entry does), then exercises the typed Depth RPC and asserts a metric depth map.
|
||||||
|
//
|
||||||
|
// Skips cleanly unless both nested GGUFs are present under ./test-models/ and the
|
||||||
|
// backend binary + fallback .so are built.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("depth-anything-cpp nested metric model", func() {
|
||||||
|
It("loads the two-file pair via the metric_model option and returns metric depth", func() {
|
||||||
|
anyviewPath := modelPathOrSkip("depth-anything-nested-anyview.gguf")
|
||||||
|
_ = modelPathOrSkip("depth-anything-nested-metric.gguf")
|
||||||
|
imgB64 := loadTestImage()
|
||||||
|
|
||||||
|
port := freePort()
|
||||||
|
cleanup := startBackend(port)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
client, closeConn := dialBackend(port)
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
loadResp, err := client.LoadModel(ctx, &pb.ModelOptions{
|
||||||
|
Model: "depth-anything-nested-anyview.gguf",
|
||||||
|
ModelFile: anyviewPath,
|
||||||
|
ModelPath: filepath.Dir(anyviewPath),
|
||||||
|
Options: []string{"metric_model:depth-anything-nested-metric.gguf"},
|
||||||
|
Threads: 8,
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "LoadModel(nested)")
|
||||||
|
Expect(loadResp.GetSuccess()).To(BeTrue(), "LoadModel reported failure: %s", loadResp.GetMessage())
|
||||||
|
|
||||||
|
resp, err := client.Depth(ctx, &pb.DepthRequest{
|
||||||
|
Src: imgB64,
|
||||||
|
IncludeDepth: true,
|
||||||
|
IncludePose: true,
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "Depth(nested)")
|
||||||
|
Expect(resp.GetWidth()).To(BeNumerically(">", 0), "depth width")
|
||||||
|
Expect(resp.GetHeight()).To(BeNumerically(">", 0), "depth height")
|
||||||
|
Expect(resp.GetIsMetric()).To(BeTrue(), "nested output must be metric")
|
||||||
|
Expect(len(resp.GetDepth())).To(Equal(int(resp.GetWidth())*int(resp.GetHeight())), "dense depth length")
|
||||||
|
Expect(len(resp.GetExtrinsics())).To(Equal(12), "extrinsics 3x4")
|
||||||
|
Expect(resp.GetIntrinsics()[0]).To(BeNumerically(">", 0), "fx > 0")
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(GinkgoWriter, "nested depth OK: %dx%d is_metric=%v fx=%.2f\n",
|
||||||
|
resp.GetWidth(), resp.GetHeight(), resp.GetIsMetric(), resp.GetIntrinsics()[0])
|
||||||
|
})
|
||||||
|
})
|
||||||
20
backend/go/depth-anything-cpp/options_test.go
Normal file
20
backend/go/depth-anything-cpp/options_test.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = DescribeTable("optionValue",
|
||||||
|
func(opts []string, key, want string) {
|
||||||
|
Expect(optionValue(opts, key)).To(Equal(want))
|
||||||
|
},
|
||||||
|
Entry("present", []string{"foo:bar", "metric_model:m.gguf"}, "metric_model", "m.gguf"),
|
||||||
|
Entry("absent", []string{"foo:bar"}, "metric_model", ""),
|
||||||
|
Entry("nil", []string(nil), "metric_model", ""),
|
||||||
|
Entry("trims space", []string{"metric_model: m.gguf "}, "metric_model", "m.gguf"),
|
||||||
|
Entry("value with colon", []string{"metric_model:a:b.gguf"}, "metric_model", "a:b.gguf"),
|
||||||
|
Entry("first wins", []string{"metric_model:first.gguf", "metric_model:second.gguf"}, "metric_model", "first.gguf"),
|
||||||
|
Entry("empty value", []string{"metric_model:"}, "metric_model", ""),
|
||||||
|
Entry("prefix not key", []string{"metric_model_extra:x"}, "metric_model", ""),
|
||||||
|
)
|
||||||
60
backend/go/depth-anything-cpp/package.sh
Executable file
60
backend/go/depth-anything-cpp/package.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script to copy the appropriate libraries based on architecture
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CURDIR=$(dirname "$(realpath $0)")
|
||||||
|
REPO_ROOT="${CURDIR}/../../.."
|
||||||
|
|
||||||
|
# Create lib directory
|
||||||
|
mkdir -p $CURDIR/package/lib
|
||||||
|
|
||||||
|
cp -fv $CURDIR/libdepthanythingcpp-*.so $CURDIR/package/ 2>/dev/null || true
|
||||||
|
cp -fv $CURDIR/libdepthanythingcpp-*.dylib $CURDIR/package/ 2>/dev/null || true
|
||||||
|
cp -avf $CURDIR/depth-anything-cpp $CURDIR/package/
|
||||||
|
cp -fv $CURDIR/run.sh $CURDIR/package/
|
||||||
|
|
||||||
|
# Detect architecture and copy appropriate libraries
|
||||||
|
if [ -f "/lib64/ld-linux-x86-64.so.2" ]; then
|
||||||
|
# x86_64 architecture
|
||||||
|
echo "Detected x86_64 architecture, copying x86_64 libraries..."
|
||||||
|
cp -arfLv /lib64/ld-linux-x86-64.so.2 $CURDIR/package/lib/ld.so
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libc.so.6 $CURDIR/package/lib/libc.so.6
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libgcc_s.so.1 $CURDIR/package/lib/libgcc_s.so.1
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libstdc++.so.6 $CURDIR/package/lib/libstdc++.so.6
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libm.so.6 $CURDIR/package/lib/libm.so.6
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libgomp.so.1 $CURDIR/package/lib/libgomp.so.1
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libdl.so.2 $CURDIR/package/lib/libdl.so.2
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/librt.so.1 $CURDIR/package/lib/librt.so.1
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libpthread.so.0 $CURDIR/package/lib/libpthread.so.0
|
||||||
|
elif [ -f "/lib/ld-linux-aarch64.so.1" ]; then
|
||||||
|
# ARM64 architecture
|
||||||
|
echo "Detected ARM64 architecture, copying ARM64 libraries..."
|
||||||
|
cp -arfLv /lib/ld-linux-aarch64.so.1 $CURDIR/package/lib/ld.so
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libc.so.6 $CURDIR/package/lib/libc.so.6
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libgcc_s.so.1 $CURDIR/package/lib/libgcc_s.so.1
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libstdc++.so.6 $CURDIR/package/lib/libstdc++.so.6
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libm.so.6 $CURDIR/package/lib/libm.so.6
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libgomp.so.1 $CURDIR/package/lib/libgomp.so.1
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libdl.so.2 $CURDIR/package/lib/libdl.so.2
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/librt.so.1 $CURDIR/package/lib/librt.so.1
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libpthread.so.0 $CURDIR/package/lib/libpthread.so.0
|
||||||
|
elif [ $(uname -s) = "Darwin" ]; then
|
||||||
|
echo "Detected Darwin"
|
||||||
|
else
|
||||||
|
echo "Error: Could not detect architecture"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Package GPU libraries based on BUILD_TYPE
|
||||||
|
GPU_LIB_SCRIPT="${REPO_ROOT}/scripts/build/package-gpu-libs.sh"
|
||||||
|
if [ -f "$GPU_LIB_SCRIPT" ]; then
|
||||||
|
echo "Packaging GPU libraries for BUILD_TYPE=${BUILD_TYPE:-cpu}..."
|
||||||
|
source "$GPU_LIB_SCRIPT" "$CURDIR/package/lib"
|
||||||
|
package_gpu_libs
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Packaging completed successfully"
|
||||||
|
ls -liah $CURDIR/package/
|
||||||
|
ls -liah $CURDIR/package/lib/
|
||||||
57
backend/go/depth-anything-cpp/run.sh
Executable file
57
backend/go/depth-anything-cpp/run.sh
Executable file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
# Get the absolute current dir where the script is located
|
||||||
|
CURDIR=$(dirname "$(realpath $0)")
|
||||||
|
|
||||||
|
cd /
|
||||||
|
|
||||||
|
echo "CPU info:"
|
||||||
|
if [ "$(uname)" != "Darwin" ]; then
|
||||||
|
grep -e "model\sname" /proc/cpuinfo | head -1
|
||||||
|
grep -e "flags" /proc/cpuinfo | head -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
|
# macOS: single dylib variant (Metal or Accelerate)
|
||||||
|
LIBRARY="$CURDIR/libdepthanythingcpp-fallback.dylib"
|
||||||
|
export DYLD_LIBRARY_PATH=$CURDIR/lib:$DYLD_LIBRARY_PATH
|
||||||
|
else
|
||||||
|
LIBRARY="$CURDIR/libdepthanythingcpp-fallback.so"
|
||||||
|
|
||||||
|
if grep -q -e "\savx\s" /proc/cpuinfo ; then
|
||||||
|
echo "CPU: AVX found OK"
|
||||||
|
if [ -e $CURDIR/libdepthanythingcpp-avx.so ]; then
|
||||||
|
LIBRARY="$CURDIR/libdepthanythingcpp-avx.so"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q -e "\savx2\s" /proc/cpuinfo ; then
|
||||||
|
echo "CPU: AVX2 found OK"
|
||||||
|
if [ -e $CURDIR/libdepthanythingcpp-avx2.so ]; then
|
||||||
|
LIBRARY="$CURDIR/libdepthanythingcpp-avx2.so"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check avx 512
|
||||||
|
if grep -q -e "\savx512f\s" /proc/cpuinfo ; then
|
||||||
|
echo "CPU: AVX512F found OK"
|
||||||
|
if [ -e $CURDIR/libdepthanythingcpp-avx512.so ]; then
|
||||||
|
LIBRARY="$CURDIR/libdepthanythingcpp-avx512.so"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH=$CURDIR/lib:$LD_LIBRARY_PATH
|
||||||
|
fi
|
||||||
|
|
||||||
|
export DEPTHANYTHING_LIBRARY=$LIBRARY
|
||||||
|
|
||||||
|
# If there is a lib/ld.so, use it
|
||||||
|
if [ -f $CURDIR/lib/ld.so ]; then
|
||||||
|
echo "Using lib/ld.so"
|
||||||
|
echo "Using library: $LIBRARY"
|
||||||
|
exec $CURDIR/lib/ld.so $CURDIR/depth-anything-cpp "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Using library: $LIBRARY"
|
||||||
|
exec $CURDIR/depth-anything-cpp "$@"
|
||||||
45
backend/go/depth-anything-cpp/test.sh
Executable file
45
backend/go/depth-anything-cpp/test.sh
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CURDIR=$(dirname "$(realpath $0)")
|
||||||
|
|
||||||
|
echo "Running depth-anything-cpp backend tests..."
|
||||||
|
|
||||||
|
# Test model from the mudler/depth-anything.cpp-gguf HuggingFace repo. The small
|
||||||
|
# (vits) f32 GGUF is the lightest backbone (~131 MB), so it keeps the download
|
||||||
|
# cheap. It is resumed with `curl -C -` and skipped entirely if already present.
|
||||||
|
DEPTHANYTHING_MODEL_DIR="${DEPTHANYTHING_MODEL_DIR:-$CURDIR/test-models}"
|
||||||
|
|
||||||
|
DEPTHANYTHING_MODEL_FILE="${DEPTHANYTHING_MODEL_FILE:-depth-anything-small-f32.gguf}"
|
||||||
|
DEPTHANYTHING_MODEL_URL="${DEPTHANYTHING_MODEL_URL:-https://huggingface.co/mudler/depth-anything.cpp-gguf/resolve/main/depth-anything-small-f32.gguf}"
|
||||||
|
|
||||||
|
mkdir -p "$DEPTHANYTHING_MODEL_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "$DEPTHANYTHING_MODEL_DIR/$DEPTHANYTHING_MODEL_FILE" ]; then
|
||||||
|
echo "Downloading depth-anything small f32 model (~131 MB)..."
|
||||||
|
# -C - resumes a partial download so an interrupted run doesn't restart from 0.
|
||||||
|
curl -L -C - -o "$DEPTHANYTHING_MODEL_DIR/$DEPTHANYTHING_MODEL_FILE" "$DEPTHANYTHING_MODEL_URL" --progress-bar
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use a real photo (people + cars) from the upstream rf-detr.cpp repo (~46 KB).
|
||||||
|
# Depth estimation needs real content; a synthetic image would be degenerate.
|
||||||
|
TEST_IMAGE_DIR="$CURDIR/test-data"
|
||||||
|
TEST_IMAGE_FILE="$TEST_IMAGE_DIR/test.jpg"
|
||||||
|
TEST_IMAGE_URL="${TEST_IMAGE_URL:-https://raw.githubusercontent.com/mudler/rf-detr.cpp/main/tests/fixtures/ci/test_image.jpg}"
|
||||||
|
|
||||||
|
mkdir -p "$TEST_IMAGE_DIR"
|
||||||
|
if [ ! -f "$TEST_IMAGE_FILE" ]; then
|
||||||
|
echo "Downloading test image..."
|
||||||
|
curl -L -o "$TEST_IMAGE_FILE" "$TEST_IMAGE_URL" --progress-bar
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "depth-anything-cpp test setup complete."
|
||||||
|
echo " model: $DEPTHANYTHING_MODEL_DIR/$DEPTHANYTHING_MODEL_FILE"
|
||||||
|
echo " test image: $TEST_IMAGE_FILE"
|
||||||
|
|
||||||
|
# Run the Go smoke test: spawns the backend binary on a free port, calls
|
||||||
|
# LoadModel + Predict via gRPC against the downloaded GGUF + image.
|
||||||
|
echo ""
|
||||||
|
echo "Running Go smoke test..."
|
||||||
|
cd "$CURDIR"
|
||||||
|
go test -v -timeout 30m ./...
|
||||||
@@ -67,8 +67,9 @@ $(LIB_SENTINEL): sources/LocalVQE
|
|||||||
# that the loader picks at runtime. We must build every target — the
|
# that the loader picks at runtime. We must build every target — the
|
||||||
# default `--target localvqe_shared` drops these. CMAKE_LIBRARY_OUTPUT_DIRECTORY
|
# default `--target localvqe_shared` drops these. CMAKE_LIBRARY_OUTPUT_DIRECTORY
|
||||||
# routes all of them into build/bin; copy them out next to the binary.
|
# routes all of them into build/bin; copy them out next to the binary.
|
||||||
cp -P build/bin/liblocalvqe.so* . 2>/dev/null || cp -P build/liblocalvqe.so* .
|
cp -P build/bin/liblocalvqe.so* . 2>/dev/null || cp -P build/bin/liblocalvqe.dylib . 2>/dev/null || cp -P build/liblocalvqe.so* . 2>/dev/null || cp -P build/liblocalvqe.dylib .
|
||||||
cp -P build/bin/libggml*.so* . 2>/dev/null || true
|
cp -P build/bin/libggml*.so* . 2>/dev/null || true
|
||||||
|
cp -P build/bin/libggml*.dylib . 2>/dev/null || true
|
||||||
touch $(LIB_SENTINEL)
|
touch $(LIB_SENTINEL)
|
||||||
|
|
||||||
liblocalvqe.so: $(LIB_SENTINEL)
|
liblocalvqe.so: $(LIB_SENTINEL)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/ebitengine/purego"
|
"github.com/ebitengine/purego"
|
||||||
grpc "github.com/mudler/LocalAI/pkg/grpc"
|
grpc "github.com/mudler/LocalAI/pkg/grpc"
|
||||||
@@ -21,7 +22,11 @@ type LibFuncs struct {
|
|||||||
func main() {
|
func main() {
|
||||||
libName := os.Getenv("LOCALVQE_LIBRARY")
|
libName := os.Getenv("LOCALVQE_LIBRARY")
|
||||||
if libName == "" {
|
if libName == "" {
|
||||||
libName = "./liblocalvqe.so"
|
if runtime.GOOS == "darwin" {
|
||||||
|
libName = "./liblocalvqe.dylib"
|
||||||
|
} else {
|
||||||
|
libName = "./liblocalvqe.so"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lib, err := purego.Dlopen(libName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
lib, err := purego.Dlopen(libName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ cp -avf $CURDIR/localvqe $CURDIR/package/
|
|||||||
# liblocalvqe.so* (with SOVERSION symlinks) and the libggml-*.so runtime
|
# liblocalvqe.so* (with SOVERSION symlinks) and the libggml-*.so runtime
|
||||||
# variants — LocalVQE picks the matching CPU variant at load time.
|
# variants — LocalVQE picks the matching CPU variant at load time.
|
||||||
cp -P $CURDIR/liblocalvqe.so* $CURDIR/package/ 2>/dev/null || true
|
cp -P $CURDIR/liblocalvqe.so* $CURDIR/package/ 2>/dev/null || true
|
||||||
|
cp -P $CURDIR/liblocalvqe.dylib $CURDIR/package/ 2>/dev/null || true
|
||||||
cp -P $CURDIR/libggml*.so* $CURDIR/package/ 2>/dev/null || true
|
cp -P $CURDIR/libggml*.so* $CURDIR/package/ 2>/dev/null || true
|
||||||
|
cp -P $CURDIR/libggml*.dylib $CURDIR/package/ 2>/dev/null || true
|
||||||
cp -fv $CURDIR/run.sh $CURDIR/package/
|
cp -fv $CURDIR/run.sh $CURDIR/package/
|
||||||
|
|
||||||
# Detect architecture and copy appropriate libraries
|
# Detect architecture and copy appropriate libraries
|
||||||
|
|||||||
@@ -10,8 +10,19 @@ CURDIR=$(dirname "$(realpath $0)")
|
|||||||
# exec'ing the binary.
|
# exec'ing the binary.
|
||||||
cd "$CURDIR"
|
cd "$CURDIR"
|
||||||
|
|
||||||
export LD_LIBRARY_PATH=$CURDIR:$CURDIR/lib:$LD_LIBRARY_PATH
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
export LOCALVQE_LIBRARY=$CURDIR/liblocalvqe.so
|
# macOS: LocalVQE is built as a SHARED library, so dyld needs the .dylib +
|
||||||
|
# DYLD_LIBRARY_PATH. Prefer .dylib and fall back to .so just in case.
|
||||||
|
export DYLD_LIBRARY_PATH=$CURDIR:$CURDIR/lib:$DYLD_LIBRARY_PATH
|
||||||
|
LOCALVQE_LIBRARY=$CURDIR/liblocalvqe.dylib
|
||||||
|
if [ ! -e "$LOCALVQE_LIBRARY" ]; then
|
||||||
|
LOCALVQE_LIBRARY=$CURDIR/liblocalvqe.so
|
||||||
|
fi
|
||||||
|
export LOCALVQE_LIBRARY
|
||||||
|
else
|
||||||
|
export LD_LIBRARY_PATH=$CURDIR:$CURDIR/lib:$LD_LIBRARY_PATH
|
||||||
|
export LOCALVQE_LIBRARY=$CURDIR/liblocalvqe.so
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -f $CURDIR/lib/ld.so ]; then
|
if [ -f $CURDIR/lib/ld.so ]; then
|
||||||
echo "Using lib/ld.so"
|
echo "Using lib/ld.so"
|
||||||
|
|||||||
7
backend/go/locate-anything-cpp/.gitignore
vendored
Normal file
7
backend/go/locate-anything-cpp/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
sources/
|
||||||
|
build*/
|
||||||
|
package/
|
||||||
|
liblocateanythingcpp*.so
|
||||||
|
locate-anything-cpp
|
||||||
|
test-models/
|
||||||
|
test-data/
|
||||||
57
backend/go/locate-anything-cpp/CMakeLists.txt
Normal file
57
backend/go/locate-anything-cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.18)
|
||||||
|
project(liblocateanythingcpp LANGUAGES C CXX)
|
||||||
|
|
||||||
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# Static-link ggml + locate_anything so the resulting .so has no runtime
|
||||||
|
# dependency on extra ggml/locate_anything shared libraries — only on
|
||||||
|
# libc/libstdc++/libgomp, which the LocalAI package step bundles into the
|
||||||
|
# docker image.
|
||||||
|
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static libraries" FORCE)
|
||||||
|
|
||||||
|
# locate-anything.cpp build switches: skip CLI/tests, keep static lib.
|
||||||
|
set(LA_BUILD_CLI OFF CACHE BOOL "Disable locate-anything CLI" FORCE)
|
||||||
|
set(LA_BUILD_TESTS OFF CACHE BOOL "Disable locate-anything tests" FORCE)
|
||||||
|
set(LA_SHARED OFF CACHE BOOL "Build locate_anything as static lib" FORCE)
|
||||||
|
|
||||||
|
# Unlike rt-detr.cpp, locate-anything.cpp ships no in-tree ggml patches, so
|
||||||
|
# there is no apply_ggml_patches.sh hook to shim here.
|
||||||
|
add_subdirectory(./sources/locate-anything.cpp)
|
||||||
|
|
||||||
|
# locate-anything.cpp's top-level CMakeLists points its own target's include
|
||||||
|
# dirs at ${CMAKE_SOURCE_DIR}/{include,src,third_party,...}. CMAKE_SOURCE_DIR
|
||||||
|
# is the *top-level* source dir of the whole CMake tree, so when we pull it in
|
||||||
|
# via add_subdirectory it resolves to OUR directory, not theirs, and the
|
||||||
|
# locate_anything target fails to find its own headers (la_capi.h, stb_image.h,
|
||||||
|
# la_gguf_keys.h). Re-add the correct, subdir-relative include paths to the
|
||||||
|
# already-defined target so it compiles regardless of where it's nested.
|
||||||
|
set(LA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sources/locate-anything.cpp)
|
||||||
|
target_include_directories(locate_anything PRIVATE
|
||||||
|
${LA_SRC}/include
|
||||||
|
${LA_SRC}/src
|
||||||
|
${LA_SRC}/third_party
|
||||||
|
${LA_SRC}/third_party/stb)
|
||||||
|
|
||||||
|
# locate-anything.cpp's C-API symbols already live inside liblocate_anything
|
||||||
|
# (src/la_capi.cpp is compiled into the lib). We re-export them via a MODULE
|
||||||
|
# library that links locate_anything so the symbols are visible at dlopen time.
|
||||||
|
add_library(locateanythingcpp MODULE
|
||||||
|
sources/locate-anything.cpp/src/la_capi.cpp)
|
||||||
|
|
||||||
|
target_include_directories(locateanythingcpp PRIVATE
|
||||||
|
sources/locate-anything.cpp/include
|
||||||
|
sources/locate-anything.cpp/src
|
||||||
|
sources/locate-anything.cpp/third_party
|
||||||
|
sources/locate-anything.cpp/third_party/stb
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(locateanythingcpp PRIVATE locate_anything ggml)
|
||||||
|
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0)
|
||||||
|
target_link_libraries(locateanythingcpp PRIVATE stdc++fs)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set_property(TARGET locateanythingcpp PROPERTY CXX_STANDARD 17)
|
||||||
|
set_target_properties(locateanythingcpp PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
143
backend/go/locate-anything-cpp/Makefile
Normal file
143
backend/go/locate-anything-cpp/Makefile
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
CMAKE_ARGS?=
|
||||||
|
BUILD_TYPE?=
|
||||||
|
NATIVE?=false
|
||||||
|
|
||||||
|
GOCMD?=go
|
||||||
|
GO_TAGS?=
|
||||||
|
JOBS?=$(shell nproc --ignore=1)
|
||||||
|
|
||||||
|
# locate-anything.cpp. Pin to a specific commit for a stable build; leaving
|
||||||
|
# this on `master` always picks up the latest C-API surface (incl. the
|
||||||
|
# per-detection accessor functions used by golocateanythingcpp.go).
|
||||||
|
LOCATEANYTHING_REPO?=https://github.com/mudler/locate-anything.cpp.git
|
||||||
|
LOCATEANYTHING_VERSION?=92c1682da792c1e8a5dec91acc2be4b02c742ded
|
||||||
|
|
||||||
|
ifeq ($(NATIVE),false)
|
||||||
|
CMAKE_ARGS+=-DGGML_NATIVE=OFF
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Forward LocalAI's BUILD_TYPE to the matching ggml backend switch.
|
||||||
|
ifeq ($(BUILD_TYPE),cublas)
|
||||||
|
CMAKE_ARGS+=-DGGML_CUDA=ON -DLA_GGML_CUDA=ON
|
||||||
|
else ifeq ($(BUILD_TYPE),openblas)
|
||||||
|
CMAKE_ARGS+=-DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS
|
||||||
|
else ifeq ($(BUILD_TYPE),clblas)
|
||||||
|
CMAKE_ARGS+=-DGGML_CLBLAST=ON
|
||||||
|
else ifeq ($(BUILD_TYPE),hipblas)
|
||||||
|
ROCM_HOME ?= /opt/rocm
|
||||||
|
ROCM_PATH ?= /opt/rocm
|
||||||
|
export CXX=$(ROCM_HOME)/llvm/bin/clang++
|
||||||
|
export CC=$(ROCM_HOME)/llvm/bin/clang
|
||||||
|
AMDGPU_TARGETS?=gfx908,gfx90a,gfx942,gfx950,gfx1030,gfx1100,gfx1101,gfx1102,gfx1200,gfx1201
|
||||||
|
CMAKE_ARGS+=-DGGML_HIPBLAS=ON -DAMDGPU_TARGETS=$(AMDGPU_TARGETS)
|
||||||
|
else ifeq ($(BUILD_TYPE),vulkan)
|
||||||
|
CMAKE_ARGS+=-DGGML_VULKAN=ON -DLA_GGML_VULKAN=ON
|
||||||
|
else ifeq ($(OS),Darwin)
|
||||||
|
ifneq ($(BUILD_TYPE),metal)
|
||||||
|
CMAKE_ARGS+=-DGGML_METAL=OFF
|
||||||
|
else
|
||||||
|
CMAKE_ARGS+=-DGGML_METAL=ON
|
||||||
|
CMAKE_ARGS+=-DGGML_METAL_EMBED_LIBRARY=ON
|
||||||
|
CMAKE_ARGS+=-DLA_GGML_METAL=ON
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(BUILD_TYPE),sycl_f16)
|
||||||
|
CMAKE_ARGS+=-DGGML_SYCL=ON \
|
||||||
|
-DCMAKE_C_COMPILER=icx \
|
||||||
|
-DCMAKE_CXX_COMPILER=icpx \
|
||||||
|
-DGGML_SYCL_F16=ON
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(BUILD_TYPE),sycl_f32)
|
||||||
|
CMAKE_ARGS+=-DGGML_SYCL=ON \
|
||||||
|
-DCMAKE_C_COMPILER=icx \
|
||||||
|
-DCMAKE_CXX_COMPILER=icpx
|
||||||
|
endif
|
||||||
|
|
||||||
|
sources/locate-anything.cpp:
|
||||||
|
mkdir -p sources && \
|
||||||
|
git clone --recursive $(LOCATEANYTHING_REPO) sources/locate-anything.cpp && \
|
||||||
|
cd sources/locate-anything.cpp && \
|
||||||
|
git checkout $(LOCATEANYTHING_VERSION) && \
|
||||||
|
git submodule update --init --recursive --depth 1 --single-branch
|
||||||
|
|
||||||
|
# Detect OS
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
|
||||||
|
# Only build CPU variants on Linux
|
||||||
|
ifeq ($(UNAME_S),Linux)
|
||||||
|
VARIANT_TARGETS = liblocateanythingcpp-avx.so liblocateanythingcpp-avx2.so liblocateanythingcpp-avx512.so liblocateanythingcpp-fallback.so
|
||||||
|
else
|
||||||
|
# On non-Linux (e.g., Darwin), build only fallback variant
|
||||||
|
VARIANT_TARGETS = liblocateanythingcpp-fallback.dylib
|
||||||
|
endif
|
||||||
|
|
||||||
|
locate-anything-cpp: main.go golocateanythingcpp.go $(VARIANT_TARGETS)
|
||||||
|
CGO_ENABLED=0 $(GOCMD) build -tags "$(GO_TAGS)" -o locate-anything-cpp ./
|
||||||
|
|
||||||
|
package: locate-anything-cpp
|
||||||
|
bash package.sh
|
||||||
|
|
||||||
|
build: package
|
||||||
|
|
||||||
|
clean: purge
|
||||||
|
rm -rf liblocateanythingcpp*.so liblocateanythingcpp*.dylib locate-anything-cpp package sources
|
||||||
|
|
||||||
|
purge:
|
||||||
|
rm -rf build*
|
||||||
|
|
||||||
|
# Build all variants (Linux only)
|
||||||
|
ifeq ($(UNAME_S),Linux)
|
||||||
|
liblocateanythingcpp-avx.so: sources/locate-anything.cpp
|
||||||
|
rm -rfv build-$@
|
||||||
|
$(info ${GREEN}I locate-anything-cpp build info:avx${RESET})
|
||||||
|
SO_TARGET=$@ CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) liblocateanythingcpp-custom
|
||||||
|
rm -rfv build-$@
|
||||||
|
|
||||||
|
liblocateanythingcpp-avx2.so: sources/locate-anything.cpp
|
||||||
|
rm -rfv build-$@
|
||||||
|
$(info ${GREEN}I locate-anything-cpp build info:avx2${RESET})
|
||||||
|
SO_TARGET=$@ CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=on -DGGML_AVX512=off -DGGML_FMA=on -DGGML_F16C=on -DGGML_BMI2=on" $(MAKE) liblocateanythingcpp-custom
|
||||||
|
rm -rfv build-$@
|
||||||
|
|
||||||
|
liblocateanythingcpp-avx512.so: sources/locate-anything.cpp
|
||||||
|
rm -rfv build-$@
|
||||||
|
$(info ${GREEN}I locate-anything-cpp build info:avx512${RESET})
|
||||||
|
SO_TARGET=$@ CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=on -DGGML_AVX512=on -DGGML_FMA=on -DGGML_F16C=on -DGGML_BMI2=on" $(MAKE) liblocateanythingcpp-custom
|
||||||
|
rm -rfv build-$@
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Build fallback variant (all platforms)
|
||||||
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
liblocateanythingcpp-fallback.dylib: sources/locate-anything.cpp
|
||||||
|
rm -rfv build-$@
|
||||||
|
$(info ${GREEN}I locate-anything-cpp build info:fallback${RESET})
|
||||||
|
SO_TARGET=$@ CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) liblocateanythingcpp-custom
|
||||||
|
rm -rfv build-$@
|
||||||
|
else
|
||||||
|
liblocateanythingcpp-fallback.so: sources/locate-anything.cpp
|
||||||
|
rm -rfv build-$@
|
||||||
|
$(info ${GREEN}I locate-anything-cpp build info:fallback${RESET})
|
||||||
|
SO_TARGET=$@ CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) liblocateanythingcpp-custom
|
||||||
|
rm -rfv build-$@
|
||||||
|
endif
|
||||||
|
|
||||||
|
liblocateanythingcpp-custom: CMakeLists.txt
|
||||||
|
mkdir -p build-$(SO_TARGET) && \
|
||||||
|
cd build-$(SO_TARGET) && \
|
||||||
|
cmake .. $(CMAKE_ARGS) && \
|
||||||
|
cmake --build . --config Release -j$(JOBS) && \
|
||||||
|
cd .. && \
|
||||||
|
(mv build-$(SO_TARGET)/liblocateanythingcpp.so ./$(SO_TARGET) 2>/dev/null || \
|
||||||
|
mv build-$(SO_TARGET)/liblocateanythingcpp.dylib ./$(SO_TARGET) 2>/dev/null)
|
||||||
|
|
||||||
|
all: locate-anything-cpp package
|
||||||
|
|
||||||
|
# `test` is invoked by the top-level Makefile's `test-extra` target. It builds
|
||||||
|
# the backend binary + the fallback shared library (needed for dlopen at
|
||||||
|
# runtime), then runs test.sh which downloads the q8_0 GGUF + COCO image and
|
||||||
|
# exercises the gRPC Load/Detect wire path via the Go smoke test in
|
||||||
|
# main_test.go.
|
||||||
|
test: locate-anything-cpp liblocateanythingcpp-fallback.so
|
||||||
|
bash test.sh
|
||||||
174
backend/go/locate-anything-cpp/golocateanythingcpp.go
Normal file
174
backend/go/locate-anything-cpp/golocateanythingcpp.go
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// golocateanythingcpp.go - gRPC handlers (Load, Detect) for the
|
||||||
|
// locate-anything-cpp backend.
|
||||||
|
//
|
||||||
|
// Embeds base.SingleThread to default unimplemented RPCs to "not supported"
|
||||||
|
// while we only implement open-vocabulary object detection (Detect).
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/mudler/LocalAI/pkg/grpc/base"
|
||||||
|
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// la_ctx* is an opaque handle. la_capi_load returns it directly (0 == failure),
|
||||||
|
// unlike rfdetr's out-parameter convention.
|
||||||
|
var (
|
||||||
|
// la_capi_load(const char* gguf_path, int n_threads) -> la_ctx* (0 = fail)
|
||||||
|
CapiLoad func(gguf string, nThreads int32) uintptr
|
||||||
|
// la_capi_free(la_ctx* ctx)
|
||||||
|
CapiFree func(handle uintptr)
|
||||||
|
// la_capi_locate_path(ctx, image_path, prompt, mode) -> char* json (0 = err)
|
||||||
|
CapiLocatePath func(handle uintptr, imagePath string, prompt string, mode int32) uintptr
|
||||||
|
// la_capi_locate_buffer(ctx, bytes, len, prompt, mode) -> char* json (0 = err)
|
||||||
|
CapiLocateBuffer func(handle uintptr, bytes uintptr, length uintptr, prompt string, mode int32) uintptr
|
||||||
|
// la_capi_get_n_detections(ctx) -> int
|
||||||
|
CapiGetNDetections func(handle uintptr) int32
|
||||||
|
// la_capi_get_detection_box(ctx, i, out_xyxy[4]) -> int (0 on success)
|
||||||
|
CapiGetDetectionBox func(handle uintptr, i int32, outXYXY uintptr) int32
|
||||||
|
// la_capi_get_detection_label(ctx, i, buf, buf_size) -> int (required size incl NUL; two-call sizing)
|
||||||
|
CapiGetDetectionLabel func(handle uintptr, i int32, buf uintptr, bufSize int32) int32
|
||||||
|
// la_capi_free_string(char* s)
|
||||||
|
CapiFreeString func(s uintptr)
|
||||||
|
// la_capi_last_error(ctx) -> const char* (owned by ctx, "" if none / null ctx).
|
||||||
|
// purego marshals the returned C string into a Go string (a copy), so we
|
||||||
|
// never free it and avoid raw pointer arithmetic.
|
||||||
|
CapiLastError func(handle uintptr) string
|
||||||
|
)
|
||||||
|
|
||||||
|
type LocateAnythingCpp struct {
|
||||||
|
base.SingleThread
|
||||||
|
handle uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads the GGUF model at opts.ModelFile (joined with opts.ModelPath if
|
||||||
|
// relative) and stores the la_ctx handle for later Detect calls.
|
||||||
|
func (r *LocateAnythingCpp) Load(opts *pb.ModelOptions) error {
|
||||||
|
modelFile := opts.ModelFile
|
||||||
|
if modelFile == "" {
|
||||||
|
modelFile = opts.Model
|
||||||
|
}
|
||||||
|
if modelFile == "" {
|
||||||
|
return fmt.Errorf("locate-anything-cpp: ModelFile is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
var modelPath string
|
||||||
|
if filepath.IsAbs(modelFile) {
|
||||||
|
modelPath = modelFile
|
||||||
|
} else {
|
||||||
|
modelPath = filepath.Join(opts.ModelPath, modelFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(modelPath); err != nil {
|
||||||
|
return fmt.Errorf("locate-anything-cpp: model file not found: %s: %w", modelPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
threads := opts.Threads
|
||||||
|
if threads <= 0 {
|
||||||
|
threads = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release previous model if any (re-Load).
|
||||||
|
if r.handle != 0 {
|
||||||
|
CapiFree(r.handle)
|
||||||
|
r.handle = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
h := CapiLoad(modelPath, threads)
|
||||||
|
if h == 0 {
|
||||||
|
// la_capi_last_error needs a ctx; on a failed load we have none (it
|
||||||
|
// returns "" for a null ctx), so the text is best-effort. Surface it
|
||||||
|
// when present.
|
||||||
|
if msg := CapiLastError(0); msg != "" {
|
||||||
|
return fmt.Errorf("locate-anything-cpp: la_capi_load failed for %s: %s", modelPath, msg)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("locate-anything-cpp: la_capi_load failed for %s", modelPath)
|
||||||
|
}
|
||||||
|
r.handle = h
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect runs open-vocabulary detection on the base64-encoded image in opts.Src
|
||||||
|
// using the required text prompt in opts.Prompt, returning one pb.Detection per
|
||||||
|
// located object with its predicted label as ClassName.
|
||||||
|
func (r *LocateAnythingCpp) Detect(opts *pb.DetectOptions) (pb.DetectResponse, error) {
|
||||||
|
if r.handle == 0 {
|
||||||
|
return pb.DetectResponse{}, fmt.Errorf("locate-anything-cpp: model not loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open-vocabulary detection is prompt-driven; without a prompt there is
|
||||||
|
// nothing to locate.
|
||||||
|
prompt := opts.Prompt
|
||||||
|
if prompt == "" {
|
||||||
|
return pb.DetectResponse{}, fmt.Errorf("locate-anything-cpp: a text prompt is required (open-vocabulary detection)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode base64 image and write to temp file.
|
||||||
|
imgData, err := base64.StdEncoding.DecodeString(opts.Src)
|
||||||
|
if err != nil {
|
||||||
|
return pb.DetectResponse{}, fmt.Errorf("locate-anything-cpp: failed to decode base64 image: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFile, err := os.CreateTemp("", "locate-anything-*.img")
|
||||||
|
if err != nil {
|
||||||
|
return pb.DetectResponse{}, fmt.Errorf("locate-anything-cpp: failed to create temp file: %w", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = os.Remove(tmpFile.Name()) }()
|
||||||
|
|
||||||
|
if _, err := tmpFile.Write(imgData); err != nil {
|
||||||
|
_ = tmpFile.Close()
|
||||||
|
return pb.DetectResponse{}, fmt.Errorf("locate-anything-cpp: failed to write temp file: %w", err)
|
||||||
|
}
|
||||||
|
if err := tmpFile.Close(); err != nil {
|
||||||
|
return pb.DetectResponse{}, fmt.Errorf("locate-anything-cpp: failed to close temp file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode 0 = hybrid (Parallel Box Decoding). The JSON return value is unused:
|
||||||
|
// structured detections are read via the accessor functions. Still must
|
||||||
|
// free the returned string.
|
||||||
|
jsonPtr := CapiLocatePath(r.handle, tmpFile.Name(), prompt, 0)
|
||||||
|
if jsonPtr != 0 {
|
||||||
|
CapiFreeString(jsonPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
n := CapiGetNDetections(r.handle)
|
||||||
|
if n < 0 {
|
||||||
|
return pb.DetectResponse{}, fmt.Errorf("locate-anything-cpp: invalid n_detections=%d", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
detections := make([]*pb.Detection, 0, n)
|
||||||
|
for i := int32(0); i < n; i++ {
|
||||||
|
var xyxy [4]float32 // x1, y1, x2, y2
|
||||||
|
if CapiGetDetectionBox(r.handle, i, uintptr(unsafe.Pointer(&xyxy[0]))) != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two-call sizing for the label string.
|
||||||
|
label := ""
|
||||||
|
need := CapiGetDetectionLabel(r.handle, i, 0, 0)
|
||||||
|
if need > 0 {
|
||||||
|
buf := make([]byte, need)
|
||||||
|
CapiGetDetectionLabel(r.handle, i, uintptr(unsafe.Pointer(&buf[0])), need)
|
||||||
|
label = string(buf[:need-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
detections = append(detections, &pb.Detection{
|
||||||
|
X: xyxy[0],
|
||||||
|
Y: xyxy[1],
|
||||||
|
Width: xyxy[2] - xyxy[0],
|
||||||
|
Height: xyxy[3] - xyxy[1],
|
||||||
|
Confidence: 1.0,
|
||||||
|
ClassName: label,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return pb.DetectResponse{
|
||||||
|
Detections: detections,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
64
backend/go/locate-anything-cpp/main.go
Normal file
64
backend/go/locate-anything-cpp/main.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// main.go - entry point for the locate-anything-cpp gRPC backend.
|
||||||
|
//
|
||||||
|
// Dlopens liblocateanythingcpp-<variant>.so via purego at the path in
|
||||||
|
// LOCATEANYTHING_LIBRARY (set by run.sh based on /proc/cpuinfo), registers
|
||||||
|
// the la_capi_* C ABI symbols, then starts the gRPC server.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/ebitengine/purego"
|
||||||
|
grpc "github.com/mudler/LocalAI/pkg/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||||
|
)
|
||||||
|
|
||||||
|
type LibFuncs struct {
|
||||||
|
FuncPtr any
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Get library name from environment variable, default to fallback
|
||||||
|
libName := os.Getenv("LOCATEANYTHING_LIBRARY")
|
||||||
|
if libName == "" {
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
libName = "./liblocateanythingcpp-fallback.dylib"
|
||||||
|
} else {
|
||||||
|
libName = "./liblocateanythingcpp-fallback.so"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lib, err := purego.Dlopen(libName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
libFuncs := []LibFuncs{
|
||||||
|
{&CapiLoad, "la_capi_load"},
|
||||||
|
{&CapiFree, "la_capi_free"},
|
||||||
|
{&CapiLocatePath, "la_capi_locate_path"},
|
||||||
|
{&CapiLocateBuffer, "la_capi_locate_buffer"},
|
||||||
|
{&CapiGetNDetections, "la_capi_get_n_detections"},
|
||||||
|
{&CapiGetDetectionBox, "la_capi_get_detection_box"},
|
||||||
|
{&CapiGetDetectionLabel, "la_capi_get_detection_label"},
|
||||||
|
{&CapiFreeString, "la_capi_free_string"},
|
||||||
|
{&CapiLastError, "la_capi_last_error"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lf := range libFuncs {
|
||||||
|
purego.RegisterLibFunc(lf.FuncPtr, lib, lf.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if err := grpc.StartServer(*addr, &LocateAnythingCpp{}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
176
backend/go/locate-anything-cpp/main_test.go
Normal file
176
backend/go/locate-anything-cpp/main_test.go
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// main_test.go - end-to-end smoke test for the locate-anything-cpp gRPC backend.
|
||||||
|
//
|
||||||
|
// Spawns the compiled locate-anything-cpp binary on a free local port, dials it
|
||||||
|
// via gRPC, and exercises LoadModel + Detect against the test fixtures
|
||||||
|
// downloaded by test.sh: the q8_0 GGUF of nvidia/LocateAnything-3B and a real
|
||||||
|
// COCO image with people + cars. Asserts that open-vocabulary detection driven
|
||||||
|
// by a text prompt returns at least one detection, each carrying a non-empty
|
||||||
|
// class name and a bounding box of non-zero size.
|
||||||
|
//
|
||||||
|
// The spec Skip()s cleanly if its fixtures (the ~6.3 GB model, the test image,
|
||||||
|
// the built binary, or the fallback .so) are missing, so the test target stays
|
||||||
|
// usable on a fresh checkout / on CI runners where the large model hasn't been
|
||||||
|
// downloaded.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDetect(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "locate-anything-cpp backend smoke suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
// freePort grabs an ephemeral TCP port and immediately releases it so the
|
||||||
|
// spawned backend can bind to it. There is a tiny TOCTOU window here but in
|
||||||
|
// practice it's adequate for a smoke test on a quiet runner.
|
||||||
|
func freePort() int {
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "freePort listen")
|
||||||
|
port := l.Addr().(*net.TCPAddr).Port
|
||||||
|
Expect(l.Close()).To(Succeed())
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
// startBackend spawns the locate-anything-cpp binary on the given port and
|
||||||
|
// waits until it accepts TCP connections (up to 10s). It mirrors how main.go
|
||||||
|
// resolves the purego library: the LOCATEANYTHING_LIBRARY env var points the
|
||||||
|
// dlopen at the freshly built fallback .so, and the la_capi_* symbols are
|
||||||
|
// registered there. The returned cleanup func kills the process and reaps it.
|
||||||
|
func startBackend(port int) func() {
|
||||||
|
binary, err := filepath.Abs("./locate-anything-cpp")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
if _, err := os.Stat(binary); err != nil {
|
||||||
|
Skip(fmt.Sprintf("backend binary not built: %s (run `make locate-anything-cpp` first)", binary))
|
||||||
|
}
|
||||||
|
|
||||||
|
libPath, err := filepath.Abs("./liblocateanythingcpp-fallback.so")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
if _, err := os.Stat(libPath); err != nil {
|
||||||
|
Skip(fmt.Sprintf("fallback library not built: %s (run `make liblocateanythingcpp-fallback.so` first)", libPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||||
|
cmd := exec.Command(binary, "--addr", addr)
|
||||||
|
cmd.Env = append(os.Environ(), "LOCATEANYTHING_LIBRARY="+libPath)
|
||||||
|
cmd.Stdout = os.Stderr
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
Expect(cmd.Start()).To(Succeed())
|
||||||
|
|
||||||
|
cleanup := func() {
|
||||||
|
if cmd.Process != nil {
|
||||||
|
_ = cmd.Process.Kill()
|
||||||
|
_, _ = cmd.Process.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deadline := time.Now().Add(10 * time.Second)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
c, err := net.DialTimeout("tcp", addr, 200*time.Millisecond)
|
||||||
|
if err == nil {
|
||||||
|
_ = c.Close()
|
||||||
|
return cleanup
|
||||||
|
}
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
Fail(fmt.Sprintf("backend did not become ready on %s within 10s", addr))
|
||||||
|
return func() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTestImage reads the COCO test image downloaded by test.sh and returns its
|
||||||
|
// base64-encoded content (the wire format accepted by the Detect RPC).
|
||||||
|
func loadTestImage() string {
|
||||||
|
imgPath, err := filepath.Abs("test-data/test.jpg")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
imgBytes, err := os.ReadFile(imgPath)
|
||||||
|
if err != nil {
|
||||||
|
Skip(fmt.Sprintf("test image not present: %s (run test.sh first)", imgPath))
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(imgBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialBackend opens a gRPC client connection to the spawned backend.
|
||||||
|
func dialBackend(port int) (pb.BackendClient, func()) {
|
||||||
|
addr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||||
|
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
return pb.NewBackendClient(conn), func() { _ = conn.Close() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// modelPathOrSkip resolves the model file under ./test-models/ and Skip()s the
|
||||||
|
// current spec if it's missing (the ~6.3 GB GGUF is not present on a fresh
|
||||||
|
// checkout / on CI runners without the download).
|
||||||
|
func modelPathOrSkip(name string) string {
|
||||||
|
modelDir, err := filepath.Abs("test-models")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
modelPath := filepath.Join(modelDir, name)
|
||||||
|
if _, err := os.Stat(modelPath); err != nil {
|
||||||
|
Skip(fmt.Sprintf("model not present: %s (run test.sh first)", modelPath))
|
||||||
|
}
|
||||||
|
return modelPath
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("locate-anything-cpp backend", func() {
|
||||||
|
It("runs open-vocabulary detection against a known-good COCO image", func() {
|
||||||
|
modelPath := modelPathOrSkip("locate-anything-q8_0.gguf")
|
||||||
|
imgB64 := loadTestImage()
|
||||||
|
|
||||||
|
port := freePort()
|
||||||
|
cleanup := startBackend(port)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
client, closeConn := dialBackend(port)
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
// The q8_0 model is ~6.3 GB and hybrid Parallel Box Decoding on CPU is
|
||||||
|
// not cheap, so give LoadModel + Detect a generous deadline.
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
loadResp, err := client.LoadModel(ctx, &pb.ModelOptions{
|
||||||
|
Model: "locate-anything-q8_0.gguf",
|
||||||
|
ModelFile: modelPath,
|
||||||
|
Threads: 4,
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "LoadModel")
|
||||||
|
Expect(loadResp.GetSuccess()).To(BeTrue(), "LoadModel reported failure: %s", loadResp.GetMessage())
|
||||||
|
|
||||||
|
// Open-vocabulary detection is prompt-driven; the prompt names the
|
||||||
|
// classes to locate (people + cars), separated by the </c> control token.
|
||||||
|
detResp, err := client.Detect(ctx, &pb.DetectOptions{
|
||||||
|
Src: imgB64,
|
||||||
|
Prompt: "Locate all the instances that matches the following description: person</c>car.",
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred(), "Detect")
|
||||||
|
Expect(detResp.GetDetections()).ToNot(BeEmpty(), "no detections returned on a known-good COCO image")
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(GinkgoWriter, "detection OK: %d detections\n", len(detResp.GetDetections()))
|
||||||
|
for i, d := range detResp.GetDetections() {
|
||||||
|
Expect(d.GetClassName()).ToNot(BeEmpty(), "detection %d has empty class_name", i)
|
||||||
|
Expect(d.GetWidth()).To(BeNumerically(">", float32(0)),
|
||||||
|
"detection %d has non-positive width", i)
|
||||||
|
Expect(d.GetHeight()).To(BeNumerically(">", float32(0)),
|
||||||
|
"detection %d has non-positive height", i)
|
||||||
|
_, _ = fmt.Fprintf(GinkgoWriter, " [%d] %s box=(%.1f,%.1f,%.1fx%.1f)\n",
|
||||||
|
i, d.GetClassName(), d.GetX(), d.GetY(), d.GetWidth(), d.GetHeight())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
60
backend/go/locate-anything-cpp/package.sh
Executable file
60
backend/go/locate-anything-cpp/package.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script to copy the appropriate libraries based on architecture
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CURDIR=$(dirname "$(realpath $0)")
|
||||||
|
REPO_ROOT="${CURDIR}/../../.."
|
||||||
|
|
||||||
|
# Create lib directory
|
||||||
|
mkdir -p $CURDIR/package/lib
|
||||||
|
|
||||||
|
cp -fv $CURDIR/liblocateanythingcpp-*.so $CURDIR/package/ 2>/dev/null || true
|
||||||
|
cp -fv $CURDIR/liblocateanythingcpp-*.dylib $CURDIR/package/ 2>/dev/null || true
|
||||||
|
cp -avf $CURDIR/locate-anything-cpp $CURDIR/package/
|
||||||
|
cp -fv $CURDIR/run.sh $CURDIR/package/
|
||||||
|
|
||||||
|
# Detect architecture and copy appropriate libraries
|
||||||
|
if [ -f "/lib64/ld-linux-x86-64.so.2" ]; then
|
||||||
|
# x86_64 architecture
|
||||||
|
echo "Detected x86_64 architecture, copying x86_64 libraries..."
|
||||||
|
cp -arfLv /lib64/ld-linux-x86-64.so.2 $CURDIR/package/lib/ld.so
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libc.so.6 $CURDIR/package/lib/libc.so.6
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libgcc_s.so.1 $CURDIR/package/lib/libgcc_s.so.1
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libstdc++.so.6 $CURDIR/package/lib/libstdc++.so.6
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libm.so.6 $CURDIR/package/lib/libm.so.6
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libgomp.so.1 $CURDIR/package/lib/libgomp.so.1
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libdl.so.2 $CURDIR/package/lib/libdl.so.2
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/librt.so.1 $CURDIR/package/lib/librt.so.1
|
||||||
|
cp -arfLv /lib/x86_64-linux-gnu/libpthread.so.0 $CURDIR/package/lib/libpthread.so.0
|
||||||
|
elif [ -f "/lib/ld-linux-aarch64.so.1" ]; then
|
||||||
|
# ARM64 architecture
|
||||||
|
echo "Detected ARM64 architecture, copying ARM64 libraries..."
|
||||||
|
cp -arfLv /lib/ld-linux-aarch64.so.1 $CURDIR/package/lib/ld.so
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libc.so.6 $CURDIR/package/lib/libc.so.6
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libgcc_s.so.1 $CURDIR/package/lib/libgcc_s.so.1
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libstdc++.so.6 $CURDIR/package/lib/libstdc++.so.6
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libm.so.6 $CURDIR/package/lib/libm.so.6
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libgomp.so.1 $CURDIR/package/lib/libgomp.so.1
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libdl.so.2 $CURDIR/package/lib/libdl.so.2
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/librt.so.1 $CURDIR/package/lib/librt.so.1
|
||||||
|
cp -arfLv /lib/aarch64-linux-gnu/libpthread.so.0 $CURDIR/package/lib/libpthread.so.0
|
||||||
|
elif [ $(uname -s) = "Darwin" ]; then
|
||||||
|
echo "Detected Darwin"
|
||||||
|
else
|
||||||
|
echo "Error: Could not detect architecture"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Package GPU libraries based on BUILD_TYPE
|
||||||
|
GPU_LIB_SCRIPT="${REPO_ROOT}/scripts/build/package-gpu-libs.sh"
|
||||||
|
if [ -f "$GPU_LIB_SCRIPT" ]; then
|
||||||
|
echo "Packaging GPU libraries for BUILD_TYPE=${BUILD_TYPE:-cpu}..."
|
||||||
|
source "$GPU_LIB_SCRIPT" "$CURDIR/package/lib"
|
||||||
|
package_gpu_libs
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Packaging completed successfully"
|
||||||
|
ls -liah $CURDIR/package/
|
||||||
|
ls -liah $CURDIR/package/lib/
|
||||||
57
backend/go/locate-anything-cpp/run.sh
Executable file
57
backend/go/locate-anything-cpp/run.sh
Executable file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
# Get the absolute current dir where the script is located
|
||||||
|
CURDIR=$(dirname "$(realpath $0)")
|
||||||
|
|
||||||
|
cd /
|
||||||
|
|
||||||
|
echo "CPU info:"
|
||||||
|
if [ "$(uname)" != "Darwin" ]; then
|
||||||
|
grep -e "model\sname" /proc/cpuinfo | head -1
|
||||||
|
grep -e "flags" /proc/cpuinfo | head -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
|
# macOS: single dylib variant (Metal or Accelerate)
|
||||||
|
LIBRARY="$CURDIR/liblocateanythingcpp-fallback.dylib"
|
||||||
|
export DYLD_LIBRARY_PATH=$CURDIR/lib:$DYLD_LIBRARY_PATH
|
||||||
|
else
|
||||||
|
LIBRARY="$CURDIR/liblocateanythingcpp-fallback.so"
|
||||||
|
|
||||||
|
if grep -q -e "\savx\s" /proc/cpuinfo ; then
|
||||||
|
echo "CPU: AVX found OK"
|
||||||
|
if [ -e $CURDIR/liblocateanythingcpp-avx.so ]; then
|
||||||
|
LIBRARY="$CURDIR/liblocateanythingcpp-avx.so"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q -e "\savx2\s" /proc/cpuinfo ; then
|
||||||
|
echo "CPU: AVX2 found OK"
|
||||||
|
if [ -e $CURDIR/liblocateanythingcpp-avx2.so ]; then
|
||||||
|
LIBRARY="$CURDIR/liblocateanythingcpp-avx2.so"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check avx 512
|
||||||
|
if grep -q -e "\savx512f\s" /proc/cpuinfo ; then
|
||||||
|
echo "CPU: AVX512F found OK"
|
||||||
|
if [ -e $CURDIR/liblocateanythingcpp-avx512.so ]; then
|
||||||
|
LIBRARY="$CURDIR/liblocateanythingcpp-avx512.so"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH=$CURDIR/lib:$LD_LIBRARY_PATH
|
||||||
|
fi
|
||||||
|
|
||||||
|
export LOCATEANYTHING_LIBRARY=$LIBRARY
|
||||||
|
|
||||||
|
# If there is a lib/ld.so, use it
|
||||||
|
if [ -f $CURDIR/lib/ld.so ]; then
|
||||||
|
echo "Using lib/ld.so"
|
||||||
|
echo "Using library: $LIBRARY"
|
||||||
|
exec $CURDIR/lib/ld.so $CURDIR/locate-anything-cpp "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Using library: $LIBRARY"
|
||||||
|
exec $CURDIR/locate-anything-cpp "$@"
|
||||||
47
backend/go/locate-anything-cpp/test.sh
Executable file
47
backend/go/locate-anything-cpp/test.sh
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CURDIR=$(dirname "$(realpath $0)")
|
||||||
|
|
||||||
|
echo "Running locate-anything-cpp backend tests..."
|
||||||
|
|
||||||
|
# Test model from the mudler/locate-anything.cpp-gguf HuggingFace repo. This is
|
||||||
|
# the q8_0 quantization of nvidia/LocateAnything-3B (~6.3 GB), so the download
|
||||||
|
# is the slow step. It is resumed with `curl -C -` and skipped entirely if the
|
||||||
|
# file is already present.
|
||||||
|
LOCATEANYTHING_MODEL_DIR="${LOCATEANYTHING_MODEL_DIR:-$CURDIR/test-models}"
|
||||||
|
|
||||||
|
LOCATEANYTHING_MODEL_FILE="${LOCATEANYTHING_MODEL_FILE:-locate-anything-q8_0.gguf}"
|
||||||
|
LOCATEANYTHING_MODEL_URL="${LOCATEANYTHING_MODEL_URL:-https://huggingface.co/mudler/locate-anything.cpp-gguf/resolve/main/locate-anything-q8_0.gguf}"
|
||||||
|
|
||||||
|
mkdir -p "$LOCATEANYTHING_MODEL_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "$LOCATEANYTHING_MODEL_DIR/$LOCATEANYTHING_MODEL_FILE" ]; then
|
||||||
|
echo "Downloading locate-anything q8_0 model (~6.3 GB, this is slow)..."
|
||||||
|
# -C - resumes a partial download so an interrupted run doesn't restart from 0.
|
||||||
|
curl -L -C - -o "$LOCATEANYTHING_MODEL_DIR/$LOCATEANYTHING_MODEL_FILE" "$LOCATEANYTHING_MODEL_URL" --progress-bar
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use a real COCO test image (people + cars) from the upstream rf-detr.cpp repo
|
||||||
|
# (~46 KB). Open-vocabulary detection needs real content to locate, so a
|
||||||
|
# synthetic image would trivially yield zero detections.
|
||||||
|
TEST_IMAGE_DIR="$CURDIR/test-data"
|
||||||
|
TEST_IMAGE_FILE="$TEST_IMAGE_DIR/test.jpg"
|
||||||
|
TEST_IMAGE_URL="${TEST_IMAGE_URL:-https://raw.githubusercontent.com/mudler/rf-detr.cpp/main/tests/fixtures/ci/test_image.jpg}"
|
||||||
|
|
||||||
|
mkdir -p "$TEST_IMAGE_DIR"
|
||||||
|
if [ ! -f "$TEST_IMAGE_FILE" ]; then
|
||||||
|
echo "Downloading COCO test image..."
|
||||||
|
curl -L -o "$TEST_IMAGE_FILE" "$TEST_IMAGE_URL" --progress-bar
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "locate-anything-cpp test setup complete."
|
||||||
|
echo " model: $LOCATEANYTHING_MODEL_DIR/$LOCATEANYTHING_MODEL_FILE"
|
||||||
|
echo " test image: $TEST_IMAGE_FILE"
|
||||||
|
|
||||||
|
# Run the Go smoke test: spawns the backend binary on a free port, calls
|
||||||
|
# LoadModel + Detect via gRPC against the downloaded GGUF + COCO image.
|
||||||
|
echo ""
|
||||||
|
echo "Running Go smoke test..."
|
||||||
|
cd "$CURDIR"
|
||||||
|
go test -v -timeout 30m ./...
|
||||||
17
backend/go/omnivoice-cpp/.gitignore
vendored
Normal file
17
backend/go/omnivoice-cpp/.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Fetched upstream sources
|
||||||
|
sources/
|
||||||
|
|
||||||
|
# CMake build directories
|
||||||
|
build*/
|
||||||
|
|
||||||
|
# Compiled shared libraries
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Compiled backend binary
|
||||||
|
omnivoice-cpp
|
||||||
|
|
||||||
|
# Packaging output
|
||||||
|
package/
|
||||||
|
|
||||||
|
# Downloaded e2e models
|
||||||
|
omnivoice-models/
|
||||||
53
backend/go/omnivoice-cpp/CMakeLists.txt
Normal file
53
backend/go/omnivoice-cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
project(gomnivoicecpp LANGUAGES C CXX)
|
||||||
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
set(OMNIVOICE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sources/omnivoice.cpp)
|
||||||
|
|
||||||
|
# Override upstream's CMAKE_CUDA_ARCHITECTURES before add_subdirectory.
|
||||||
|
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
|
||||||
|
set(CMAKE_CUDA_ARCHITECTURES "75-virtual;80-virtual;86-real;89-real")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Add the upstream project. Its own CMakeLists adds ggml + builds
|
||||||
|
# omnivoice-core (STATIC, contains src/omnivoice.cpp i.e. the ov_* impl).
|
||||||
|
# EXCLUDE_FROM_ALL keeps its CLI tools/tests from building unless referenced.
|
||||||
|
add_subdirectory(${OMNIVOICE_DIR} omnivoice EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
# Upstream generates version.h into its own CMAKE_CURRENT_BINARY_DIR and adds
|
||||||
|
# the top-level ${CMAKE_BINARY_DIR} to omnivoice-core's include path. When the
|
||||||
|
# project is nested under add_subdirectory those two directories differ
|
||||||
|
# (<build>/omnivoice vs <build>), so omnivoice.cpp cannot find version.h. Point
|
||||||
|
# omnivoice-core at the subproject binary dir where version.h is actually
|
||||||
|
# generated. (Fix lives here, never in the fetched upstream checkout.)
|
||||||
|
target_include_directories(omnivoice-core PRIVATE ${CMAKE_BINARY_DIR}/omnivoice)
|
||||||
|
|
||||||
|
add_library(gomnivoicecpp MODULE cpp/gomnivoicecpp.cpp)
|
||||||
|
target_link_libraries(gomnivoicecpp PRIVATE omnivoice-core)
|
||||||
|
|
||||||
|
target_include_directories(gomnivoicecpp PRIVATE ${OMNIVOICE_DIR}/src)
|
||||||
|
target_include_directories(gomnivoicecpp SYSTEM PRIVATE ${OMNIVOICE_DIR}/ggml/include)
|
||||||
|
|
||||||
|
# Link GPU backends if the upstream ggml created them.
|
||||||
|
foreach(backend blas cuda metal vulkan sycl)
|
||||||
|
if(TARGET ggml-${backend})
|
||||||
|
target_link_libraries(gomnivoicecpp PRIVATE ggml-${backend})
|
||||||
|
if(backend STREQUAL "cuda")
|
||||||
|
find_package(CUDAToolkit QUIET)
|
||||||
|
if(CUDAToolkit_FOUND)
|
||||||
|
target_link_libraries(gomnivoicecpp PRIVATE CUDA::cudart)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(gomnivoicecpp PRIVATE /W4 /wd4100 /wd4505)
|
||||||
|
else()
|
||||||
|
target_compile_options(gomnivoicecpp PRIVATE -Wall -Wextra
|
||||||
|
-Wno-unused-parameter -Wno-unused-function)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set_property(TARGET gomnivoicecpp PROPERTY CXX_STANDARD 17)
|
||||||
|
set_target_properties(gomnivoicecpp PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
130
backend/go/omnivoice-cpp/Makefile
Normal file
130
backend/go/omnivoice-cpp/Makefile
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
CMAKE_ARGS?=
|
||||||
|
BUILD_TYPE?=
|
||||||
|
NATIVE?=false
|
||||||
|
|
||||||
|
GOCMD?=go
|
||||||
|
GO_TAGS?=
|
||||||
|
JOBS?=$(shell nproc --ignore=1)
|
||||||
|
|
||||||
|
# omnivoice.cpp version
|
||||||
|
OMNIVOICE_REPO?=https://github.com/ServeurpersoCom/omnivoice.cpp
|
||||||
|
OMNIVOICE_VERSION?=96d30169afd5e6bb3fd6a0e9be0eb505bfe81fcd
|
||||||
|
SO_TARGET?=libgomnivoicecpp.so
|
||||||
|
|
||||||
|
CMAKE_ARGS+=-DBUILD_SHARED_LIBS=OFF
|
||||||
|
|
||||||
|
ifeq ($(NATIVE),false)
|
||||||
|
CMAKE_ARGS+=-DGGML_NATIVE=OFF
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(BUILD_TYPE),cublas)
|
||||||
|
CMAKE_ARGS+=-DGGML_CUDA=ON
|
||||||
|
else ifeq ($(BUILD_TYPE),openblas)
|
||||||
|
CMAKE_ARGS+=-DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS
|
||||||
|
else ifeq ($(BUILD_TYPE),clblas)
|
||||||
|
CMAKE_ARGS+=-DGGML_CLBLAST=ON -DCLBlast_DIR=/some/path
|
||||||
|
else ifeq ($(BUILD_TYPE),hipblas)
|
||||||
|
CMAKE_ARGS+=-DGGML_HIPBLAS=ON
|
||||||
|
else ifeq ($(BUILD_TYPE),vulkan)
|
||||||
|
CMAKE_ARGS+=-DGGML_VULKAN=ON
|
||||||
|
else ifeq ($(OS),Darwin)
|
||||||
|
ifneq ($(BUILD_TYPE),metal)
|
||||||
|
CMAKE_ARGS+=-DGGML_METAL=OFF
|
||||||
|
else
|
||||||
|
CMAKE_ARGS+=-DGGML_METAL=ON
|
||||||
|
CMAKE_ARGS+=-DGGML_METAL_EMBED_LIBRARY=ON
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(BUILD_TYPE),sycl_f16)
|
||||||
|
CMAKE_ARGS+=-DGGML_SYCL=ON \
|
||||||
|
-DCMAKE_C_COMPILER=icx \
|
||||||
|
-DCMAKE_CXX_COMPILER=icpx \
|
||||||
|
-DGGML_SYCL_F16=ON
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(BUILD_TYPE),sycl_f32)
|
||||||
|
CMAKE_ARGS+=-DGGML_SYCL=ON \
|
||||||
|
-DCMAKE_C_COMPILER=icx \
|
||||||
|
-DCMAKE_CXX_COMPILER=icpx
|
||||||
|
endif
|
||||||
|
|
||||||
|
sources/omnivoice.cpp:
|
||||||
|
mkdir -p sources/omnivoice.cpp
|
||||||
|
cd sources/omnivoice.cpp && \
|
||||||
|
git init && \
|
||||||
|
git remote add origin $(OMNIVOICE_REPO) && \
|
||||||
|
git fetch origin && \
|
||||||
|
git checkout $(OMNIVOICE_VERSION) && \
|
||||||
|
git submodule update --init --recursive --depth 1 --single-branch
|
||||||
|
|
||||||
|
# Detect OS
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
|
||||||
|
# Only build CPU variants on Linux
|
||||||
|
ifeq ($(UNAME_S),Linux)
|
||||||
|
VARIANT_TARGETS = libgomnivoicecpp-avx.so libgomnivoicecpp-avx2.so libgomnivoicecpp-avx512.so libgomnivoicecpp-fallback.so
|
||||||
|
else
|
||||||
|
# On non-Linux (e.g., Darwin), build only fallback variant (as a dylib)
|
||||||
|
VARIANT_TARGETS = libgomnivoicecpp-fallback.dylib
|
||||||
|
endif
|
||||||
|
|
||||||
|
omnivoice-cpp: main.go gomnivoicecpp.go $(VARIANT_TARGETS)
|
||||||
|
CGO_ENABLED=0 $(GOCMD) build -tags "$(GO_TAGS)" -o omnivoice-cpp ./
|
||||||
|
|
||||||
|
package: omnivoice-cpp
|
||||||
|
bash package.sh
|
||||||
|
|
||||||
|
build: package
|
||||||
|
|
||||||
|
clean: purge
|
||||||
|
rm -rf libgomnivoicecpp*.so libgomnivoicecpp*.dylib package sources/omnivoice.cpp omnivoice-cpp
|
||||||
|
|
||||||
|
purge:
|
||||||
|
rm -rf build*
|
||||||
|
|
||||||
|
.NOTPARALLEL:
|
||||||
|
|
||||||
|
ifeq ($(UNAME_S),Linux)
|
||||||
|
libgomnivoicecpp-avx.so: sources/omnivoice.cpp
|
||||||
|
$(info ${GREEN}I omnivoice-cpp build info:avx${RESET})
|
||||||
|
SO_TARGET=libgomnivoicecpp-avx.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libgomnivoicecpp-custom
|
||||||
|
rm -rf build-libgomnivoicecpp-avx.so
|
||||||
|
|
||||||
|
libgomnivoicecpp-avx2.so: sources/omnivoice.cpp
|
||||||
|
$(info ${GREEN}I omnivoice-cpp build info:avx2${RESET})
|
||||||
|
SO_TARGET=libgomnivoicecpp-avx2.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=on -DGGML_AVX512=off -DGGML_FMA=on -DGGML_F16C=on -DGGML_BMI2=on" $(MAKE) libgomnivoicecpp-custom
|
||||||
|
rm -rf build-libgomnivoicecpp-avx2.so
|
||||||
|
|
||||||
|
libgomnivoicecpp-avx512.so: sources/omnivoice.cpp
|
||||||
|
$(info ${GREEN}I omnivoice-cpp build info:avx512${RESET})
|
||||||
|
SO_TARGET=libgomnivoicecpp-avx512.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=on -DGGML_AVX2=on -DGGML_AVX512=on -DGGML_FMA=on -DGGML_F16C=on -DGGML_BMI2=on" $(MAKE) libgomnivoicecpp-custom
|
||||||
|
rm -rf build-libgomnivoicecpp-avx512.so
|
||||||
|
endif
|
||||||
|
|
||||||
|
libgomnivoicecpp-fallback.so: sources/omnivoice.cpp
|
||||||
|
$(info ${GREEN}I omnivoice-cpp build info:fallback${RESET})
|
||||||
|
SO_TARGET=libgomnivoicecpp-fallback.so CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libgomnivoicecpp-custom
|
||||||
|
rm -rf build-libgomnivoicecpp-fallback.so
|
||||||
|
|
||||||
|
# Build fallback variant as a dylib (Darwin)
|
||||||
|
libgomnivoicecpp-fallback.dylib: sources/omnivoice.cpp
|
||||||
|
$(info ${GREEN}I omnivoice-cpp build info:fallback (dylib)${RESET})
|
||||||
|
SO_TARGET=libgomnivoicecpp-fallback.dylib CMAKE_ARGS="$(CMAKE_ARGS) -DGGML_AVX=off -DGGML_AVX2=off -DGGML_AVX512=off -DGGML_FMA=off -DGGML_F16C=off -DGGML_BMI2=off" $(MAKE) libgomnivoicecpp-custom
|
||||||
|
rm -rf build-libgomnivoicecpp-fallback.dylib
|
||||||
|
|
||||||
|
libgomnivoicecpp-custom: CMakeLists.txt cpp/gomnivoicecpp.cpp cpp/gomnivoicecpp.h
|
||||||
|
mkdir -p build-$(SO_TARGET) && \
|
||||||
|
cd build-$(SO_TARGET) && \
|
||||||
|
cmake .. $(CMAKE_ARGS) && \
|
||||||
|
cmake --build . --config Release -j$(JOBS) --target gomnivoicecpp && \
|
||||||
|
cd .. && \
|
||||||
|
(mv build-$(SO_TARGET)/libgomnivoicecpp.so ./$(SO_TARGET) 2>/dev/null || \
|
||||||
|
mv build-$(SO_TARGET)/libgomnivoicecpp.dylib ./$(SO_TARGET) 2>/dev/null)
|
||||||
|
|
||||||
|
test: omnivoice-cpp
|
||||||
|
@echo "Running omnivoice-cpp tests..."
|
||||||
|
bash test.sh
|
||||||
|
@echo "omnivoice-cpp tests completed."
|
||||||
|
|
||||||
|
all: omnivoice-cpp package
|
||||||
129
backend/go/omnivoice-cpp/audio.go
Normal file
129
backend/go/omnivoice-cpp/audio.go
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/go-audio/audio"
|
||||||
|
"github.com/go-audio/wav"
|
||||||
|
)
|
||||||
|
|
||||||
|
const omnivoiceSampleRate = 24000
|
||||||
|
|
||||||
|
// wavHeader24k returns a 44-byte WAV header for a streaming 24 kHz mono 16-bit
|
||||||
|
// PCM stream, with placeholder (0xFFFFFFFF) sizes since the total length is
|
||||||
|
// unknown up front. Emitted as the first chunk of TTSStream so the HTTP layer
|
||||||
|
// receives a self-describing WAV (the gRPC TTSStream path never sets Message,
|
||||||
|
// so the backend owns the header - see core/backend/tts.go:ModelTTSStream).
|
||||||
|
func wavHeader24k() []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
w := func(v any) { _ = binary.Write(&buf, binary.LittleEndian, v) }
|
||||||
|
buf.WriteString("RIFF")
|
||||||
|
w(uint32(0xFFFFFFFF))
|
||||||
|
buf.WriteString("WAVE")
|
||||||
|
buf.WriteString("fmt ")
|
||||||
|
w(uint32(16)) // Subchunk1Size
|
||||||
|
w(uint16(1)) // PCM
|
||||||
|
w(uint16(1)) // mono
|
||||||
|
w(uint32(omnivoiceSampleRate)) // sample rate
|
||||||
|
w(uint32(omnivoiceSampleRate * 2)) // byte rate = SR * blockAlign
|
||||||
|
w(uint16(2)) // block align (16-bit mono)
|
||||||
|
w(uint16(16)) // bits per sample
|
||||||
|
buf.WriteString("data")
|
||||||
|
w(uint32(0xFFFFFFFF))
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// floatToPCM16LE clamps each sample to [-1,1] and encodes it as little-endian
|
||||||
|
// signed 16-bit PCM.
|
||||||
|
func floatToPCM16LE(samples []float32) []byte {
|
||||||
|
out := make([]byte, len(samples)*2)
|
||||||
|
for i, s := range samples {
|
||||||
|
if s > 1 {
|
||||||
|
s = 1
|
||||||
|
} else if s < -1 {
|
||||||
|
s = -1
|
||||||
|
}
|
||||||
|
v := int16(s * 32767)
|
||||||
|
out[i*2] = byte(v)
|
||||||
|
out[i*2+1] = byte(v >> 8)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeWAV24k writes samples as a finalized 24 kHz mono 16-bit WAV at dst.
|
||||||
|
func writeWAV24k(dst string, samples []float32) error {
|
||||||
|
f, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("omnivoice: create %q: %w", dst, err)
|
||||||
|
}
|
||||||
|
enc := wav.NewEncoder(f, omnivoiceSampleRate, 16, 1, 1)
|
||||||
|
ints := make([]int, len(samples))
|
||||||
|
for i, s := range samples {
|
||||||
|
if s > 1 {
|
||||||
|
s = 1
|
||||||
|
} else if s < -1 {
|
||||||
|
s = -1
|
||||||
|
}
|
||||||
|
ints[i] = int(s * 32767)
|
||||||
|
}
|
||||||
|
b := &audio.IntBuffer{
|
||||||
|
Format: &audio.Format{NumChannels: 1, SampleRate: omnivoiceSampleRate},
|
||||||
|
Data: ints,
|
||||||
|
SourceBitDepth: 16,
|
||||||
|
}
|
||||||
|
if err := enc.Write(b); err != nil {
|
||||||
|
_ = enc.Close()
|
||||||
|
_ = f.Close()
|
||||||
|
return fmt.Errorf("omnivoice: encode WAV: %w", err)
|
||||||
|
}
|
||||||
|
if err := enc.Close(); err != nil {
|
||||||
|
_ = f.Close()
|
||||||
|
return fmt.Errorf("omnivoice: finalize WAV: %w", err)
|
||||||
|
}
|
||||||
|
return f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// readWAVAsFloat decodes a WAV file (any sample rate/channels) to a mono
|
||||||
|
// float32 slice in [-1,1] for use as reference audio. OmniVoice expects 24 kHz;
|
||||||
|
// callers should supply 24 kHz reference clips.
|
||||||
|
func readWAVAsFloat(path string) ([]float32, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("omnivoice: open ref %q: %w", path, err)
|
||||||
|
}
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
|
dec := wav.NewDecoder(f)
|
||||||
|
buf, err := dec.FullPCMBuffer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("omnivoice: decode ref %q: %w", path, err)
|
||||||
|
}
|
||||||
|
ch := int(buf.Format.NumChannels)
|
||||||
|
if ch < 1 {
|
||||||
|
ch = 1
|
||||||
|
}
|
||||||
|
bitDepth := int(buf.SourceBitDepth)
|
||||||
|
if bitDepth == 0 {
|
||||||
|
bitDepth = 16
|
||||||
|
}
|
||||||
|
scale := float32(int64(1) << uint(bitDepth-1))
|
||||||
|
n := len(buf.Data) / ch
|
||||||
|
out := make([]float32, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
// Downmix to mono by averaging channels.
|
||||||
|
var acc int
|
||||||
|
for c := 0; c < ch; c++ {
|
||||||
|
acc += buf.Data[i*ch+c]
|
||||||
|
}
|
||||||
|
out[i] = float32(acc) / float32(ch) / scale
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtimeKeepAlive prevents the GC from reclaiming the reference-audio slice
|
||||||
|
// while its backing pointer is in use across the C call.
|
||||||
|
func runtimeKeepAlive(v any) { runtime.KeepAlive(v) }
|
||||||
166
backend/go/omnivoice-cpp/cpp/gomnivoicecpp.cpp
Normal file
166
backend/go/omnivoice-cpp/cpp/gomnivoicecpp.cpp
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#include "gomnivoicecpp.h"
|
||||||
|
#include "ggml-backend.h"
|
||||||
|
#include "omnivoice.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
static ov_context *g_ctx = nullptr;
|
||||||
|
|
||||||
|
static void ggml_log_cb(enum ggml_log_level level, const char *log,
|
||||||
|
void * /*data*/) {
|
||||||
|
if (!log)
|
||||||
|
return;
|
||||||
|
const char *lvl = "?????";
|
||||||
|
switch (level) {
|
||||||
|
case GGML_LOG_LEVEL_DEBUG: lvl = "DEBUG"; break;
|
||||||
|
case GGML_LOG_LEVEL_INFO: lvl = "INFO"; break;
|
||||||
|
case GGML_LOG_LEVEL_WARN: lvl = "WARN"; break;
|
||||||
|
case GGML_LOG_LEVEL_ERROR: lvl = "ERROR"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "[%-5s] %s", lvl, log);
|
||||||
|
fflush(stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int omni_load(const char *model_path, const char *codec_path, int use_fa,
|
||||||
|
int clamp_fp16) {
|
||||||
|
ggml_log_set(ggml_log_cb, nullptr);
|
||||||
|
ggml_backend_load_all();
|
||||||
|
|
||||||
|
if (!model_path || model_path[0] == '\0') {
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] ERROR: model_path is required\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (!codec_path || codec_path[0] == '\0') {
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] ERROR: codec_path is required\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ov_init_params p;
|
||||||
|
ov_init_default_params(&p);
|
||||||
|
p.model_path = model_path;
|
||||||
|
p.codec_path = codec_path;
|
||||||
|
p.use_fa = use_fa != 0;
|
||||||
|
p.clamp_fp16 = clamp_fp16 != 0;
|
||||||
|
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] Loading model=%s codec=%s\n", model_path,
|
||||||
|
codec_path);
|
||||||
|
|
||||||
|
g_ctx = ov_init(&p);
|
||||||
|
if (!g_ctx) {
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] FATAL: ov_init failed: %s\n",
|
||||||
|
ov_last_error());
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] Model loaded (%s)\n", ov_version());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill an ov_tts_params from the flat wrapper arguments.
|
||||||
|
static void fill_params(ov_tts_params *tp, const char *text, const char *lang,
|
||||||
|
const char *instruct, const float *ref_samples,
|
||||||
|
int ref_n, const char *ref_text, long long seed,
|
||||||
|
int denoise) {
|
||||||
|
ov_tts_default_params(tp);
|
||||||
|
tp->text = text ? text : "";
|
||||||
|
tp->lang = lang ? lang : "";
|
||||||
|
if (instruct && instruct[0] != '\0')
|
||||||
|
tp->instruct = instruct;
|
||||||
|
if (ref_samples && ref_n > 0) {
|
||||||
|
tp->ref_audio_24k = ref_samples;
|
||||||
|
tp->ref_n_samples = ref_n;
|
||||||
|
if (ref_text && ref_text[0] != '\0')
|
||||||
|
tp->ref_text = ref_text;
|
||||||
|
tp->denoise = denoise != 0;
|
||||||
|
}
|
||||||
|
if (seed >= 0)
|
||||||
|
tp->mg_seed = (uint64_t)seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
float *omni_tts(const char *text, const char *lang, const char *instruct,
|
||||||
|
const float *ref_samples, int ref_n, const char *ref_text,
|
||||||
|
long long seed, int denoise, int *out_n) {
|
||||||
|
if (out_n)
|
||||||
|
*out_n = 0;
|
||||||
|
if (!g_ctx) {
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] ERROR: model not loaded\n");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!text || text[0] == '\0') {
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] ERROR: text is required\n");
|
||||||
|
return nullptr; // omni_tts: out_n already 0
|
||||||
|
}
|
||||||
|
ov_tts_params tp;
|
||||||
|
fill_params(&tp, text, lang, instruct, ref_samples, ref_n, ref_text, seed,
|
||||||
|
denoise);
|
||||||
|
|
||||||
|
ov_audio out = {0};
|
||||||
|
enum ov_status rc = ov_synthesize(g_ctx, &tp, &out);
|
||||||
|
if (rc != OV_STATUS_OK || out.n_samples <= 0 || !out.samples) {
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] ERROR: synthesize failed (rc=%d): %s\n",
|
||||||
|
(int)rc, ov_last_error());
|
||||||
|
ov_audio_free(&out);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy into a plain malloc buffer the Go side can free symmetrically via
|
||||||
|
// omni_pcm_free; then release the ov_audio-owned buffer.
|
||||||
|
size_t bytes = (size_t)out.n_samples * sizeof(float);
|
||||||
|
float *buf = (float *)malloc(bytes);
|
||||||
|
if (!buf) {
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] ERROR: malloc(%zu) failed\n", bytes);
|
||||||
|
ov_audio_free(&out);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
memcpy(buf, out.samples, bytes);
|
||||||
|
if (out_n)
|
||||||
|
*out_n = out.n_samples;
|
||||||
|
ov_audio_free(&out);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
int omni_tts_stream(const char *text, const char *lang, const char *instruct,
|
||||||
|
const float *ref_samples, int ref_n, const char *ref_text,
|
||||||
|
long long seed, int denoise, omni_pcm_chunk_cb cb,
|
||||||
|
void *user_data) {
|
||||||
|
if (!g_ctx) {
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] ERROR: model not loaded\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (!cb) {
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] ERROR: stream callback is null\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (!text || text[0] == '\0') {
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] ERROR: text is required\n");
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
ov_tts_params tp;
|
||||||
|
fill_params(&tp, text, lang, instruct, ref_samples, ref_n, ref_text, seed,
|
||||||
|
denoise);
|
||||||
|
// ov_audio_chunk_cb has the identical signature to omni_pcm_chunk_cb
|
||||||
|
// (bool vs int return are ABI-compatible; non-zero == true).
|
||||||
|
tp.on_chunk = (ov_audio_chunk_cb)cb;
|
||||||
|
tp.on_chunk_user_data = user_data;
|
||||||
|
|
||||||
|
ov_audio out = {0}; // stays empty in streaming mode
|
||||||
|
enum ov_status rc = ov_synthesize(g_ctx, &tp, &out);
|
||||||
|
ov_audio_free(&out);
|
||||||
|
if (rc != OV_STATUS_OK && rc != OV_STATUS_CANCELLED) {
|
||||||
|
fprintf(stderr, "[omnivoice-cpp] ERROR: stream synth failed (rc=%d): %s\n",
|
||||||
|
(int)rc, ov_last_error());
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void omni_pcm_free(float *p) { free(p); }
|
||||||
|
|
||||||
|
void omni_unload(void) {
|
||||||
|
if (g_ctx) {
|
||||||
|
ov_free(g_ctx);
|
||||||
|
g_ctx = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user