Powered by AppSignal & Oban Pro

HPC Benchmark

examples/benchmark_hpc.livemd

HPC Benchmark

Mix.install([
  {:atp_benchmark_runner, path: "item_14_ATP_Benchmark_Runner/atp_benchmark_runner", force: true}
])

1. Configure paths from .env

project_root = Path.expand("..", __DIR__)
env_file = Path.join(project_root, ".env")

# Load .env values into System.put_env so Config.get() picks them up
env_map = HpcConnect.load_env_file(env_file)
Enum.each(env_map, fn {k, v} -> System.put_env(k, v) end)

IO.puts("Configured paths:")
IO.puts("  TPTP_ROOT:      #{AtpBenchmarkRunner.tptp_dir()}")
IO.puts("  Store dir:      #{AtpBenchmarkRunner.store_dir()}")
IO.puts("  SMT temp dir:   #{AtpBenchmarkRunner.Config.smt_tmp_dir()}")
IO.puts("  .env file:      #{env_file}")

This notebook is the HPC counterpart to the local example. It uses the local workspace versions of both libraries because atp_benchmark_runner depends on the sibling hpc_connect path from its mix.exs.


2. Bootstrap the HPC session

boot =
    HpcConnect.bootstrap(
        mode: :local,
        env_file: env_file
    )

session = boot.session

IO.inspect(
    %{
        cluster: session.cluster.name,
        ssh_alias: session.ssh_alias,
        username: session.username,
        work_dir: session.work_dir,
        vault_dir: session.vault_dir
    },
    label: "Resolved HPC session"
)

HpcConnect.bootstrap/1 already installs the remote helper scripts and uploads the generic bundled hpc_connect def-file set. ATP prover definitions stay in atp_benchmark_runner/priv/provers, so ATP-specific image prep still goes through the benchmark-runner helpers below.


3. Pick provers and select problems from TPTP_ROOT

selected_provers = [:vampire, :cvc5]

# Use the configured TPTP_ROOT (C:\tmp\tptp from .env).
# Problems can be selected by name — they resolve from the TPTP archive,
# bundled examples, or a livebook cache automatically.
problems =
    AtpBenchmarkRunner.select_problems(
        names: ["GRP001-0.p", "GRP002+0.p", "LAT001+0.p"],
        limit: 2
    )

IO.inspect(Enum.map(problems, &%{name: &1.name, path: &1.path}), label: "Problems")

4. Inspect the remote image prep plan

prover_structs = Enum.map(selected_provers, &AtpBenchmarkRunner.Prover.builtin!/1)

image_plan =
    AtpBenchmarkRunner.image_build_plan(
        session,
        prover_structs
    )

IO.inspect(image_plan, label: "Remote image build plan", limit: :infinity)

5. Optionally prepare ATP prover images now

prepare_images_now? = false

if prepare_images_now? do
    uploaded_defs =
        AtpBenchmarkRunner.upload_prover_definitions!(
            session,
            prover_structs,
            install_scripts: false
        )

    IO.inspect(uploaded_defs, label: "Uploaded ATP prover defs")

    built_images =
        AtpBenchmarkRunner.build_prover_images!(
            session,
            prover_structs,
            install_scripts: false
        )

    IO.inspect(built_images, label: "Built ATP prover images")
else
    :ok
end

6. Bootstrap an HPC benchmark plan

Select the execution mode and resource strategy:

node_size controls how many CPUs of a node are requested. Auto-detected per cluster (Helma CPU: 384/192, Fritz: 72/36, spr*: 104/52).

node_size --exclusive Use case
:full Yes Maximise per-prover throughput
:half No Share node with other jobs; efficient
Mode hpc_mode single_node_mode Resource allocation
Single-node sequential (default) :single_node :sequential All provers share 1 node. Tasks one at a time; each gets node CPUs.
Single-node parallel :single_node :parallel All provers share 1 node. Tasks run concurrently (≤ max_parallel).
Multi-node (prover per node) :multi_node Each prover gets its own node.
# ── Single-node sequential, full node (default) ───────────────
plan =
    AtpBenchmarkRunner.bootstrap(
        session,
        selected_provers,
        problems,
        mode: :hpc,
        hpc_mode: :single_node,
        single_node_mode: :sequential,
        timeout_seconds: 30,
        wait_for_completion: false,
        prepare_images: false
    )

IO.inspect(plan, label: "Single-node sequential, full node (default)", limit: :infinity)
# ── Single-node sequential, half node ────────────────────────
plan_half =
    AtpBenchmarkRunner.bootstrap(
        session,
        selected_provers,
        problems,
        mode: :hpc,
        hpc_mode: :single_node,
        single_node_mode: :sequential,
        node_size: :half,
        timeout_seconds: 30,
        wait_for_completion: false,
        prepare_images: false
    )

IO.inspect(plan_half, label: "Single-node sequential, half node", limit: :infinity)
# ── Single-node parallel, full node ──────────────────────────
plan_parallel =
    AtpBenchmarkRunner.bootstrap(
        session,
        selected_provers,
        problems,
        mode: :hpc,
        hpc_mode: :single_node,
        single_node_mode: :parallel,
        max_parallel_jobs: 4,
        timeout_seconds: 30,
        wait_for_completion: false,
        prepare_images: false
    )

IO.inspect(plan_parallel, label: "Single-node parallel plan", limit: :infinity)
# ── Multi-node ────────────────────────────────────────────────
# Each prover gets its own node (default full).
plan_multi =
    AtpBenchmarkRunner.bootstrap(
        session,
        selected_provers,
        problems,
        mode: :hpc,
        hpc_mode: :multi_node,
        timeout_seconds: 30,
        wait_for_completion: false,
        prepare_images: false
    )

IO.inspect(plan_multi, label: "Multi-node plan", limit: :infinity)

If you want the runner itself to prepare missing prover images immediately before launch, set prepare_images: true in the benchmark plan above.


7. Optionally submit the run

submit_run? = false

results =
    if submit_run? do
        AtpBenchmarkRunner.run_benchmark(plan)
    else
        []
    end

IO.inspect(results, label: "Results")