Skip to content

Add cactus bench: CPU throughput benchmarking for Mac and Android#698

Open
ncylich wants to merge 4 commits into
mainfrom
android-bench
Open

Add cactus bench: CPU throughput benchmarking for Mac and Android#698
ncylich wants to merge 4 commits into
mainfrom
android-bench

Conversation

@ncylich

@ncylich ncylich commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds cactus bench, a CLI verb for reproducible CPU inference throughput benchmarks. Runs locally by default; --android cross-builds for arm64-v8a and runs on a connected device over adb. This replaces the standalone tests/android-e2e bash/C++ harness from earlier revisions of this PR — the protocol is unchanged, but it now rides the existing infrastructure end to end.

cactus bench                                   # gemma-4-e2b-it, local
cactus bench --android                         # same workload on a device
cactus bench Qwen/Qwen3-0.6B --android
cactus bench --prompt "Explain relativity:"    # chat-template path
  • Token mode (default): prefill exactly --prefill-tokens raw token IDs (512 = 4 full 128-token prefill chunks, zero padding) + --decode-tokens decode via cactus_benchmark_tokens. No tokenizer, template, or padding variance; fresh process per round, first (warmup) round discarded.
  • Prompt mode (--prompt): greedy cactus_complete through the chat-template path with a discarded in-process warmup per round; also reports TTFT.
  • Every round runs with CACTUS_DISABLE_CLOUD_HANDOFF=1 and CACTUS_NO_CLOUD_TELE=1 so no routing probe, network fallback, or telemetry upload lands inside a measurement.
  • Android runs push weights once (--if-missing semantics), warn when the device is not charging, and accept a --cpu-mask taskset mask for core-restricted runs.

How it reuses existing infrastructure

  • The on-device workload is the engine's own cactus_benchmark_tokens; the only new C++ is a ~120-line driver (cactus-engine/tests/llm_bench.cpp) registered as a target in the existing host and Android test CMake trees (which link the cactus_engine target directly — no hand-maintained archive lists).
  • Bundle provisioning reuses the cactus test path: download a prebuilt bundle, else convert + transpile. A local bundle directory is also accepted.
  • Methodology lives in docs/benchmarking.md: device preparation, core configuration, chunk-padding interpretation of prefill numbers, and bundle-vintage checks (row-major LM heads, pre-chunked-prefill text bundles).

Verified

Target Model Token mode 512+32 (prefill / decode tps) Prompt mode
Mac (local) qwen3-0.6b 383.2 / 79.5 93.0 / 104.4, ttft 247 ms
Samsung S25 (8 Elite) gemma-4-e2b-it 203.7 / 25.7 † 28.1 / 27.4, ttft 640 ms †

† measured on battery (power-limited; treat as a floor). Token-mode numbers match the previous standalone harness on the same device within noise, and the prefill ~204 tps / decode ~25.7 tps is consistent with the documented Samsung reference band (22–24 / 200+, verified 2026-06-09 on power).

Copilot AI review requested due to automatic review settings June 10, 2026 00:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an Android (arm64-v8a) end-to-end and exact-spec benchmark harness under tests/android-e2e/, intended to run on-device via adb and link against engine archives produced by android/build.sh.

Changes:

  • Introduces two Android benchmark executables: e2e_bench (prompt-based) and llm_bench (token-ID exact-spec via cactus_benchmark_tokens).
  • Adds build + run scripts (build.sh, run.sh, run_spec.sh) with shared bench_common.sh helpers for pushing artifacts, disabling cloud/telemetry, and power-state warnings.
  • Documents a “trustworthy numbers” measurement protocol and known performance pitfalls in a dedicated README.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/android-e2e/run.sh Runs prompt-based e2e benchmark on-device and pulls per-round CSV output.
tests/android-e2e/run_spec.sh Runs exact-spec token-ID benchmark on-device and prints per-round JSON-derived metrics.
tests/android-e2e/README.md Documents setup, required performance protocol, and reference numbers.
tests/android-e2e/e2e_driver.h Declares the minimal backend/driver interface and CSV/summary helpers.
tests/android-e2e/e2e_driver.cpp Implements scheduling, CSV output, and summary statistics.
tests/android-e2e/e2e_bench.cpp Implements the benchmark runner and minimal config parsing.
tests/android-e2e/e2e_backend_cactus.cpp Implements the Cactus backend adapter for the e2e harness.
tests/android-e2e/CMakeLists.txt Defines Android CMake build for e2e_bench and llm_bench linked to engine archives.
tests/android-e2e/cactus_llm_bench.cpp Implements llm_bench wrapper around cactus_benchmark_tokens.
tests/android-e2e/build.sh Convenience script to build engine archives (optional) and benchmark binaries via CMake.
tests/android-e2e/bench_common.sh Shared adb/push helpers and environment setup for both run scripts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/android-e2e/e2e_bench.cpp Outdated
Comment on lines +4 to +8
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
Comment on lines +5 to +9
#include <chrono>
#include <cstring>
#include <iostream>
#include <string>

Comment on lines +35 to +36
while (*pos && (*pos == ' ' || *pos == ':' || *pos == '\t')) pos++;
return atof(pos);
Comment on lines +71 to +75
char response[8192] = {};
auto wall_start = std::chrono::steady_clock::now();
int ret = cactus_complete(h->model, messages.c_str(), response, sizeof(response),
options.c_str(), nullptr, nullptr, nullptr, nullptr, 0);
auto wall_end = std::chrono::steady_clock::now();
Comment thread tests/android-e2e/run.sh Outdated
Comment on lines +34 to +41
echo "==> Running e2e_bench (max-tokens=$MAX_TOKENS, rounds=$ROUNDS)"
"${ADB[@]}" shell "cd $DEVICE_DIR && $BENCH_ENV ./e2e_bench \
--model-config $DEVICE_DIR/models.json \
--backends cactus \
--rounds $ROUNDS \
--max-tokens $MAX_TOKENS \
--prompt \"$PROMPT\" \
--output $DEVICE_DIR/e2e_results_android.csv"
Comment thread tests/android-e2e/run_spec.sh Outdated
Comment on lines +17 to +18
python3 -c "print(','.join(str(1000+i) for i in range($PREFILL_TOKENS)))" > /tmp/spec_tokens.csv
"${ADB[@]}" push /tmp/spec_tokens.csv "$DEVICE_DIR/spec_tokens.csv" >/dev/null
Token mode prefills exact raw token IDs and times decode via
cactus_benchmark_tokens, so prefill numbers carry no tokenizer,
template, or chunk-padding variance; prompt mode runs a greedy
chat-path completion after a discarded in-process warmup. Registered
in the host and Android test builds.

Signed-off-by: Noah Cylich <noahcylich@gmail.com>
@ncylich ncylich changed the title Add Android e2e and exact-spec benchmark harness Add cactus bench: CPU throughput benchmarking for Mac and Android Jun 12, 2026
ncylich added 2 commits June 12, 2026 14:12
Builds the llm_bench fixture through the existing test build trees
(host CMake, or the NDK arm64-v8a toolchain for --android), provisions
the bundle like cactus test does, and runs a 1-warmup + N-round
protocol over the 512+32 token workload or a --prompt chat workload.
Android runs push weights once, warn when the device is not charging,
and accept a taskset core mask; cloud handoff and telemetry are
disabled for every round.

Signed-off-by: Noah Cylich <noahcylich@gmail.com>
Device preparation, core configuration, chunk-padding interpretation,
bundle-vintage checks, and the verified per-device reference table.

Signed-off-by: Noah Cylich <noahcylich@gmail.com>
Signed-off-by: Noah Cylich <noahcylich@gmail.com>

# Conflicts:
#	python/cactus/cli/__init__.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants