Spaces:
Running
Running
Amlan-109
feat: Initial commit of LocalAI Amlan Edition with premium branding and personalization
750bbe6
| set -euo pipefail | |
| # | |
| # use the library by adding the following line to a script: | |
| # source $(dirname $0)/../common/libbackend.sh | |
| # | |
| # If you want to limit what targets a backend can be used on, set the variable LIMIT_TARGETS to a | |
| # space separated list of valid targets BEFORE sourcing the library, for example to only allow a backend | |
| # to be used on CUDA and CPU backends: | |
| # | |
| # LIMIT_TARGETS="cublas cpu" | |
| # source $(dirname $0)/../common/libbackend.sh | |
| # | |
| # You can use any valid BUILD_TYPE or BUILD_PROFILE, if you need to limit a backend to CUDA 12 only: | |
| # | |
| # LIMIT_TARGETS="cublas12" | |
| # source $(dirname $0)/../common/libbackend.sh | |
| # | |
| # You can switch between uv (conda-like) and pip installation methods by setting USE_PIP: | |
| # USE_PIP=true source $(dirname $0)/../common/libbackend.sh | |
| # | |
| # ===================== user-configurable defaults ===================== | |
| PYTHON_VERSION="${PYTHON_VERSION:-3.10}" # e.g. 3.10 / 3.11 / 3.12 / 3.13 | |
| PYTHON_PATCH="${PYTHON_PATCH:-18}" # e.g. 18 -> 3.10.18 ; 13 -> 3.11.13 | |
| PY_STANDALONE_TAG="${PY_STANDALONE_TAG:-20250818}" # release tag date | |
| # Enable/disable bundling of a portable Python build | |
| PORTABLE_PYTHON="${PORTABLE_PYTHON:-false}" | |
| # If you want to fully pin the filename (including tuned CPU targets), set: | |
| # PORTABLE_PY_FILENAME="cpython-3.10.18+20250818-x86_64_v3-unknown-linux-gnu-install_only.tar.gz" | |
| : "${PORTABLE_PY_FILENAME:=}" | |
| : "${PORTABLE_PY_SHA256:=}" # optional; if set we verify the download | |
| # ===================================================================== | |
| # Default to uv if USE_PIP is not set | |
| if [ "x${USE_PIP:-}" == "x" ]; then | |
| USE_PIP=false | |
| fi | |
| # ----------------------- helpers ----------------------- | |
| function _is_musl() { | |
| # detect musl (Alpine, etc) | |
| if command -v ldd >/dev/null 2>&1; then | |
| ldd --version 2>&1 | grep -qi musl && return 0 | |
| fi | |
| # busybox-ish fallback | |
| if command -v getconf >/dev/null 2>&1; then | |
| getconf GNU_LIBC_VERSION >/dev/null 2>&1 || return 0 | |
| fi | |
| return 1 | |
| } | |
| function _triple() { | |
| local os="" arch="" libc="gnu" | |
| case "$(uname -s)" in | |
| Linux*) os="unknown-linux" ;; | |
| Darwin*) os="apple-darwin" ;; | |
| MINGW*|MSYS*|CYGWIN*) os="pc-windows-msvc" ;; # best-effort for Git Bash | |
| *) echo "Unsupported OS $(uname -s)"; exit 1;; | |
| esac | |
| case "$(uname -m)" in | |
| x86_64) arch="x86_64" ;; | |
| aarch64|arm64) arch="aarch64" ;; | |
| armv7l) arch="armv7" ;; | |
| i686|i386) arch="i686" ;; | |
| ppc64le) arch="ppc64le" ;; | |
| s390x) arch="s390x" ;; | |
| riscv64) arch="riscv64" ;; | |
| *) echo "Unsupported arch $(uname -m)"; exit 1;; | |
| esac | |
| if [[ "$os" == "unknown-linux" ]]; then | |
| if _is_musl; then | |
| libc="musl" | |
| else | |
| libc="gnu" | |
| fi | |
| echo "${arch}-${os}-${libc}" | |
| else | |
| echo "${arch}-${os}" | |
| fi | |
| } | |
| function _portable_dir() { | |
| echo "${EDIR}/python" | |
| } | |
| function _portable_bin() { | |
| # python-build-standalone puts python in ./bin | |
| echo "$(_portable_dir)/bin" | |
| } | |
| function _portable_python() { | |
| if [ -x "$(_portable_bin)/python3" ]; then | |
| echo "$(_portable_bin)/python3" | |
| else | |
| echo "$(_portable_bin)/python" | |
| fi | |
| } | |
| # macOS loader env for the portable CPython | |
| _macosPortableEnv() { | |
| if [ "$(uname -s)" = "Darwin" ]; then | |
| export DYLD_LIBRARY_PATH="$(_portable_dir)/lib${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}" | |
| export DYLD_FALLBACK_LIBRARY_PATH="$(_portable_dir)/lib${DYLD_FALLBACK_LIBRARY_PATH:+:${DYLD_FALLBACK_LIBRARY_PATH}}" | |
| fi | |
| } | |
| # Good hygiene on macOS for downloaded/extracted trees | |
| _unquarantinePortablePython() { | |
| if [ "$(uname -s)" = "Darwin" ]; then | |
| command -v xattr >/dev/null 2>&1 && xattr -dr com.apple.quarantine "$(_portable_dir)" || true | |
| fi | |
| } | |
| # ------------------ ### PORTABLE PYTHON ------------------ | |
| function ensurePortablePython() { | |
| local pdir="$(_portable_dir)" | |
| local pbin="$(_portable_bin)" | |
| local pyexe | |
| if [ -x "${pbin}/python3" ] || [ -x "${pbin}/python" ]; then | |
| _macosPortableEnv | |
| return 0 | |
| fi | |
| mkdir -p "${pdir}" | |
| local triple="$(_triple)" | |
| local full_ver="${PYTHON_VERSION}.${PYTHON_PATCH}" | |
| local fn="" | |
| if [ -n "${PORTABLE_PY_FILENAME}" ]; then | |
| fn="${PORTABLE_PY_FILENAME}" | |
| else | |
| # generic asset name: cpython-<full_ver>+<tag>-<triple>-install_only.tar.gz | |
| fn="cpython-${full_ver}+${PY_STANDALONE_TAG}-${triple}-install_only.tar.gz" | |
| fi | |
| local url="https://github.com/astral-sh/python-build-standalone/releases/download/${PY_STANDALONE_TAG}/${fn}" | |
| local tmp="${pdir}/${fn}" | |
| echo "Downloading portable Python: ${fn}" | |
| # curl with retries; fall back to wget if needed | |
| if command -v curl >/dev/null 2>&1; then | |
| curl -L --fail --retry 3 --retry-delay 1 -o "${tmp}" "${url}" | |
| else | |
| wget -O "${tmp}" "${url}" | |
| fi | |
| if [ -n "${PORTABLE_PY_SHA256}" ]; then | |
| echo "${PORTABLE_PY_SHA256} ${tmp}" | sha256sum -c - | |
| fi | |
| echo "Extracting ${fn} -> ${pdir}" | |
| # always a .tar.gz (we purposely choose install_only) | |
| tar -xzf "${tmp}" -C "${pdir}" | |
| rm -f "${tmp}" | |
| # Some archives nest a directory; if so, flatten to ${pdir} | |
| # Find the first dir with a 'bin/python*' | |
| local inner | |
| inner="$(find "${pdir}" -type f -path "*/bin/python*" -maxdepth 3 2>/dev/null | head -n1 || true)" | |
| if [ -n "${inner}" ]; then | |
| local inner_root | |
| inner_root="$(dirname "$(dirname "${inner}")")" # .../bin -> root | |
| if [ "${inner_root}" != "${pdir}" ]; then | |
| # move contents up one level | |
| shopt -s dotglob | |
| mv "${inner_root}/"* "${pdir}/" | |
| rm -rf "${inner_root}" | |
| shopt -u dotglob | |
| fi | |
| fi | |
| _unquarantinePortablePython | |
| _macosPortableEnv | |
| # Make sure it's runnable | |
| pyexe="$(_portable_python)" | |
| "${pyexe}" -V | |
| } | |
| # init handles the setup of the library | |
| function init() { | |
| BACKEND_NAME=${PWD##*/} | |
| MY_DIR=$(realpath "$(dirname "$0")") | |
| BUILD_PROFILE=$(getBuildProfile) | |
| EDIR=${MY_DIR} | |
| if [ "x${ENV_DIR:-}" != "x" ]; then | |
| EDIR=${ENV_DIR} | |
| fi | |
| if [ ! -z "${LIMIT_TARGETS:-}" ]; then | |
| isValidTarget=$(checkTargets ${LIMIT_TARGETS}) | |
| if [ ${isValidTarget} != true ]; then | |
| echo "${BACKEND_NAME} can only be used on the following targets: ${LIMIT_TARGETS}" | |
| exit 0 | |
| fi | |
| fi | |
| echo "Initializing libbackend for ${BACKEND_NAME}" | |
| } | |
| # getBuildProfile will inspect the system to determine which build profile is appropriate: | |
| # returns one of the following: | |
| # - cublas12 | |
| # - cublas13 | |
| # - hipblas | |
| # - intel | |
| function getBuildProfile() { | |
| if [ x"${BUILD_TYPE:-}" == "xcublas" ] || [ x"${BUILD_TYPE:-}" == "xl4t" ]; then | |
| if [ ! -z "${CUDA_MAJOR_VERSION:-}" ]; then | |
| echo ${BUILD_TYPE}${CUDA_MAJOR_VERSION} | |
| else | |
| echo ${BUILD_TYPE} | |
| fi | |
| return 0 | |
| fi | |
| if [ -d "/opt/intel" ]; then | |
| echo "intel" | |
| return 0 | |
| fi | |
| if [ -n "${BUILD_TYPE:-}" ]; then | |
| echo ${BUILD_TYPE} | |
| return 0 | |
| fi | |
| echo "cpu" | |
| } | |
| # Make the venv relocatable: | |
| # - rewrite venv/bin/python{,3} to relative symlinks into $(_portable_dir) | |
| # - normalize entrypoint shebangs to /usr/bin/env python3 | |
| # - optionally update pyvenv.cfg to point to the portable Python directory (only at runtime) | |
| # Usage: _makeVenvPortable [--update-pyvenv-cfg] | |
| _makeVenvPortable() { | |
| local update_pyvenv_cfg=false | |
| if [ "${1:-}" = "--update-pyvenv-cfg" ]; then | |
| update_pyvenv_cfg=true | |
| fi | |
| local venv_dir="${EDIR}/venv" | |
| local vbin="${venv_dir}/bin" | |
| [ -d "${vbin}" ] || return 0 | |
| # 1) Replace python symlinks with relative ones to ../../python/bin/python3 | |
| # (venv/bin -> venv -> EDIR -> python/bin) | |
| local rel_py='../../python/bin/python3' | |
| for name in python3 python; do | |
| if [ -e "${vbin}/${name}" ] || [ -L "${vbin}/${name}" ]; then | |
| rm -f "${vbin}/${name}" | |
| fi | |
| done | |
| ln -s "${rel_py}" "${vbin}/python3" | |
| ln -s "python3" "${vbin}/python" | |
| # 2) Update pyvenv.cfg to point to the portable Python directory (only at runtime) | |
| # Use absolute path resolved at runtime so it works when the venv is copied | |
| if [ "$update_pyvenv_cfg" = "true" ]; then | |
| local pyvenv_cfg="${venv_dir}/pyvenv.cfg" | |
| if [ -f "${pyvenv_cfg}" ]; then | |
| local portable_dir="$(_portable_dir)" | |
| # Resolve to absolute path - this ensures it works when the backend is copied | |
| # Only resolve if the directory exists (it should if ensurePortablePython was called) | |
| if [ -d "${portable_dir}" ]; then | |
| portable_dir="$(cd "${portable_dir}" && pwd)" | |
| else | |
| # Fallback to relative path if directory doesn't exist yet | |
| portable_dir="../python" | |
| fi | |
| local sed_i=(sed -i) | |
| # macOS/BSD sed needs a backup suffix; GNU sed doesn't. Make it portable: | |
| if sed --version >/dev/null 2>&1; then | |
| sed_i=(sed -i) | |
| else | |
| sed_i=(sed -i '') | |
| fi | |
| # Update the home field in pyvenv.cfg | |
| # Handle both absolute paths (starting with /) and relative paths | |
| if grep -q "^home = " "${pyvenv_cfg}"; then | |
| "${sed_i[@]}" "s|^home = .*|home = ${portable_dir}|" "${pyvenv_cfg}" | |
| else | |
| # If home field doesn't exist, add it | |
| echo "home = ${portable_dir}" >> "${pyvenv_cfg}" | |
| fi | |
| fi | |
| fi | |
| # 3) Rewrite shebangs of entry points to use env, so the venv is relocatable | |
| # Only touch text files that start with #! and reference the current venv. | |
| local ve_abs="${vbin}/python" | |
| local sed_i=(sed -i) | |
| # macOS/BSD sed needs a backup suffix; GNU sed doesn't. Make it portable: | |
| if sed --version >/dev/null 2>&1; then | |
| sed_i=(sed -i) | |
| else | |
| sed_i=(sed -i '') | |
| fi | |
| for f in "${vbin}"/*; do | |
| [ -f "$f" ] || continue | |
| # Fast path: check first two bytes (#!) | |
| head -c2 "$f" 2>/dev/null | grep -q '^#!' || continue | |
| # Only rewrite if the shebang mentions the (absolute) venv python | |
| if head -n1 "$f" | grep -Fq "${ve_abs}"; then | |
| "${sed_i[@]}" '1s|^#!.*$|#!/usr/bin/env python3|' "$f" | |
| chmod +x "$f" 2>/dev/null || true | |
| fi | |
| done | |
| } | |
| # ensureVenv makes sure that the venv for the backend both exists, and is activated. | |
| # | |
| # This function is idempotent, so you can call it as many times as you want and it will | |
| # always result in an activated virtual environment | |
| function ensureVenv() { | |
| local interpreter="" | |
| if [ "x${PORTABLE_PYTHON}" == "xtrue" ] || [ -e "$(_portable_python)" ]; then | |
| echo "Using portable Python" | |
| ensurePortablePython | |
| interpreter="$(_portable_python)" | |
| else | |
| # Prefer system python${PYTHON_VERSION}, else python3, else fall back to bundled | |
| if command -v python${PYTHON_VERSION} >/dev/null 2>&1; then | |
| interpreter="python${PYTHON_VERSION}" | |
| elif command -v python3 >/dev/null 2>&1; then | |
| interpreter="python3" | |
| else | |
| echo "No suitable system Python found, bootstrapping portable build..." | |
| ensurePortablePython | |
| interpreter="$(_portable_python)" | |
| fi | |
| fi | |
| if [ ! -d "${EDIR}/venv" ]; then | |
| if [ "x${USE_PIP}" == "xtrue" ]; then | |
| "${interpreter}" -m venv --copies "${EDIR}/venv" | |
| source "${EDIR}/venv/bin/activate" | |
| "${interpreter}" -m pip install --upgrade pip | |
| else | |
| if [ "x${PORTABLE_PYTHON}" == "xtrue" ]; then | |
| uv venv --python "${interpreter}" "${EDIR}/venv" | |
| else | |
| uv venv --python "${PYTHON_VERSION}" "${EDIR}/venv" | |
| fi | |
| fi | |
| if [ "x${PORTABLE_PYTHON}" == "xtrue" ]; then | |
| # During install, only update symlinks and shebangs, not pyvenv.cfg | |
| _makeVenvPortable | |
| fi | |
| fi | |
| # We call it here to make sure that when we source a venv we can still use python as expected | |
| if [ -x "$(_portable_python)" ]; then | |
| _macosPortableEnv | |
| fi | |
| if [ "x${VIRTUAL_ENV:-}" != "x${EDIR}/venv" ]; then | |
| source "${EDIR}/venv/bin/activate" | |
| fi | |
| } | |
| function runProtogen() { | |
| ensureVenv | |
| if [ "x${USE_PIP}" == "xtrue" ]; then | |
| pip install grpcio-tools | |
| else | |
| uv pip install grpcio-tools | |
| fi | |
| pushd "${EDIR}" >/dev/null | |
| # use the venv python (ensures correct interpreter & sys.path) | |
| python -m grpc_tools.protoc -I../../ -I./ --python_out=. --grpc_python_out=. backend.proto | |
| popd >/dev/null | |
| } | |
| # installRequirements looks for several requirements files and if they exist runs the install for them in order | |
| # | |
| # - requirements-install.txt | |
| # - requirements.txt | |
| # - requirements-${BUILD_TYPE}.txt | |
| # - requirements-${BUILD_PROFILE}.txt | |
| # | |
| # BUILD_PROFILE is a more specific version of BUILD_TYPE, ex: cuda-12 or cuda-13 | |
| # it can also include some options that we do not have BUILD_TYPES for, ex: intel | |
| # | |
| # NOTE: for BUILD_PROFILE==intel, this function does NOT automatically use the Intel python package index. | |
| # you may want to add the following line to a requirements-intel.txt if you use one: | |
| # | |
| # --index-url https://download.pytorch.org/whl/xpu | |
| # | |
| # If you need to add extra flags into the pip install command you can do so by setting the variable EXTRA_PIP_INSTALL_FLAGS | |
| # before calling installRequirements. For example: | |
| # | |
| # source $(dirname $0)/../common/libbackend.sh | |
| # EXTRA_PIP_INSTALL_FLAGS="--no-build-isolation" | |
| # installRequirements | |
| function installRequirements() { | |
| ensureVenv | |
| declare -a requirementFiles=( | |
| "${EDIR}/requirements-install.txt" | |
| "${EDIR}/requirements.txt" | |
| "${EDIR}/requirements-${BUILD_TYPE:-}.txt" | |
| ) | |
| if [ "x${BUILD_TYPE:-}" != "x${BUILD_PROFILE}" ]; then | |
| requirementFiles+=("${EDIR}/requirements-${BUILD_PROFILE}.txt") | |
| fi | |
| if [ "x${BUILD_TYPE:-}" == "x" ]; then | |
| requirementFiles+=("${EDIR}/requirements-cpu.txt") | |
| fi | |
| requirementFiles+=("${EDIR}/requirements-after.txt") | |
| if [ "x${BUILD_TYPE:-}" != "x${BUILD_PROFILE}" ]; then | |
| requirementFiles+=("${EDIR}/requirements-${BUILD_PROFILE}-after.txt") | |
| fi | |
| # This is needed to build wheels that e.g. depends on Python.h | |
| if [ "x${PORTABLE_PYTHON}" == "xtrue" ]; then | |
| export C_INCLUDE_PATH="${C_INCLUDE_PATH:-}:$(_portable_dir)/include/python${PYTHON_VERSION}" | |
| fi | |
| for reqFile in ${requirementFiles[@]}; do | |
| if [ -f "${reqFile}" ]; then | |
| echo "starting requirements install for ${reqFile}" | |
| if [ "x${USE_PIP}" == "xtrue" ]; then | |
| pip install ${EXTRA_PIP_INSTALL_FLAGS:-} --requirement "${reqFile}" | |
| else | |
| uv pip install ${EXTRA_PIP_INSTALL_FLAGS:-} --requirement "${reqFile}" | |
| fi | |
| echo "finished requirements install for ${reqFile}" | |
| fi | |
| done | |
| runProtogen | |
| } | |
| # startBackend discovers and runs the backend GRPC server | |
| # | |
| # You can specify a specific backend file to execute by setting BACKEND_FILE before calling startBackend. | |
| # example: | |
| # | |
| # source ../common/libbackend.sh | |
| # BACKEND_FILE="${MY_DIR}/source/backend.py" | |
| # startBackend $@ | |
| # | |
| # valid filenames for autodiscovered backend servers are: | |
| # - server.py | |
| # - backend.py | |
| # - ${BACKEND_NAME}.py | |
| function startBackend() { | |
| ensureVenv | |
| # Update pyvenv.cfg before running to ensure paths are correct for current location | |
| # This is critical when the backend position is dynamic (e.g., copied from container) | |
| if [ "x${PORTABLE_PYTHON}" == "xtrue" ] || [ -x "$(_portable_python)" ]; then | |
| _makeVenvPortable --update-pyvenv-cfg | |
| fi | |
| # Set up GPU library paths if a lib directory exists | |
| # This allows backends to include their own GPU libraries (CUDA, ROCm, etc.) | |
| if [ -d "${EDIR}/lib" ]; then | |
| export LD_LIBRARY_PATH="${EDIR}/lib:${LD_LIBRARY_PATH:-}" | |
| echo "Added ${EDIR}/lib to LD_LIBRARY_PATH for GPU libraries" | |
| fi | |
| if [ ! -z "${BACKEND_FILE:-}" ]; then | |
| exec "${EDIR}/venv/bin/python" "${BACKEND_FILE}" "$@" | |
| elif [ -e "${MY_DIR}/server.py" ]; then | |
| exec "${EDIR}/venv/bin/python" "${MY_DIR}/server.py" "$@" | |
| elif [ -e "${MY_DIR}/backend.py" ]; then | |
| exec "${EDIR}/venv/bin/python" "${MY_DIR}/backend.py" "$@" | |
| elif [ -e "${MY_DIR}/${BACKEND_NAME}.py" ]; then | |
| exec "${EDIR}/venv/bin/python" "${MY_DIR}/${BACKEND_NAME}.py" "$@" | |
| fi | |
| } | |
| # runUnittests discovers and runs python unittests | |
| # | |
| # You can specify a specific test file to use by setting TEST_FILE before calling runUnittests. | |
| # example: | |
| # | |
| # source ../common/libbackend.sh | |
| # TEST_FILE="${MY_DIR}/source/test.py" | |
| # runUnittests $@ | |
| # | |
| # be default a file named test.py in the backends directory will be used | |
| function runUnittests() { | |
| ensureVenv | |
| if [ ! -z "${TEST_FILE:-}" ]; then | |
| testDir=$(dirname "$(realpath "${TEST_FILE}")") | |
| testFile=$(basename "${TEST_FILE}") | |
| pushd "${testDir}" >/dev/null | |
| python -m unittest "${testFile}" | |
| popd >/dev/null | |
| elif [ -f "${MY_DIR}/test.py" ]; then | |
| pushd "${MY_DIR}" >/dev/null | |
| python -m unittest test.py | |
| popd >/dev/null | |
| else | |
| echo "no tests defined for ${BACKEND_NAME}" | |
| fi | |
| } | |
| ################################################################################## | |
| # Below here are helper functions not intended to be used outside of the library # | |
| ################################################################################## | |
| # checkTargets determines if the current BUILD_TYPE or BUILD_PROFILE is in a list of valid targets | |
| function checkTargets() { | |
| targets=$@ | |
| declare -a targets=($targets) | |
| for target in ${targets[@]}; do | |
| if [ "x${BUILD_TYPE:-}" == "x${target}" ]; then | |
| echo true; return 0 | |
| fi | |
| if [ "x${BUILD_PROFILE}" == "x${target}" ]; then | |
| echo true; return 0 | |
| fi | |
| done | |
| echo false | |
| } | |
| init | |