|
| 1 | +#!/bin/bash |
| 2 | +# Checks how the run.sh of each C++ backend sets up the Intel graphics driver. |
| 3 | +# |
| 4 | +# A backend built for Intel GPUs carries its own copy of the Intel graphics |
| 5 | +# driver. run.sh has to tell Level Zero, which is how llama.cpp reaches the |
| 6 | +# card, to use that copy. Three things must hold, and all three have broken in |
| 7 | +# the past: |
| 8 | +# |
| 9 | +# 1. If the user already chose a driver, keep the user's choice. Otherwise a |
| 10 | +# machine with a graphics card too new for the carried driver stops |
| 11 | +# working, with no way to get back to the driver that did work. |
| 12 | +# 2. Say nothing about OpenCL. No OpenCL driver is carried, so pointing |
| 13 | +# OpenCL at the backend's own directory would leave it with no driver at |
| 14 | +# all, where saying nothing leaves it the machine's own. |
| 15 | +# 3. Ask the driver for the amount of free memory. Without this, llama.cpp |
| 16 | +# reads zero free memory on an integrated graphics chip, because such a |
| 17 | +# chip has no memory of its own and shares the system's. |
| 18 | +# |
| 19 | +# The test builds a fake backend directory for each run.sh, runs it, and reads |
| 20 | +# back the variables it exported. |
| 21 | +set -euo pipefail |
| 22 | + |
| 23 | +WORK=$(mktemp -d) |
| 24 | +trap 'rm -rf "$WORK"' EXIT |
| 25 | + |
| 26 | +REPO_ROOT=$(dirname "$(dirname "$(dirname "$(realpath "$0")")")") |
| 27 | + |
| 28 | +RUN_SCRIPTS=( |
| 29 | + "backend/cpp/llama-cpp/run.sh llama-cpp" |
| 30 | + "backend/cpp/turboquant/run.sh turboquant" |
| 31 | + "backend/cpp/bonsai/run.sh bonsai" |
| 32 | +) |
| 33 | + |
| 34 | +failures=0 |
| 35 | + |
| 36 | +fail() { |
| 37 | + echo "FAIL: $*" |
| 38 | + failures=$((failures + 1)) |
| 39 | +} |
| 40 | + |
| 41 | +# Builds a fake backend directory: the real run.sh, a stand-in for the backend |
| 42 | +# program that prints the variables we care about, and whichever libraries the |
| 43 | +# caller asked for. |
| 44 | +# |
| 45 | +# Usage: make_backend <dir> <program-prefix> [library ...] |
| 46 | +make_backend() { |
| 47 | + local dir="$1" prefix="$2" |
| 48 | + shift 2 |
| 49 | + |
| 50 | + mkdir -p "$dir/lib" |
| 51 | + cp "$RUN_SH" "$dir/run.sh" |
| 52 | + chmod +x "$dir/run.sh" |
| 53 | + |
| 54 | + local lib |
| 55 | + for lib in "$@"; do |
| 56 | + : > "$dir/lib/$lib" |
| 57 | + done |
| 58 | + |
| 59 | + cat > "$dir/${prefix}-fallback" <<'PROGRAM' |
| 60 | +#!/bin/bash |
| 61 | +echo "level_zero_driver=${ZE_ENABLE_ALT_DRIVERS:-}" |
| 62 | +echo "opencl_driver_list=${OCL_ICD_VENDORS:-}" |
| 63 | +echo "report_free_memory=${ZES_ENABLE_SYSMAN:-}" |
| 64 | +PROGRAM |
| 65 | + chmod +x "$dir/${prefix}-fallback" |
| 66 | +} |
| 67 | + |
| 68 | +# Runs a fake backend and prints the one variable asked for. |
| 69 | +# Usage: read_variable <dir> <name> |
| 70 | +read_variable() { |
| 71 | + local dir="$1" name="$2" |
| 72 | + bash "$dir/run.sh" 2>/dev/null | sed -n "s/^${name}=//p" |
| 73 | +} |
| 74 | + |
| 75 | +for entry in "${RUN_SCRIPTS[@]}"; do |
| 76 | + read -r script prefix <<< "$entry" |
| 77 | + RUN_SH="$REPO_ROOT/$script" |
| 78 | + |
| 79 | + if [ ! -f "$RUN_SH" ]; then |
| 80 | + fail "$script does not exist" |
| 81 | + continue |
| 82 | + fi |
| 83 | + |
| 84 | + # An Intel build with its own graphics driver: point Level Zero and OpenCL |
| 85 | + # at the bundled copies and ask for the free memory reading. |
| 86 | + bundled="$WORK/$prefix-bundled" |
| 87 | + make_backend "$bundled" "$prefix" \ |
| 88 | + libze_loader.so.1 libze_intel_gpu.so.1 libigdrcl.so |
| 89 | + mkdir -p "$bundled/etc/OpenCL/vendors" |
| 90 | + echo "libigdrcl.so" > "$bundled/etc/OpenCL/vendors/intel.icd" |
| 91 | + |
| 92 | + got=$(read_variable "$bundled" level_zero_driver) |
| 93 | + if [ "$got" != "$bundled/lib/libze_intel_gpu.so.1" ]; then |
| 94 | + fail "$script: expected Level Zero to use the bundled driver, got '$got'" |
| 95 | + fi |
| 96 | + |
| 97 | + # Even with an OpenCL driver and a driver list sitting in the backend, which |
| 98 | + # is what an older packaging left behind, OpenCL must be left alone. |
| 99 | + got=$(read_variable "$bundled" opencl_driver_list) |
| 100 | + if [ -n "$got" ]; then |
| 101 | + fail "$script: OpenCL was pointed at the backend's own directory ('$got')" |
| 102 | + fi |
| 103 | + |
| 104 | + got=$(read_variable "$bundled" report_free_memory) |
| 105 | + if [ "$got" != "1" ]; then |
| 106 | + fail "$script: expected the free memory reading to be turned on, got '$got'" |
| 107 | + fi |
| 108 | + |
| 109 | + # The user picked a driver already. Both choices must survive. |
| 110 | + got=$(ZE_ENABLE_ALT_DRIVERS=/usr/lib/host-driver.so \ |
| 111 | + read_variable "$bundled" level_zero_driver) |
| 112 | + if [ "$got" != "/usr/lib/host-driver.so" ]; then |
| 113 | + fail "$script: the user's Level Zero driver was overwritten with '$got'" |
| 114 | + fi |
| 115 | + |
| 116 | + got=$(ZES_ENABLE_SYSMAN=0 read_variable "$bundled" report_free_memory) |
| 117 | + if [ "$got" != "0" ]; then |
| 118 | + fail "$script: the user's free memory setting was overwritten with '$got'" |
| 119 | + fi |
| 120 | + |
| 121 | + # A build for some other kind of graphics card. None of the Intel |
| 122 | + # variables belong here. |
| 123 | + other="$WORK/$prefix-other" |
| 124 | + make_backend "$other" "$prefix" libcublas.so.12 |
| 125 | + |
| 126 | + for name in level_zero_driver opencl_driver_list report_free_memory; do |
| 127 | + got=$(read_variable "$other" "$name") |
| 128 | + if [ -n "$got" ]; then |
| 129 | + fail "$script: $name was set on a build with no Intel libraries ('$got')" |
| 130 | + fi |
| 131 | + done |
| 132 | +done |
| 133 | + |
| 134 | +if [ "$failures" -gt 0 ]; then |
| 135 | + echo "$failures check(s) failed" |
| 136 | + exit 1 |
| 137 | +fi |
| 138 | + |
| 139 | +echo "PASS: every run.sh sets up the Intel graphics driver correctly" |
0 commit comments