Tesla K80 for Local Inference

The less interesting part.

1970-01-01

* Contents

* Info

This is the companion piece of "Private AI for U$70".

The main reason the Tesla K80 is cheap is because it is obsolete. Not necessarily in performance, as it is quite good in that regard (with tempered expectations), but is sorely lacking in modern support. This post contains the dry information not quite suited for the main post.

* Components

The Tesla K80, being designed for server use at fixed fan speeds, has neither fans nor hookups for them. Additionally, the power connector on the card expects an EPS-12V (8 pin) connection, whereas the VGA/PCIe cables typically available on consumer power supplies are basically pinned oppositely.

The PCIe power cable may fit, but you will fry your GPU if you do not use an appropriate adapter.

** Bill of Materials

ItemEst. Cost
NVIDIA Tesla K80$60USD
12V DC blower fan$10USD
EPS-12V to PCIe power adapter$0-10USD
Access to a 3D printer$0USD
(Optional) PWM Fan Controller$10USD
Total$70-90USD

Many listings include the power adapter, and the PWM fan controller is optional so long as you don't mind some additional noise.

** Assembly

You need to 3D-print the fan mount bracket, or have it printed for you. It should be printed in a heat-resistant filament like ABS or PETG. PLA will warp over time due to the heat; PETG is resistant enough to heat that it should be no issue

Everything should fit into place if printed on a reasonable printer. In case your needs are different, the design files are available. FreeCAD was used, so it is similarly free to actually perform the modifications you may need.

* Drivers

Depending on your distro, drivers range from annoying to impossible to install. Read your distribution's guide to installing NVIDIA drivers 470.256.02-r2. The proprietary drivers are your only choice unfortunately, which limits compatibility to Linux 6.6 at maximum.

In my case, the proprietary drivers require

Which on Gentoo, requires some modification to allow installation.

FILE package.accept_keywords
# Latest CUDA for GK210
=dev-util/nvidia-cuda-toolkit-11.8.0-r4 ~amd64

# HIP support
sci-libs/hip* ~amd64
sci-libs/miopen ~amd64
dev-util/hip* ~amd64
dev-util/Tensile ~amd64
dev-libs/half ~amd64
dev-libs/hipother ~amd64
llvm-core/* ~amd64
llvm-runtimes/* ~amd64
FILE package.unmask
# Nvidia 470 for GK210
~x11-drivers/nvidia-drivers-470.256.02
FILE package.use
# NVIDIA 470 drivers
*/* VIDEO_CARDS: nvidia
*/* nvidia vdpau nvenc
x11-drivers/nvidia-drivers -kernel-open persistenced

# If you have an AMD GPU in the system as well:
dev-util/hip -video_cards_amdgpu

Install your Linux 6.6 of choice, select the kernel in eselect, run emerge =x11-drivers/nvidia-drivers-470.256.02-r2 =dev-util/nvidia-cuda-toolkit-11.8.0-r4 and then rebuild world.

** Caveats

As if there weren't already enough sticking points, one huge caveat for anyone who has more than one NVIDIA GPU in their system is that multiple driver slots are not really a possibility. So if one card (say, an RTX 4070) is still supported on the latest 610.xxx drivers (and only supported on >=472), you are SOL if you want the K80 in the same host system.

The workaround to this would be running a PCIe passthrough of the K80 to a virtual machine. This entails some performance overhead as well as reduced resource availability to the K80 (in the form of less available system memory), but could be your only option.

When Linux 6.6 becomes EOL, a VM will be your only (relatively sane) option.

* Software

It's not hard to install llama.cpp once you have the dependencies satisfied. Just follow their build instructions for CUDA.

You should download and build nccl it according to the instructions at the nccl repository. I used version 2.24.3-1, as the latest will not compile with this old of a CUDA toolkit.

Specifically for the K80, you will want the following commands:

BASH $ compile nccl and llama.cpp
# build nccl
cd [nccl_directory]
make src.build CUDA_HOME=/opt/cuda/ NVCC_GENCODE="-gencode=arch=compute_37,code=sm_37" -j$(nproc)
make pkg.txz.build
NCDIR=[nccl_build_dir]

# build llama.cpp
cd [llama.cpp_directory]
cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=37 -DGGML_CUDA_NCCL=ON -DNCCL_INCLUDE_DIR=$NCDIR/include/ -DNCCL_LIBRARY=$NCDIR/lib/libnccl.so
cmake --build build --config Release -j$(nproc)

Although, you may run into the two following issues:

** Unsupported GCC Version

If you see some error during compilation about GCC like so:

LOG nvcc build log
132 | #error -- unsupported GNU version! gcc versions later than 11 are not
supported! The nvcc flag '-allow-unsupported-compiler' can be used to override
this version check; however, using an unsupported host compiler may cause
compilation failure or incorrect run time execution. Use at your own risk.

Then you will need to install GCC 11, then export NVCC_PREPEND_FLAGS="-ccbin /usr/bin/gcc-11" before invoking cmake.

For nccl, you just need to set CXX in makefiles/common.mk.

** New glibc Compatibility

Exactly as per llama.cpp documentation, you may run into build issues from math.h. This is trivially solvable by adding noexcept (true) to the ending of 6 function prototypes. It is a permanent fix as well, so you won't have do this every time you compile something with nvcc.

Here's a (bad but robust) script for it:

BASH # New glibc fix for old CUDA
# Set $f to your target CUDA's math_functions.h file
f="/opt/cuda/targets/x86_64-linux/include/crt/math_functions.h"
cp "$f" "$f.bak" &&
  sed -E -i 's/^(extern __DEVICE_FUNCTIONS_DECL__ __device_builtin__ )(double|float)( *)(cospi|sinpi|rsqrt)([a-z]*\([a-z ]*\));/\1\2\3\4\5 noexcept (true);/g' "$f"

# Verify that it looks fine
diff "$f.bak" "$f"

* Appendix

At this point, you're off to the races with your K80. Below are some random snippets that don't quit fit in the main post.

** llama.cpp Args

Here is a full list of arguments that I used. Note that you may need to increase lockable memory with ulimit -l as root.

BASH $ llama.sh
#!/usr/bin/env bash
BIN="/home/me/code/llama.cpp/cuda/build/bin/llama"

exec \
  "$BIN" serve \
  --cache-prompt \
  --models-max 1 \
  --split-mode tensor \
  --mlock \
  --no-mmap \
  --threads $(nproc) \
  --flash-attn on \
  --direct-io \
  --parallel 1 \
  --offline \
  $@

I didn't read the docs too hard, I mostly just picked options that seemed like they would cache everything really hard for my single-user & single-session setup.

** Overclocking

Memory bandwidth is the biggest bottleneck, so our efforts are focused on that mostly.

*** Supported tweaks

By default, the only thing you can change is ECC and the power limit, which tops at 175W per-die. Run nvidia-smi -e 0 as root to disable ECC until you turn it back on. Power limit is not persistent, but installing dev-python/nvidia-ml-py lets you easily change powerlimit via a script.

PYTHON overclock.py
#!/usr/bin/env python
# get UUIDs with:
# nvidia-smi -q | grep "\(Product Name\|UUID\)"
from pynvml import *

power_limits = {
    "GPU-d18e19a0-5e83-4052-fa1e-43ed462725fd": 175000,
    "GPU-ff564a6d-376d-8f6d-2a9a-e956486d03d3": 175000
}

nvmlInit()
for uuid in power_limits.keys():
    try:
        h = nvmlDeviceGetHandleByUUID(uuid)
        pl = power_limits[uuid]
        nvmlDeviceSetPowerManagementLimit(h, pl)
    except Exception as e:
        print(f"some failure with {uuid}: {e}")

nvmlShutdown()

*** VBIOS Flashing

In order to change the clocks of the card, you have to flash the BIOS (scary). What's worse is that it requires Windows (terrible).

This gets a little dangerous here; I accept zero liability for your card!

I simply followed this guide on modifying and flashing the VBIOS of Kepler cards. I truly do not know if way overtuning the clocks is recoverable, so I went with conservative values.

After a reboot back into a sane operating system, you can verify that your settings actually applied by running nvidia-smi -q -d CLOCK.

LOG nvidia-smi -q -d CLOCK
==============NVSMI LOG==============

Timestamp                                 : Wed Aug 26 15:47:19 2026
Driver Version                            : 470.256.02
CUDA Version                              : 11.4

Attached GPUs                             : 2
GPU 00000000:06:00.0
    Max Clocks
        Graphics                          : 875 MHz
        Memory                            : 3200 MHz

GPU 00000000:07:00.0
    Max Clocks
        Graphics                          : 875 MHz
        Memory                            : 3200 MHz