lnp_ml/server_test_cases/run_cases.sh

188 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
CASE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
API_PORT=${API_PORT:-18000}
API_URL=${API_URL:-http://127.0.0.1:${API_PORT}}
GPU_INDEX=${GPU_INDEX:-0}
CURL_TIMEOUT=${CURL_TIMEOUT:-1800}
PASS=0
FAIL=0
run_case() {
local name=$1
shift
echo
echo "===== ${name} ====="
if "$@"; then
PASS=$((PASS + 1))
echo "PASS: ${name}"
else
FAIL=$((FAIL + 1))
echo "FAIL: ${name}" >&2
fi
}
validate_health() {
local response=/tmp/lnp-test-health.json
curl -fsS --max-time 30 "${API_URL}/" >"${response}"
python3 - "${response}" <<'PY'
import json, sys
data = json.load(open(sys.argv[1], encoding="utf-8"))
assert data["status"] == "healthy", data
assert data["model_loaded"] is True, data
assert data["use_llm"] is True, data
print(json.dumps(data, ensure_ascii=False, indent=2))
PY
}
validate_organs() {
local response=/tmp/lnp-test-organs.json
curl -fsS --max-time 30 "${API_URL}/organs" >"${response}"
python3 - "${response}" <<'PY'
import json, sys
data = json.load(open(sys.argv[1], encoding="utf-8"))
expected = {"lymph_nodes", "heart", "liver", "spleen", "lung", "kidney", "muscle"}
assert set(data) == expected, data
print(json.dumps(data, ensure_ascii=False))
PY
}
validate_single() {
local response=/tmp/lnp-test-single.json
curl -fsS --max-time "${CURL_TIMEOUT}" \
-H 'Content-Type: application/json' \
--data-binary "@${CASE_DIR}/single_valid.json" \
"${API_URL}/predict" >"${response}"
python3 - "${response}" <<'PY'
import json, math, sys
data = json.load(open(sys.argv[1], encoding="utf-8"))
required = ["biodist", "quantified_delivery", "pdi_class", "ee_class", "toxic_class"]
assert all(k in data for k in required), data
values = list(data["biodist"].values())
assert len(values) == 7, values
assert all(math.isfinite(float(x)) for x in values), values
assert abs(sum(values) - 1.0) < 1e-4, sum(values)
assert math.isfinite(float(data["quantified_delivery"]))
print(json.dumps(data, ensure_ascii=False, indent=2))
PY
}
make_batch_payload() {
local count=$1
local use_llm=$2
local batch_size=$3
python3 - "${CASE_DIR}/single_valid.json" "${count}" "${use_llm}" "${batch_size}" <<'PY'
import json, sys
item = json.load(open(sys.argv[1], encoding="utf-8"))
print(json.dumps({
"items": [item for _ in range(int(sys.argv[2]))],
"batch_size": int(sys.argv[4]),
"use_llm": sys.argv[3].lower() == "true",
}))
PY
}
validate_batch_response() {
local response=$1
local expected=$2
python3 - "${response}" "${expected}" <<'PY'
import json, math, sys
data = json.load(open(sys.argv[1], encoding="utf-8"))
expected = int(sys.argv[2])
assert data["n_requested"] == expected, data
assert data["n_succeeded"] == expected, data
assert data["errors"] == [], data["errors"]
assert len(data["predictions"]) == expected
for pred in data["predictions"]:
values = list(pred["biodist"].values())
assert len(values) == 7
assert all(math.isfinite(float(x)) for x in values)
assert abs(sum(values) - 1.0) < 1e-4
print(f"n_succeeded={data['n_succeeded']}, errors={len(data['errors'])}")
PY
}
run_batch() {
local count=$1
local use_llm=$2
local batch_size=$3
local response="/tmp/lnp-test-batch-${count}-${use_llm}.json"
local payload before peak current curl_pid start_ms end_ms
payload=$(make_batch_payload "${count}" "${use_llm}" "${batch_size}")
before=$(nvidia-smi --id="${GPU_INDEX}" --query-gpu=memory.used \
--format=csv,noheader,nounits | head -n 1 | tr -d ' ')
peak=${before}
start_ms=$(date +%s%3N)
curl -fsS --max-time "${CURL_TIMEOUT}" \
-H 'Content-Type: application/json' \
-d "${payload}" "${API_URL}/predict/batch" >"${response}" &
curl_pid=$!
while kill -0 "${curl_pid}" 2>/dev/null; do
current=$(nvidia-smi --id="${GPU_INDEX}" --query-gpu=memory.used \
--format=csv,noheader,nounits | head -n 1 | tr -d ' ')
if [ "${current}" -gt "${peak}" ]; then peak=${current}; fi
sleep 0.2
done
wait "${curl_pid}"
end_ms=$(date +%s%3N)
validate_batch_response "${response}" "${count}"
echo "use_llm=${use_llm}, count=${count}, outer_batch=${batch_size}"
echo "GPU used before=${before} MiB, observed peak=${peak} MiB, delta=$((peak - before)) MiB"
echo "elapsed=$((end_ms - start_ms)) ms"
}
validate_mixed_errors() {
local response=/tmp/lnp-test-mixed.json
curl -fsS --max-time "${CURL_TIMEOUT}" \
-H 'Content-Type: application/json' \
--data-binary "@${CASE_DIR}/mixed_valid_invalid.json" \
"${API_URL}/predict/batch" >"${response}"
python3 - "${response}" <<'PY'
import json, sys
data = json.load(open(sys.argv[1], encoding="utf-8"))
assert data["n_requested"] == 5, data
assert data["n_succeeded"] == 2, data
assert len(data["errors"]) == 3, data
assert {x["index"] for x in data["errors"]} == {2, 3, 4}, data["errors"]
print(json.dumps(data["errors"], ensure_ascii=False, indent=2))
PY
}
validate_optimize() {
local response=/tmp/lnp-test-optimize.json
curl -fsS --max-time "${CURL_TIMEOUT}" \
-H 'Content-Type: application/json' \
--data-binary "@${CASE_DIR}/optimize_smoke.json" \
"${API_URL}/optimize" >"${response}"
python3 - "${response}" <<'PY'
import json, math, sys
data = json.load(open(sys.argv[1], encoding="utf-8"))
assert data["target_organ"] == "liver", data
assert 1 <= len(data["formulations"]) <= 3, data
for row in data["formulations"]:
assert math.isfinite(float(row["target_biodist"])), row
assert abs(sum(float(x) for x in row["all_biodist"].values()) - 1.0) < 1e-4, row
print(f"optimize returned {len(data['formulations'])} formulations")
PY
}
run_case "health" validate_health
run_case "available organs" validate_organs
run_case "single prediction with LLM" validate_single
run_case "64 items without LLM (throughput baseline)" run_batch 64 false 64
run_case "32 items with LLM (VRAM regression)" run_batch 32 true 32
run_case "mixed valid/invalid items" validate_mixed_errors
run_case "small optimize search" validate_optimize
echo
echo "===== SUMMARY ====="
echo "passed=${PASS}, failed=${FAIL}"
if [ "${FAIL}" -ne 0 ]; then
exit 1
fi