#!/bin/bash
# ARM 12's server. ONE CARD, by UUID — Kev-9B is ~19 GB in bf16 and fits a 3090
# whole, so this arm has no tensor-parallel and no all-reduce at all. That is
# the point of it: arm 2 paid 6.4 ms of host bridge on every token and this arm
# does not pay it, so the two latencies are not the same quantity and the tables
# say which is which.
#
#   kev-serve.sh <hf-run> [raw]
#
# `raw` sets KEV_TEMPERATURE=1.0. Kev's checkpoints carry a fitted temperature
# in head.pt (9B 2.30, 4B 2.14) and every loader applies it by default; it never
# changes an answer, so accuracy is identical, but Brier is not — and Kev's own
# published Brier is for the raw logits. The default run is "as served", which
# is what a seat would get; the raw run exists so exactly one Brier in our
# tables is comparable with the card's.
set -u
RUN=${1:?usage: kev-serve.sh <hf-run> [raw]}
MODE=${2:-served}
CARD0=GPU-46890836-d8f7-e868-1977-c1ce28db0d7a
export PATH=/workshop/.local/bin:$PATH
export CUDA_VISIBLE_DEVICES=$CARD0
# THE C COMPILER, and it is arm 2's shim rather than a new idea. benchbox carries
# no cc, gcc or clang and a lane has no sudo to add one; triton's JIT builds a
# small launcher stub per kernel, so without a compiler `flash-linear-attention`
# dies inside l2norm_fwd with "Failed to find C compiler" and the server answers
# 500 (receipts/kev-smoke-serve2.log, 2026-09-22). `~/bench-arm2/shim/cc` is
# zig's clang, installed as an ordinary wheel, and it is what arms 2, 3, 9 and
# 10 already compiled their triton kernels with. It changes no arithmetic: it
# lets the kernel the library ALREADY CHOSE build.
export PATH=/workshop/bench-arm2/shim:$PATH
export CC=/workshop/bench-arm2/shim/cc
export CXX=/workshop/bench-arm2/shim/c++
export KEV_DTYPE=bf16
# KEV_MERGE=0, and it is the reason this arm runs at all. Kev's loader
# (kev/checkpoint.py::Checkpoint.load) reads:
#     dtype, merge = opts.dtype or torch.float32, opts.merge
#     m = DecisionModel(..., dtype=torch.float32 if merge else dtype, ...)
#     if merge: m.lm = m.lm.merge_and_unload()   # in fp32: exact
#     if dtype != torch.float32: m.lm = m.lm.to(dtype)
# so with the DEFAULT merge=1 the 9B base is loaded in fp32 FIRST and only then
# cast — KEV_DTYPE=bf16 never gets a chance, and the load died at 23.4 GiB of a
# 23.56 GiB card (receipts/kev-smoke-serve.log, 2026-09-22). KEV_MERGE=0 is
# Kev's OWN documented switch (README, "Serving Performance"): the base loads in
# bf16 and the rank-16 adapter stays unmerged and is applied at runtime. It is
# not a fallback invented here, it is how this checkpoint is served in bf16 —
# which is the ~19 GB figure its own model card publishes. The cost is stated by
# the project: on 24 records bf16 probabilities differed from fp32 by at most
# 0.017, with no change in the highest-probability answer.
export KEV_MERGE=0
# KEV_DATE_FACTS stays at its default 0: it is a preprocessor, Kev's own card
# reports it separately, and none of our five sets is date arithmetic.
if [ "$MODE" = "raw" ]; then export KEV_TEMPERATURE=1.0; fi
cd /workshop/kev || exit 1
echo "ANNOUNCE  box benchbox · card 0 ${CARD0:0:16} (one RTX 3090, 250 W cap; card 1 stays dark) · arm 12 · kev.serve $RUN bf16 $MODE · start $(date -u +%FT%TZ) · expect ~5 min to ready, then ~40 min of task arms"
exec uv run --extra serve python -m kev.serve --run "$RUN" --port 8009
