diff --git a/backend/cpp/audio-cpp/CMakeLists.txt b/backend/cpp/audio-cpp/CMakeLists.txt new file mode 100644 index 000000000..42e4e564a --- /dev/null +++ b/backend/cpp/audio-cpp/CMakeLists.txt @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.20) +project(audio-cpp-grpc-server LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(AUDIO_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/audio.cpp" + CACHE PATH "Path to the audio.cpp source tree") +set(LOCALAI_BACKEND_PROTO "${CMAKE_CURRENT_SOURCE_DIR}/../../backend.proto" + CACHE FILEPATH "Path to the LocalAI backend protocol") + +option(ENGINE_ENABLE_CUDA "Build audio.cpp with CUDA support" OFF) +option(ENGINE_ENABLE_VULKAN "Build audio.cpp with Vulkan support" OFF) +option(ENGINE_ENABLE_METAL "Build audio.cpp with Metal support" OFF) + +find_package(Protobuf CONFIG REQUIRED) +find_package(gRPC CONFIG REQUIRED) +find_program(PROTOC_EXECUTABLE NAMES protoc REQUIRED) +find_program(GRPC_CPP_PLUGIN_EXECUTABLE NAMES grpc_cpp_plugin REQUIRED) + +get_filename_component(LOCALAI_BACKEND_PROTO_DIR + "${LOCALAI_BACKEND_PROTO}" DIRECTORY) +set(LOCALAI_PROTO_SOURCES + "${CMAKE_CURRENT_BINARY_DIR}/backend.pb.cc" + "${CMAKE_CURRENT_BINARY_DIR}/backend.grpc.pb.cc") +set(LOCALAI_PROTO_HEADERS + "${CMAKE_CURRENT_BINARY_DIR}/backend.pb.h" + "${CMAKE_CURRENT_BINARY_DIR}/backend.grpc.pb.h") + +add_custom_command( + OUTPUT ${LOCALAI_PROTO_SOURCES} ${LOCALAI_PROTO_HEADERS} + COMMAND "${PROTOC_EXECUTABLE}" + ARGS + --cpp_out "${CMAKE_CURRENT_BINARY_DIR}" + --grpc_out "${CMAKE_CURRENT_BINARY_DIR}" + -I "${LOCALAI_BACKEND_PROTO_DIR}" + --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN_EXECUTABLE}" + "${LOCALAI_BACKEND_PROTO}" + DEPENDS "${LOCALAI_BACKEND_PROTO}" + VERBATIM) + +add_library(localai_backend_proto STATIC + ${LOCALAI_PROTO_SOURCES} + ${LOCALAI_PROTO_HEADERS}) +target_include_directories(localai_backend_proto + PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") +target_link_libraries(localai_backend_proto + PUBLIC protobuf::libprotobuf gRPC::grpc++) + +if(NOT EXISTS "${AUDIO_CPP_DIR}/CMakeLists.txt") + message(FATAL_ERROR + "AUDIO_CPP_DIR does not point to an audio.cpp source tree: ${AUDIO_CPP_DIR}") +endif() +add_subdirectory("${AUDIO_CPP_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/audio.cpp") + +# Task 2 replaces this generated entry point with the LocalAI service. +set(AUDIO_CPP_SERVER_PLACEHOLDER + "${CMAKE_CURRENT_BINARY_DIR}/audio-cpp-grpc-server-placeholder.cpp") +file(GENERATE OUTPUT "${AUDIO_CPP_SERVER_PLACEHOLDER}" + CONTENT "int main() { return 0; }\n") + +add_executable(audio-cpp-grpc-server "${AUDIO_CPP_SERVER_PLACEHOLDER}") +target_link_libraries(audio-cpp-grpc-server PRIVATE + engine_runtime + localai_backend_proto + gRPC::grpc++ + gRPC::grpc++_reflection) diff --git a/backend/cpp/audio-cpp/Makefile b/backend/cpp/audio-cpp/Makefile new file mode 100644 index 000000000..b474d3fe5 --- /dev/null +++ b/backend/cpp/audio-cpp/Makefile @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: MIT + +AUDIO_CPP_VERSION?=f8fb0c19739193adfad0d9e58da99f25eda65256 +AUDIO_CPP_REPO?=https://github.com/0xShug0/audio.cpp +AUDIO_CPP_SRC?= + +CURRENT_MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +BUILD_DIR := build + +BUILD_TYPE ?= +JOBS ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) +UNAME_S := $(shell uname -s) + +CMAKE_ARGS ?= -DCMAKE_BUILD_TYPE=Release +CMAKE_ARGS += -DENGINE_ENABLE_CUDA=OFF +CMAKE_ARGS += -DENGINE_ENABLE_VULKAN=OFF +CMAKE_ARGS += -DENGINE_ENABLE_METAL=OFF + +ifeq ($(BUILD_TYPE),cublas) + CMAKE_ARGS += -DENGINE_ENABLE_CUDA=ON +else ifeq ($(BUILD_TYPE),vulkan) + CMAKE_ARGS += -DENGINE_ENABLE_VULKAN=ON +else ifeq ($(UNAME_S),Darwin) + CMAKE_ARGS += -DENGINE_ENABLE_METAL=ON +endif + +.PHONY: all grpc-server test clean purge + +all: grpc-server + +audio.cpp: +ifneq ($(AUDIO_CPP_SRC),) + ln -sfn $(abspath $(AUDIO_CPP_SRC)) audio.cpp +else + mkdir -p audio.cpp + cd audio.cpp && \ + git init -q && \ + git remote add origin $(AUDIO_CPP_REPO) && \ + git fetch --depth 1 origin $(AUDIO_CPP_VERSION) && \ + git checkout --detach FETCH_HEAD && \ + git submodule update --init --recursive --depth 1 +endif + +grpc-server: audio.cpp + mkdir -p $(BUILD_DIR) + cd $(BUILD_DIR) && cmake $(CMAKE_ARGS) $(CURRENT_MAKEFILE_DIR) + cmake --build $(BUILD_DIR) --config Release \ + --target audio-cpp-grpc-server -j $(JOBS) + cp $(BUILD_DIR)/audio-cpp-grpc-server grpc-server + +test: + bash tests/build_contract_test.sh + +clean: + rm -rf $(BUILD_DIR) grpc-server + +purge: clean + rm -rf audio.cpp diff --git a/backend/cpp/audio-cpp/tests/build_contract_test.sh b/backend/cpp/audio-cpp/tests/build_contract_test.sh new file mode 100755 index 000000000..02df3fba1 --- /dev/null +++ b/backend/cpp/audio-cpp/tests/build_contract_test.sh @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MIT + +set -euo pipefail + +backend_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +tmp_dir="$(mktemp -d)" +trap 'rm -rf "${tmp_dir}"' EXIT + +fixture_dir="${tmp_dir}/audio.cpp" +prefix_dir="${tmp_dir}/prefix" +tools_dir="${tmp_dir}/tools" +mkdir -p "${fixture_dir}" "${prefix_dir}/lib/cmake/Protobuf" \ + "${prefix_dir}/lib/cmake/gRPC" "${tools_dir}" + +cat >"${fixture_dir}/engine_runtime.cpp" <<'EOF' +void audio_cpp_build_contract_fixture() {} +EOF + +cat >"${fixture_dir}/CMakeLists.txt" <<'EOF' +cmake_minimum_required(VERSION 3.20) +project(AudioCppBuildContractFixture LANGUAGES CXX) + +option(ENGINE_ENABLE_CUDA "Build with CUDA" OFF) +option(ENGINE_ENABLE_VULKAN "Build with Vulkan" OFF) +option(ENGINE_ENABLE_METAL "Build with Metal" OFF) + +foreach(wrong_option IN ITEMS + AUDIO_CPP_ENABLE_CUDA + AUDIO_CPP_ENABLE_VULKAN + AUDIO_CPP_ENABLE_METAL + AUDIOCPP_ENABLE_CUDA + AUDIOCPP_ENABLE_VULKAN + AUDIOCPP_ENABLE_METAL + ENGINE_CUDA + ENGINE_VULKAN + ENGINE_METAL + GGML_CUDA + GGML_VULKAN + GGML_METAL) + if(DEFINED ${wrong_option}) + message(FATAL_ERROR "legacy or unsupported audio.cpp option: ${wrong_option}") + endif() +endforeach() + +add_library(engine_runtime STATIC engine_runtime.cpp) +EOF + +cat >"${prefix_dir}/lib/cmake/Protobuf/ProtobufConfig.cmake" <<'EOF' +set(Protobuf_FOUND TRUE) +set(Protobuf_VERSION 0.0.0) +if(NOT TARGET protobuf::libprotobuf) + add_library(protobuf::libprotobuf INTERFACE IMPORTED) +endif() +EOF + +cat >"${prefix_dir}/lib/cmake/gRPC/gRPCConfig.cmake" <<'EOF' +set(gRPC_FOUND TRUE) +if(NOT TARGET gRPC::grpc++) + add_library(gRPC::grpc++ INTERFACE IMPORTED) +endif() +if(NOT TARGET gRPC::grpc++_reflection) + add_library(gRPC::grpc++_reflection INTERFACE IMPORTED) +endif() +EOF + +cat >"${tools_dir}/protoc" <<'EOF' +#!/usr/bin/env sh +exit 0 +EOF +cat >"${tools_dir}/grpc_cpp_plugin" <<'EOF' +#!/usr/bin/env sh +exit 0 +EOF +chmod +x "${tools_dir}/protoc" "${tools_dir}/grpc_cpp_plugin" + +assert_cache_bool() { + local cache_file="$1" + local name="$2" + local expected="$3" + grep -q "^${name}:BOOL=${expected}$" "${cache_file}" || { + echo "expected ${name}:BOOL=${expected} in ${cache_file}" >&2 + return 1 + } +} + +configure_case() { + local name="$1" + local cuda="$2" + local vulkan="$3" + local metal="$4" + local build_dir="${tmp_dir}/build-${name}" + + PATH="${tools_dir}:${PATH}" cmake \ + -S "${backend_dir}" \ + -B "${build_dir}" \ + -DCMAKE_PREFIX_PATH="${prefix_dir}" \ + -DAUDIO_CPP_DIR="${fixture_dir}" \ + -DENGINE_ENABLE_CUDA="${cuda}" \ + -DENGINE_ENABLE_VULKAN="${vulkan}" \ + -DENGINE_ENABLE_METAL="${metal}" \ + >/dev/null + + assert_cache_bool "${build_dir}/CMakeCache.txt" ENGINE_ENABLE_CUDA "${cuda}" + assert_cache_bool "${build_dir}/CMakeCache.txt" ENGINE_ENABLE_VULKAN "${vulkan}" + assert_cache_bool "${build_dir}/CMakeCache.txt" ENGINE_ENABLE_METAL "${metal}" + + grep -q 'engine_runtime' \ + "${build_dir}/CMakeFiles/audio-cpp-grpc-server.dir/link.txt" || { + echo "audio-cpp-grpc-server does not link engine_runtime" >&2 + return 1 + } +} + +configure_case cpu OFF OFF OFF +configure_case cuda ON OFF OFF +configure_case vulkan OFF ON OFF +configure_case metal OFF OFF ON + +if PATH="${tools_dir}:${PATH}" cmake \ + -S "${backend_dir}" \ + -B "${tmp_dir}/build-wrong-option" \ + -DCMAKE_PREFIX_PATH="${prefix_dir}" \ + -DAUDIO_CPP_DIR="${fixture_dir}" \ + -DGGML_CUDA=ON \ + >/dev/null 2>&1; then + echo "strict audio.cpp fixture accepted legacy GGML_CUDA option" >&2 + exit 1 +fi + +make_database="${tmp_dir}/make-database" +make -C "${backend_dir}" -pn >"${make_database}" +audio_cpp_version="$( + sed -n 's/^AUDIO_CPP_VERSION = //p' "${make_database}" | head -n 1 +)" +[[ "${audio_cpp_version}" =~ ^[0-9a-f]{40}$ ]] || { + echo "AUDIO_CPP_VERSION must be a pinned 40-character commit" >&2 + exit 1 +} + +fetch_plan="${tmp_dir}/fetch-plan" +make -C "${backend_dir}" -n audio.cpp >"${fetch_plan}" +grep -q 'github.com/0xShug0/audio.cpp' "${fetch_plan}" +grep -q "${audio_cpp_version}" "${fetch_plan}" + +cat >"${tools_dir}/uname" <<'EOF' +#!/usr/bin/env sh +if [ "$#" -eq 1 ] && [ "$1" = "-s" ]; then + echo Darwin + exit 0 +fi +echo "build contract requires uname -s" >&2 +exit 64 +EOF +chmod +x "${tools_dir}/uname" + +darwin_plan="${tmp_dir}/darwin-plan" +PATH="${tools_dir}:${PATH}" make -C "${backend_dir}" -n \ + AUDIO_CPP_SRC="${fixture_dir}" grpc-server >"${darwin_plan}" +grep -q -- '-DENGINE_ENABLE_CUDA=OFF' "${darwin_plan}" +grep -q -- '-DENGINE_ENABLE_VULKAN=OFF' "${darwin_plan}" +grep -q -- '-DENGINE_ENABLE_METAL=ON' "${darwin_plan}" + +echo "audio.cpp build contract: PASS"