142 lines
4.9 KiB
Bash
Executable file
142 lines
4.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# docker-e2e.sh — the Stage 11 pre-deploy gate.
|
|
#
|
|
# Brings up `N` stub-mode `pp-gpu-node` containers on localhost, drives
|
|
# one InferenceRequest through them via `pp-smoke-run --seed`, and tears
|
|
# everything down. The image is built locally from the workspace's
|
|
# release artifacts; no GPU, no tinygrad, no GGUF required.
|
|
#
|
|
# Usage:
|
|
# examples/pipeline-parallel-inference/scripts/docker-e2e.sh [N]
|
|
#
|
|
# Environment overrides:
|
|
# PP_IMAGE container image tag (default: pp-gpu-node-stub:latest)
|
|
# PP_CONTAINER_PREFIX container name prefix (default: pp-stage)
|
|
# PP_MAX_TOKENS max decode tokens (default: 4)
|
|
# PP_PROMPT inference prompt (default: "Say hello")
|
|
# PP_SKIP_BUILD if set, skip cargo build (use existing target/)
|
|
# PP_SKIP_IMAGE_BUILD if set, skip docker image build (use existing tag)
|
|
#
|
|
# Exit code is 0 only when the response banner was non-empty AND no
|
|
# stage containers remain afterwards.
|
|
set -euo pipefail
|
|
|
|
NUM_STAGES="${1:-3}"
|
|
IMAGE="${PP_IMAGE:-pp-gpu-node-stub:latest}"
|
|
PREFIX="${PP_CONTAINER_PREFIX:-pp-stage}"
|
|
MAX_TOKENS="${PP_MAX_TOKENS:-4}"
|
|
PROMPT="${PP_PROMPT:-Say hello}"
|
|
|
|
if ! [[ "$NUM_STAGES" =~ ^[0-9]+$ ]] || [ "$NUM_STAGES" -lt 2 ]; then
|
|
echo "docker-e2e: NUM_STAGES must be an integer >= 2, got '$NUM_STAGES'" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo "docker-e2e: docker not on PATH; install Docker or run on a host that has it" >&2
|
|
exit 2
|
|
fi
|
|
if ! docker info >/dev/null 2>&1; then
|
|
echo "docker-e2e: docker daemon unreachable (need to start it, or fix DOCKER_HOST)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# Locate the example crate and workspace root. The script lives in
|
|
# `<crate>/scripts/`; the workspace root is two levels above.
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
CRATE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
WORKSPACE_DIR="$(cd "$CRATE_DIR/../.." && pwd)"
|
|
SMOKE_RUN_BIN="$CRATE_DIR/target/release/pp-smoke-run"
|
|
GPU_NODE_BIN="$CRATE_DIR/target/release/pp-gpu-node"
|
|
WORKER_PY="$CRATE_DIR/pp_tinygrad_worker.py"
|
|
|
|
# Step 1: build release artifacts the docker image will package.
|
|
if [ -z "${PP_SKIP_BUILD:-}" ]; then
|
|
echo "docker-e2e: building pp-gpu-node + pp-smoke-run (release)"
|
|
cargo build --manifest-path "$CRATE_DIR/Cargo.toml" --release \
|
|
--bin pp-gpu-node --bin pp-smoke-run
|
|
fi
|
|
for f in "$SMOKE_RUN_BIN" "$GPU_NODE_BIN" "$WORKER_PY"; do
|
|
[ -f "$f" ] || { echo "docker-e2e: missing $f" >&2; exit 1; }
|
|
done
|
|
|
|
# Step 2: build the stub-mode image. Build context is the workspace
|
|
# root because the Dockerfile copies from `examples/...`.
|
|
if [ -z "${PP_SKIP_IMAGE_BUILD:-}" ]; then
|
|
echo "docker-e2e: building $IMAGE"
|
|
docker build \
|
|
-f "$CRATE_DIR/Dockerfile.stub" \
|
|
-t "$IMAGE" \
|
|
"$WORKSPACE_DIR"
|
|
fi
|
|
|
|
# Step 3: clean up any stage containers left over from prior failed runs.
|
|
cleanup_containers() {
|
|
local ids
|
|
ids=$(docker ps -aq --filter "name=^${PREFIX}-[0-9]+$" || true)
|
|
if [ -n "$ids" ]; then
|
|
# shellcheck disable=SC2086
|
|
docker rm -f $ids >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
cleanup_containers
|
|
|
|
# Step 4: drive pp-smoke-run with the docker shim as its --gpu-node.
|
|
# The shim consults PP_IMAGE / PP_CONTAINER_PREFIX / PP_DEV from its env.
|
|
OUTPUT_DIR="$(mktemp -d)"
|
|
STDOUT_LOG="$OUTPUT_DIR/stdout.log"
|
|
STDERR_LOG="$OUTPUT_DIR/stderr.log"
|
|
trap 'rm -rf "$OUTPUT_DIR"' EXIT
|
|
|
|
set +e
|
|
PP_WORKER_STUB=1 \
|
|
PP_IMAGE="$IMAGE" \
|
|
PP_CONTAINER_PREFIX="$PREFIX" \
|
|
PP_DEV=CPU \
|
|
"$SMOKE_RUN_BIN" \
|
|
--seed \
|
|
--num-stages "$NUM_STAGES" \
|
|
--gpu-node "$SCRIPT_DIR/docker-gpu-node.sh" \
|
|
--worker "$WORKER_PY" \
|
|
--prompt "$PROMPT" \
|
|
--max-tokens "$MAX_TOKENS" \
|
|
>"$STDOUT_LOG" 2>"$STDERR_LOG"
|
|
SMOKE_STATUS=$?
|
|
set -e
|
|
|
|
if [ $SMOKE_STATUS -ne 0 ]; then
|
|
echo "docker-e2e: pp-smoke-run exited $SMOKE_STATUS" >&2
|
|
echo "----- stdout -----" >&2
|
|
cat "$STDOUT_LOG" >&2
|
|
echo "----- stderr (last 60) -----" >&2
|
|
tail -n 60 "$STDERR_LOG" >&2
|
|
cleanup_containers
|
|
exit 1
|
|
fi
|
|
|
|
# Step 5: verify the orchestrator printed a non-empty response between
|
|
# its banner lines.
|
|
HEADER='=== pipeline-parallel Inference Response ==='
|
|
FOOTER='============================================'
|
|
RESPONSE=$(awk -v hdr="$HEADER" -v ftr="$FOOTER" \
|
|
'BEGIN{in_body=0} $0==hdr{in_body=1;next} $0==ftr{in_body=0;exit} in_body{print}' \
|
|
"$STDOUT_LOG")
|
|
if [ -z "$RESPONSE" ]; then
|
|
echo "docker-e2e: response banner missing or empty" >&2
|
|
echo "----- stdout -----" >&2
|
|
cat "$STDOUT_LOG" >&2
|
|
cleanup_containers
|
|
exit 1
|
|
fi
|
|
|
|
# Step 6: verify cleanup — no stage container may survive a clean run.
|
|
LEFTOVERS=$(docker ps -aq --filter "name=^${PREFIX}-[0-9]+$" || true)
|
|
if [ -n "$LEFTOVERS" ]; then
|
|
echo "docker-e2e: leftover stage containers after exit:" >&2
|
|
docker ps -a --filter "name=^${PREFIX}-[0-9]+$" >&2 || true
|
|
cleanup_containers
|
|
exit 1
|
|
fi
|
|
|
|
cat "$STDOUT_LOG"
|
|
echo "docker-e2e: PASS at N=$NUM_STAGES (response length ${#RESPONSE} chars)"
|