Rework architecture so each challenge has separate runtime & docker.

This commit is contained in:
FiveMovesAhead 2025-06-04 11:06:26 +01:00
parent 7ebad69cf7
commit 89d86f9deb
17 changed files with 447 additions and 315 deletions

View File

@ -1,8 +1,13 @@
ARG BASE_IMAGE=ubuntu:24.04 ARG BASE_IMAGE=ubuntu:24.04
ARG CHALLENGE
# Development environment stage # Development environment stage
FROM ${BASE_IMAGE} FROM ${BASE_IMAGE}
ARG CHALLENGE
ENV CHALLENGE=${CHALLENGE}
RUN if [ -z "$CHALLENGE" ]; then echo "Error: '--build-arg CHALLENGE' must be set." && exit 1; fi
RUN ARCH=$(uname -m) && \ RUN ARCH=$(uname -m) && \
if [ "$ARCH" != "aarch64" ] && [ "$ARCH" != "arm64" ] && [ "$ARCH" != "amd64" ] && [ "$ARCH" != "x86_64" ]; then \ if [ "$ARCH" != "aarch64" ] && [ "$ARCH" != "arm64" ] && [ "$ARCH" != "amd64" ] && [ "$ARCH" != "x86_64" ]; then \
echo "Unsupported architecture: $ARCH. Must be 'aarch64', 'arm64', 'amd64', or 'x86_64'." && exit 1; \ echo "Unsupported architecture: $ARCH. Must be 'aarch64', 'arm64', 'amd64', or 'x86_64'." && exit 1; \
@ -26,9 +31,8 @@ RUN ARCH=$(uname -m) && \
rustup component add rust-src && \ rustup component add rust-src && \
rustup target add $RUST_TARGET && \ rustup target add $RUST_TARGET && \
RUST_LIBDIR=$(rustc --print target-libdir --target=$RUST_TARGET) && \ RUST_LIBDIR=$(rustc --print target-libdir --target=$RUST_TARGET) && \
ln -s $RUST_LIBDIR /usr/local/lib/rust && \ ln -s $RUST_LIBDIR /usr/local/lib/rust
echo "export LD_LIBRARY_PATH=\"${LD_LIBRARY_PATH}:/usr/local/lib/rust\"" >> /etc/bash.bashrc && \ ENV LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/usr/local/lib/rust"
echo "export RUST_TARGET=\"${RUST_TARGET}\"" >> /etc/bash.bashrc
RUN ARCH=$(uname -m) && \ RUN ARCH=$(uname -m) && \
LLVM_URL=$(if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then \ LLVM_URL=$(if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then \
@ -42,25 +46,20 @@ RUN ARCH=$(uname -m) && \
rm -rf llvm.tar.zst && \ rm -rf llvm.tar.zst && \
ln -s /opt/llvm/bin/* /usr/local/bin/ ln -s /opt/llvm/bin/* /usr/local/bin/
COPY scripts/ /usr/local/bin/tig-scripts/
COPY tig-binary/scripts/ /usr/local/bin/tig-scripts/
RUN chmod +x /usr/local/bin/tig-scripts/*
ENV PATH="/usr/local/bin/tig-scripts:${PATH}"
COPY . /tmp/tig-monorepo COPY . /tmp/tig-monorepo
WORKDIR /tmp/tig-monorepo WORKDIR /tmp/tig-monorepo
RUN if command -v nvcc > /dev/null 2>&1; then \ RUN cargo build -r -p tig-runtime --features $CHALLENGE && \
cargo build -r -p tig-runtime --features cuda && \ cargo build -r -p tig-verifier --features $CHALLENGE && \
cargo build -r -p tig-verifier --features cuda; \
else \
cargo build -r -p tig-runtime && \
cargo build -r -p tig-verifier; \
fi && \
mv target/release/tig-runtime /usr/local/bin/ && \ mv target/release/tig-runtime /usr/local/bin/ && \
mv target/release/tig-verifier /usr/local/bin/ && \ mv target/release/tig-verifier /usr/local/bin/ && \
chmod +x /usr/local/bin/tig-runtime && \ chmod +x /usr/local/bin/tig-runtime && \
chmod +x /usr/local/bin/tig-verifier && \ chmod +x /usr/local/bin/tig-verifier && \
rm -rf tig-monorepo rm -rf tig-monorepo
COPY scripts/ /usr/local/bin/tig-scripts/
COPY tig-binary/scripts/ /usr/local/bin/tig-scripts/
RUN chmod +x /usr/local/bin/tig-scripts/*
ENV PATH="/usr/local/bin/tig-scripts:${PATH}"
WORKDIR /app WORKDIR /app

View File

@ -1,27 +1,28 @@
ARG BASE_IMAGE=ubuntu:24.04 ARG BASE_IMAGE=ubuntu:24.04
ARG DEV_IMAGE=ghcr.io/tig-foundation/tig-monorepo/dev ARG CHALLENGE
FROM ${DEV_IMAGE} AS dev FROM ghcr.io/tig-foundation/tig-monorepo/${CHALLENGE}/dev AS dev
FROM ${BASE_IMAGE} FROM ${BASE_IMAGE}
ARG CHALLENGE
ENV CHALLENGE=${CHALLENGE}
RUN if [ -z "$CHALLENGE" ]; then echo "Error: '--build-arg CHALLENGE' must be set." && exit 1; fi
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && apt install -y python3 python3-pip
RUN pip3 install requests --break-system-packages
COPY scripts/ /usr/local/bin/tig-scripts/
RUN chmod +x /usr/local/bin/tig-scripts/*
ENV PATH="/usr/local/bin/tig-scripts:${PATH}"
COPY --from=dev /usr/local/bin/tig-runtime /usr/local/bin/tig-runtime COPY --from=dev /usr/local/bin/tig-runtime /usr/local/bin/tig-runtime
COPY --from=dev /usr/local/bin/tig-verifier /usr/local/bin/tig-verifier COPY --from=dev /usr/local/bin/tig-verifier /usr/local/bin/tig-verifier
COPY --from=dev /usr/local/lib/rust /usr/local/lib/rust COPY --from=dev /usr/local/lib/rust /usr/local/lib/rust
RUN chmod +x /usr/local/bin/tig-runtime && \ RUN chmod +x /usr/local/bin/tig-runtime && \
chmod +x /usr/local/bin/tig-verifier && \ chmod +x /usr/local/bin/tig-verifier
echo "export LD_LIBRARY_PATH=\"${LD_LIBRARY_PATH}:/usr/local/lib/rust\"" >> /etc/bash.bashrc ENV LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/usr/local/lib/rust"
RUN apt update && apt install -y python3 python3-pip
RUN pip3 install blake3 requests randomname --break-system-packages
COPY scripts/ /usr/local/bin/tig-scripts/
RUN chmod +x /usr/local/bin/tig-scripts/*
ENV PATH="/usr/local/bin/tig-scripts:${PATH}"
COPY tig-benchmarker /app
WORKDIR /app WORKDIR /app

View File

@ -7,9 +7,13 @@ import requests
import sys import sys
import tarfile import tarfile
CHALLENGE = os.getenv("CHALLENGE")
if CHALLENGE is None:
print("CHALLENGE environment variable must be set!")
sys.exit(1)
def main(): def main():
parser = argparse.ArgumentParser(description='TIG Algorithm Downloader') parser = argparse.ArgumentParser(description='TIG Algorithm Downloader')
parser.add_argument('challenge', help="Challenge name or id")
parser.add_argument('algorithm', help="Algorithm name or id") parser.add_argument('algorithm', help="Algorithm name or id")
parser.add_argument('--out', default="tig-algorithms/lib", help="Output directory (default: tig-algorithms/lib)") parser.add_argument('--out', default="tig-algorithms/lib", help="Output directory (default: tig-algorithms/lib)")
parser.add_argument('--testnet', action='store_true', help="Use testnet API") parser.add_argument('--testnet', action='store_true', help="Use testnet API")
@ -26,12 +30,12 @@ def main():
c_id = next( c_id = next(
( (
c['id'] for c in challenges c['id'] for c in challenges
if c['details']['name'] == args.challenge or c['id'] == args.challenge if c['details']['name'] == CHALLENGE
), ),
None None
) )
if c_id is None: if c_id is None:
print(f"Challenge '{args.challenge}' not found.") print(f"Challenge '{CHALLENGE}' not found.")
sys.exit(1) sys.exit(1)
a_id = next( a_id = next(
@ -44,7 +48,7 @@ def main():
None None
) )
if a_id is None: if a_id is None:
print(f"Algorithm '{args.algorithm}' not found for challenge '{args.challenge}'.") print(f"Algorithm '{args.algorithm}' not found for challenge '{CHALLENGE}'.")
sys.exit(1) sys.exit(1)
download_url = download_urls.get(a_id) download_url = download_urls.get(a_id)
@ -54,7 +58,7 @@ def main():
print(f"Downloading algorithm '{args.algorithm}' from {download_url}...") print(f"Downloading algorithm '{args.algorithm}' from {download_url}...")
resp = requests.get(download_url, stream=True) resp = requests.get(download_url, stream=True)
output_dir = f"{args.out}/{args.challenge}" output_dir = f"{args.out}/{CHALLENGE}"
os.makedirs(output_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True)
with tarfile.open(fileobj=io.BytesIO(resp.content), mode='r:gz') as tar: with tarfile.open(fileobj=io.BytesIO(resp.content), mode='r:gz') as tar:
tar.extractall(path=output_dir) tar.extractall(path=output_dir)

View File

@ -1,15 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import io
import os import os
import requests import requests
import sys import sys
import tarfile
CHALLENGE = os.getenv("CHALLENGE")
if CHALLENGE is None:
print("CHALLENGE environment variable must be set!")
sys.exit(1)
def main(): def main():
parser = argparse.ArgumentParser(description='TIG Algorithm Lister') parser = argparse.ArgumentParser(description='TIG Algorithm Lister')
parser.add_argument('challenge', help="Challenge name or id")
parser.add_argument('--testnet', action='store_true', help="Use testnet API") parser.add_argument('--testnet', action='store_true', help="Use testnet API")
args = parser.parse_args() args = parser.parse_args()
@ -24,12 +26,12 @@ def main():
c_id = next( c_id = next(
( (
c['id'] for c in challenges c['id'] for c in challenges
if c['details']['name'] == args.challenge or c['id'] == args.challenge if c['details']['name'] == CHALLENGE
), ),
None None
) )
if c_id is None: if c_id is None:
print(f"Challenge '{args.challenge}' not found.") print(f"Challenge '{CHALLENGE}' not found.")
sys.exit(1) sys.exit(1)
algorithms = sorted([ algorithms = sorted([

View File

@ -37,6 +37,11 @@ elif (VISIBLE_GPUS := os.environ.get("CUDA_VISIBLE_DEVICES", None)) is None:
else: else:
VISIBLE_GPUS = list(map(int, VISIBLE_GPUS.split(","))) VISIBLE_GPUS = list(map(int, VISIBLE_GPUS.split(",")))
CHALLENGE = os.getenv("CHALLENGE")
if CHALLENGE is None:
print("CHALLENGE environment variable must be set!")
sys.exit(1)
def now(): def now():
return int(time.time() * 1000) return int(time.time() * 1000)
@ -44,7 +49,6 @@ def now():
if __name__ == "__main__": if __name__ == "__main__":
tig_runtime_path = shutil.which("tig-runtime") tig_runtime_path = shutil.which("tig-runtime")
parser = argparse.ArgumentParser(description="TIG Algorithm Tester") parser = argparse.ArgumentParser(description="TIG Algorithm Tester")
parser.add_argument("challenge", type=str, help="Challenge name")
parser.add_argument("algorithm", type=str, help="Algorithm name") parser.add_argument("algorithm", type=str, help="Algorithm name")
parser.add_argument("difficulty", type=str, help="JSON string of difficulty") parser.add_argument("difficulty", type=str, help="JSON string of difficulty")
parser.add_argument("--tig_runtime_path", type=str, default=tig_runtime_path, help=f"Path to tig-runtime executable (default: {tig_runtime_path})") parser.add_argument("--tig_runtime_path", type=str, default=tig_runtime_path, help=f"Path to tig-runtime executable (default: {tig_runtime_path})")
@ -58,8 +62,8 @@ if __name__ == "__main__":
args = parser.parse_args() args = parser.parse_args()
so_path = f"{args.lib_dir}/{args.challenge}/{CPU_ARCH}/{args.algorithm}.so" so_path = f"{args.lib_dir}/{CHALLENGE}/{CPU_ARCH}/{args.algorithm}.so"
ptx_path = f"{args.lib_dir}/{args.challenge}/ptx/{args.algorithm}.ptx" ptx_path = f"{args.lib_dir}/{CHALLENGE}/ptx/{args.algorithm}.ptx"
if not os.path.exists(so_path): if not os.path.exists(so_path):
print( print(
@ -93,10 +97,7 @@ f"""Library not found at {so_path}:
"hypergraph": "c005", "hypergraph": "c005",
"optimiser": "c006", "optimiser": "c006",
} }
challenge_id = challenge_ids.get(args.challenge, None) challenge_id = challenge_ids[CHALLENGE]
if challenge_id is None:
print(f"Unknown challenge '{args.challenge}'. Choices: ({', '.join(challenge_ids.keys())})")
sys.exit(1)
settings = {"algorithm_id": "", "challenge_id": challenge_id, "difficulty": difficulty, "block_id": "", "player_id": ""} settings = {"algorithm_id": "", "challenge_id": challenge_id, "difficulty": difficulty, "block_id": "", "player_id": ""}
pool = ThreadPoolExecutor(max_workers=args.workers + 1) pool = ThreadPoolExecutor(max_workers=args.workers + 1)

View File

@ -23,4 +23,14 @@ tig-challenges = { path = "../tig-challenges" }
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[features] [features]
cuda = ["cudarc", "tig-challenges/cuda"] cuda = ["cudarc"]
c001 = ["tig-challenges/c001"]
satisfiability = ["c001"]
c002 = ["tig-challenges/c002"]
vehicle_routing = ["c002"]
c003 = ["tig-challenges/c003"]
knapsack = ["c003"]
c004 = ["cuda", "tig-challenges/c004"]
vector_search = ["c004"]
c005 = ["cuda", "tig-challenges/c005"]
hypergraph = ["c005"]

View File

@ -11,18 +11,23 @@ pub(crate) type HashSet<T> = std::collections::HashSet<T, RandomState>;
pub const BUILD_TIME_PATH: &str = env!("CARGO_MANIFEST_DIR"); pub const BUILD_TIME_PATH: &str = env!("CARGO_MANIFEST_DIR");
pub mod knapsack; #[cfg(feature = "c001")]
pub use knapsack as c003;
pub mod satisfiability; pub mod satisfiability;
#[cfg(feature = "c001")]
pub use satisfiability as c001; pub use satisfiability as c001;
#[cfg(feature = "c002")]
pub mod vehicle_routing; pub mod vehicle_routing;
#[cfg(feature = "c002")]
pub use vehicle_routing as c002; pub use vehicle_routing as c002;
#[cfg(feature = "c003")]
#[cfg(feature = "cuda")] pub mod knapsack;
#[cfg(feature = "c003")]
pub use knapsack as c003;
#[cfg(feature = "c004")]
pub mod vector_search; pub mod vector_search;
#[cfg(feature = "cuda")] #[cfg(feature = "c004")]
pub use vector_search as c004; pub use vector_search as c004;
#[cfg(feature = "cuda")] #[cfg(feature = "c005")]
pub mod hypergraph; pub mod hypergraph;
#[cfg(feature = "cuda")] #[cfg(feature = "c005")]
pub use hypergraph as c005; pub use hypergraph as c005;

View File

@ -19,4 +19,14 @@ tig-algorithms = { path = "../tig-algorithms" }
[features] [features]
entry_point = [] entry_point = []
cuda = ["cudarc", "tig-algorithms/cuda", "tig-challenges/cuda"] cuda = ["cudarc"]
c001 = ["tig-algorithms/c001", "tig-challenges/c001"]
satisfiability = ["c001"]
c002 = ["tig-algorithms/c002", "tig-challenges/c002"]
vehicle_routing = ["c002"]
c003 = ["tig-algorithms/c003", "tig-challenges/c003"]
knapsack = ["c003"]
c004 = ["cuda", "tig-algorithms/c004", "tig-challenges/c004"]
vector_search = ["c004"]
c005 = ["cuda", "tig-algorithms/c005", "tig-challenges/c005"]
hypergraph = ["c005"]

View File

@ -0,0 +1,48 @@
#!/bin/bash
set -e
# Check if CHALLENGE environment variable is set
if [ -z "$CHALLENGE" ]; then
echo "Error: CHALLENGE environment variable is not set."
exit 1
fi
# Check if ALGORITHM argument is provided
if [ -z "$1" ]; then
echo "Error: ALGORITHM argument is required."
echo "Usage: $0 <ALGORITHM>"
exit 1
fi
ALGORITHM="$1"
# Match CHALLENGE value
case "$CHALLENGE" in
satisfiability)
echo "Building ALGORITHM '$ALGORITHM' for CHALLENGE 'satisfiability'"
build_so $ALGORITHM
;;
vehicle_routing)
echo "Building ALGORITHM '$ALGORITHM' for CHALLENGE 'vehicle_routing'"
build_so $ALGORITHM
;;
knapsack)
echo "Building ALGORITHM '$ALGORITHM' for CHALLENGE 'knapsack'"
build_so $ALGORITHM
;;
vector_search)
echo "Building ALGORITHM '$ALGORITHM' for CHALLENGE 'vector_search'"
build_so $ALGORITHM
build_ptx $ALGORITHM
;;
hypergraph)
echo "Building ALGORITHM '$ALGORITHM' for CHALLENGE 'hypergraph'"
build_so $ALGORITHM
build_ptx $ALGORITHM
;;
*)
echo "Error: Invalid CHALLENGE value. Must be one of: satisfiability, knapsack, vehicle_routing, vector_search, hypergraph"
exit 1
;;
esac

View File

@ -3,11 +3,15 @@
import argparse import argparse
import os import os
import re import re
import shutil
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
CHALLENGE = os.getenv("CHALLENGE")
if CHALLENGE is None:
print("CHALLENGE environment variable must be set!")
sys.exit(1)
# Import the dictionary from ptx_instructions.py # Import the dictionary from ptx_instructions.py
instruction_fuel_cost = { instruction_fuel_cost = {
'add.u32': 2, 'add.u32': 2,
@ -232,12 +236,11 @@ $NORMAL_EXIT:
def main(): def main():
parser = argparse.ArgumentParser(description='Compile PTX with injected runtime signature') parser = argparse.ArgumentParser(description='Compile PTX with injected runtime signature')
parser.add_argument('challenge', help='Challenge name')
parser.add_argument('algorithm', help='Algorithm name') parser.add_argument('algorithm', help='Algorithm name')
args = parser.parse_args() args = parser.parse_args()
print(f"Compiling .ptx for {args.challenge}/{args.algorithm}") print(f"Compiling .ptx for {CHALLENGE}/{args.algorithm}")
framework_cu = "tig-binary/src/framework.cu" framework_cu = "tig-binary/src/framework.cu"
if not os.path.exists(framework_cu): if not os.path.exists(framework_cu):
@ -245,14 +248,14 @@ def main():
f"Framework code does not exist @ '{framework_cu}'. This script must be run from the root of tig-monorepo" f"Framework code does not exist @ '{framework_cu}'. This script must be run from the root of tig-monorepo"
) )
challenge_cu = f"tig-challenges/src/{args.challenge}.cu" challenge_cu = f"tig-challenges/src/{CHALLENGE}.cu"
if not os.path.exists(challenge_cu): if not os.path.exists(challenge_cu):
raise FileNotFoundError( raise FileNotFoundError(
f"Challenge code does not exist @ '{challenge_cu}'. Is the challenge name correct?" f"Challenge code does not exist @ '{challenge_cu}'. Is the challenge name correct?"
) )
algorithm_cu = f"tig-algorithms/src/{args.challenge}/{args.algorithm}.cu" algorithm_cu = f"tig-algorithms/src/{CHALLENGE}/{args.algorithm}.cu"
algorithm_cu2 = f"tig-algorithms/src/{args.challenge}/{args.algorithm}/benchmarker_outbound.cu" algorithm_cu2 = f"tig-algorithms/src/{CHALLENGE}/{args.algorithm}/benchmarker_outbound.cu"
if not os.path.exists(algorithm_cu) and not os.path.exists(algorithm_cu2): if not os.path.exists(algorithm_cu) and not os.path.exists(algorithm_cu2):
raise FileNotFoundError( raise FileNotFoundError(
f"Algorithm code does not exist @ '{algorithm_cu}' or '{algorithm_cu2}'. Is the algorithm name correct?" f"Algorithm code does not exist @ '{algorithm_cu}' or '{algorithm_cu2}'. Is the algorithm name correct?"
@ -295,10 +298,8 @@ def main():
parsed = parse_ptx_code(ptx_code) parsed = parse_ptx_code(ptx_code)
modified_code = inject_fuel_and_runtime_sig(parsed, kernels_to_ignore) modified_code = inject_fuel_and_runtime_sig(parsed, kernels_to_ignore)
output_path = f"tig-algorithms/lib/{args.challenge}/ptx/{args.algorithm}.ptx" output_path = f"tig-algorithms/lib/{CHALLENGE}/ptx/{args.algorithm}.ptx"
os.makedirs(os.path.dirname(output_path), exist_ok=True) os.makedirs(os.path.dirname(output_path), exist_ok=True)
# with open(output_path, 'w') as f:
# f.writelines(ptx_code)
with open(output_path, 'w') as f: with open(output_path, 'w') as f:
f.writelines(modified_code) f.writelines(modified_code)
print(f"Wrote ptx to {output_path}") print(f"Wrote ptx to {output_path}")

View File

@ -2,55 +2,34 @@
set -e set -e
CUDA=false # Check if CHALLENGE environment variable is set
if [ $# -lt 2 ]; then
echo "Error: Missing required arguments"
echo "Usage: $0 CHALLENGE ALGORITHM [--cuda]"
echo "Example: $0 knapsack dynamic"
echo "Example with CUDA: $0 knapsack dynamic --cuda"
exit 1
fi
CHALLENGE="$1"
ALGORITHM="$2"
shift 2
while [ $# -gt 0 ]; do
case "$1" in
--cuda)
CUDA=true
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 CHALLENGE ALGORITHM [--cuda]"
exit 1
;;
esac
shift
done
if [ -z "$CHALLENGE" ]; then if [ -z "$CHALLENGE" ]; then
echo "Error: CHALLENGE argument is empty" echo "Error: CHALLENGE environment variable is not set."
exit 1 exit 1
fi fi
if [ -z "$ALGORITHM" ]; then # Check if ALGORITHM argument is provided
echo "Error: ALGORITHM argument is empty" if [ -z "$1" ]; then
exit 1 echo "Error: ALGORITHM argument is required."
echo "Usage: $0 <ALGORITHM>"
exit 1
fi fi
ALGORITHM="$1"
echo "Compiling .so for $CHALLENGE/$ALGORITHM" echo "Compiling .so for $CHALLENGE/$ALGORITHM"
cp tig-binary/src/entry_point_template.rs tig-binary/src/entry_point.rs cp tig-binary/src/entry_point_template.rs tig-binary/src/entry_point.rs
sed -i "s/{CHALLENGE}/$CHALLENGE/g" tig-binary/src/entry_point.rs sed -i "s/{CHALLENGE}/$CHALLENGE/g" tig-binary/src/entry_point.rs
sed -i "s/{ALGORITHM}/$ALGORITHM/g" tig-binary/src/entry_point.rs sed -i "s/{ALGORITHM}/$ALGORITHM/g" tig-binary/src/entry_point.rs
if [ "$CUDA" = true ]; then FEATURES="entry_point $CHALLENGE"
FEATURES="entry_point cuda"
else
FEATURES="entry_point"
fi
ARCH=$(uname -m)
RUST_TARGET=$(if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
echo "aarch64-unknown-linux-gnu";
else
echo "x86_64-unknown-linux-gnu";
fi)
RUSTFLAGS="--emit=llvm-ir -C embed-bitcode=yes -C codegen-units=1 -C opt-level=3 -C lto=no -C debuginfo=2 -C relocation-model=pic" \ RUSTFLAGS="--emit=llvm-ir -C embed-bitcode=yes -C codegen-units=1 -C opt-level=3 -C lto=no -C debuginfo=2 -C relocation-model=pic" \
cargo +nightly-2025-02-10 build \ cargo +nightly-2025-02-10 build \
-p tig-binary \ -p tig-binary \

View File

@ -23,3 +23,13 @@ statrs = { version = "0.18.0" }
[features] [features]
cuda = ["cudarc"] cuda = ["cudarc"]
c001 = []
satisfiability = ["c001"]
c002 = []
vehicle_routing = ["c002"]
c003 = []
knapsack = ["c003"]
c004 = ["cuda"]
vector_search = ["c004"]
c005 = ["cuda"]
hypergraph = ["c005"]

View File

@ -1,17 +1,22 @@
pub const BUILD_TIME_PATH: &str = env!("CARGO_MANIFEST_DIR"); pub const BUILD_TIME_PATH: &str = env!("CARGO_MANIFEST_DIR");
pub mod knapsack; #[cfg(feature = "c001")]
pub use knapsack as c003;
pub mod satisfiability; pub mod satisfiability;
#[cfg(feature = "c001")]
pub use satisfiability as c001; pub use satisfiability as c001;
#[cfg(feature = "c002")]
pub mod vehicle_routing; pub mod vehicle_routing;
#[cfg(feature = "c002")]
pub use vehicle_routing as c002; pub use vehicle_routing as c002;
#[cfg(feature = "c003")]
#[cfg(feature = "cuda")] pub mod knapsack;
#[cfg(feature = "c003")]
pub use knapsack as c003;
#[cfg(feature = "c004")]
pub mod vector_search; pub mod vector_search;
#[cfg(feature = "cuda")] #[cfg(feature = "c004")]
pub use vector_search as c004; pub use vector_search as c004;
#[cfg(feature = "cuda")] #[cfg(feature = "c005")]
pub mod hypergraph; pub mod hypergraph;
#[cfg(feature = "cuda")] #[cfg(feature = "c005")]
pub use hypergraph as c005; pub use hypergraph as c005;

View File

@ -21,4 +21,14 @@ tig-structs = { path = "../tig-structs" }
tig-utils = { path = "../tig-utils" } tig-utils = { path = "../tig-utils" }
[features] [features]
cuda = ["cudarc", "tig-challenges/cuda"] cuda = ["cudarc"]
c001 = ["tig-challenges/c001"]
satisfiability = ["c001"]
c002 = ["tig-challenges/c002"]
vehicle_routing = ["c002"]
c003 = ["tig-challenges/c003"]
knapsack = ["c003"]
c004 = ["cuda", "tig-challenges/c004"]
vector_search = ["c004"]
c005 = ["cuda", "tig-challenges/c005"]
hypergraph = ["c005"]

View File

@ -51,7 +51,6 @@ fn cli() -> Command {
) )
.arg( .arg(
arg!(--gpu [GPU] "Which GPU device to use") arg!(--gpu [GPU] "Which GPU device to use")
.default_value("0")
.value_parser(clap::value_parser!(usize)), .value_parser(clap::value_parser!(usize)),
) )
} }
@ -68,7 +67,7 @@ fn main() {
*matches.get_one::<u64>("fuel").unwrap(), *matches.get_one::<u64>("fuel").unwrap(),
matches.get_one::<PathBuf>("output").cloned(), matches.get_one::<PathBuf>("output").cloned(),
matches.get_one::<bool>("compress").unwrap().clone(), matches.get_one::<bool>("compress").unwrap().clone(),
matches.get_one::<usize>("gpu").unwrap().clone(), matches.get_one::<usize>("gpu").cloned(),
) { ) {
eprintln!("Error: {}", e); eprintln!("Error: {}", e);
std::process::exit(1); std::process::exit(1);
@ -80,13 +79,11 @@ pub fn compute_solution(
rand_hash: String, rand_hash: String,
nonce: u64, nonce: u64,
library_path: PathBuf, library_path: PathBuf,
#[cfg(feature = "cuda")] ptx_path: Option<PathBuf>, ptx_path: Option<PathBuf>,
#[cfg(not(feature = "cuda"))] _ptx_path: Option<PathBuf>,
max_fuel: u64, max_fuel: u64,
output_file: Option<PathBuf>, output_file: Option<PathBuf>,
compress: bool, compress: bool,
#[cfg(feature = "cuda")] gpu_device: usize, gpu_device: Option<usize>,
#[cfg(not(feature = "cuda"))] _gpu_device: usize,
) -> Result<()> { ) -> Result<()> {
let settings = load_settings(&settings); let settings = load_settings(&settings);
let seed = settings.calc_seed(&rand_hash, nonce); let seed = settings.calc_seed(&rand_hash, nonce);
@ -97,7 +94,12 @@ pub fn compute_solution(
let runtime_signature_ptr = unsafe { *library.get::<*mut u64>(b"__runtime_signature")? }; let runtime_signature_ptr = unsafe { *library.get::<*mut u64>(b"__runtime_signature")? };
unsafe { *runtime_signature_ptr = u64::from_be_bytes(seed[0..8].try_into().unwrap()) }; unsafe { *runtime_signature_ptr = u64::from_be_bytes(seed[0..8].try_into().unwrap()) };
let (fuel_consumed, runtime_signature, solution, invalid_reason) = 'out_of_fuel: { let (fuel_consumed, runtime_signature, solution, invalid_reason): (
u64,
u64,
Solution,
Option<String>,
) = 'out_of_fuel: {
macro_rules! dispatch_challenge { macro_rules! dispatch_challenge {
($c:ident, cpu) => {{ ($c:ident, cpu) => {{
// library function may exit 87 if it runs out of fuel // library function may exit 87 if it runs out of fuel
@ -138,141 +140,157 @@ pub fn compute_solution(
}}; }};
($c:ident, gpu) => {{ ($c:ident, gpu) => {{
#[cfg(not(feature = "cuda"))] if ptx_path.is_none() {
panic!("tig-runtime was not compiled with '--features cuda'"); panic!("PTX file is required for GPU challenges.");
#[cfg(feature = "cuda")]
{
if ptx_path.is_none() {
panic!("PTX file is required for GPU challenges.");
}
let ptx_path = ptx_path.unwrap();
// library function may exit 87 if it runs out of fuel
let solve_challenge_fn = unsafe {
library.get::<fn(
&$c::Challenge,
Arc<CudaModule>,
Arc<CudaStream>,
&cudaDeviceProp,
) -> Result<Option<$c::Solution>, String>>(
b"entry_point"
)?
};
let gpu_fuel_scale = 20; // scale fuel to loosely align with CPU
let ptx_content = std::fs::read_to_string(&ptx_path)
.map_err(|e| anyhow!("Failed to read PTX file: {}", e))?;
let max_fuel_hex = format!("0x{:016x}", max_fuel * gpu_fuel_scale);
let modified_ptx = ptx_content.replace("0xdeadbeefdeadbeef", &max_fuel_hex);
let ptx = Ptx::from_src(modified_ptx);
let ctx = CudaContext::new(gpu_device)?;
ctx.set_blocking_synchronize()?;
let module = ctx.load_module(ptx)?;
let stream = ctx.fuel_check_stream();
let prop = get_device_prop(gpu_device as i32)?;
let challenge = $c::Challenge::generate_instance(
&seed,
&settings.difficulty.into(),
module.clone(),
stream.clone(),
&prop,
)?;
let initialize_kernel = module.load_function("initialize_kernel")?;
let cfg = LaunchConfig {
grid_dim: (1, 1, 1),
block_dim: (1, 1, 1),
shared_mem_bytes: 0,
};
unsafe {
stream
.launch_builder(&initialize_kernel)
.arg(&(u64::from_be_bytes(seed[8..16].try_into().unwrap())))
.launch(cfg)?;
}
let result =
solve_challenge_fn(&challenge, module.clone(), stream.clone(), &prop);
if result
.as_ref()
.is_err_and(|e| e.contains("ran out of fuel"))
{
break 'out_of_fuel (max_fuel + 1, 0, Solution::new(), None);
}
let result = result.map_err(|e| anyhow!("{}", e))?;
stream.synchronize()?;
ctx.synchronize()?;
let mut fuel_usage = stream.alloc_zeros::<u64>(1)?;
let mut signature = stream.alloc_zeros::<u64>(1)?;
let mut error_stat = stream.alloc_zeros::<u64>(1)?;
let finalize_kernel = module.load_function("finalize_kernel")?;
let cfg = LaunchConfig {
grid_dim: (1, 1, 1),
block_dim: (1, 1, 1),
shared_mem_bytes: 0,
};
unsafe {
stream
.launch_builder(&finalize_kernel)
.arg(&mut fuel_usage)
.arg(&mut signature)
.arg(&mut error_stat)
.launch(cfg)?;
}
if stream.memcpy_dtov(&error_stat)?[0] != 0 {
break 'out_of_fuel (max_fuel + 1, 0, Solution::new(), None);
}
let fuel_consumed = (stream.memcpy_dtov(&fuel_usage)?[0] / gpu_fuel_scale
+ max_fuel
- unsafe { **library.get::<*const u64>(b"__fuel_remaining")? });
if fuel_consumed > max_fuel {
break 'out_of_fuel (max_fuel + 1, 0, Solution::new(), None);
}
let runtime_signature = (stream.memcpy_dtov(&signature)?[0]
^ unsafe { **library.get::<*const u64>(b"__runtime_signature")? });
let (solution, invalid_reason) = match result {
Some(s) => (
serde_json::to_value(&s)
.unwrap()
.as_object()
.unwrap()
.to_owned(),
match challenge.verify_solution(
&s,
module.clone(),
stream.clone(),
&prop,
) {
Ok(_) => None,
Err(e) => Some(e.to_string()),
},
),
None => (Solution::new(), None),
};
(fuel_consumed, runtime_signature, solution, invalid_reason)
} }
let ptx_path = ptx_path.unwrap();
// library function may exit 87 if it runs out of fuel
let solve_challenge_fn = unsafe {
library.get::<fn(
&$c::Challenge,
Arc<CudaModule>,
Arc<CudaStream>,
&cudaDeviceProp,
) -> Result<Option<$c::Solution>, String>>(b"entry_point")?
};
let gpu_fuel_scale = 20; // scale fuel to loosely align with CPU
let ptx_content = std::fs::read_to_string(&ptx_path)
.map_err(|e| anyhow!("Failed to read PTX file: {}", e))?;
let max_fuel_hex = format!("0x{:016x}", max_fuel * gpu_fuel_scale);
let modified_ptx = ptx_content.replace("0xdeadbeefdeadbeef", &max_fuel_hex);
let num_gpus = CudaContext::device_count()?;
if num_gpus == 0 {
panic!("No CUDA devices found");
}
let gpu_device = gpu_device.unwrap_or((nonce % num_gpus as u64) as usize);
let ptx = Ptx::from_src(modified_ptx);
let ctx = CudaContext::new(gpu_device)?;
ctx.set_blocking_synchronize()?;
let module = ctx.load_module(ptx)?;
let stream = ctx.fuel_check_stream();
let prop = get_device_prop(gpu_device as i32)?;
let challenge = $c::Challenge::generate_instance(
&seed,
&settings.difficulty.into(),
module.clone(),
stream.clone(),
&prop,
)?;
let initialize_kernel = module.load_function("initialize_kernel")?;
let cfg = LaunchConfig {
grid_dim: (1, 1, 1),
block_dim: (1, 1, 1),
shared_mem_bytes: 0,
};
unsafe {
stream
.launch_builder(&initialize_kernel)
.arg(&(u64::from_be_bytes(seed[8..16].try_into().unwrap())))
.launch(cfg)?;
}
let result = solve_challenge_fn(&challenge, module.clone(), stream.clone(), &prop);
if result
.as_ref()
.is_err_and(|e| e.contains("ran out of fuel"))
{
break 'out_of_fuel (max_fuel + 1, 0, Solution::new(), None);
}
let result = result.map_err(|e| anyhow!("{}", e))?;
stream.synchronize()?;
ctx.synchronize()?;
let mut fuel_usage = stream.alloc_zeros::<u64>(1)?;
let mut signature = stream.alloc_zeros::<u64>(1)?;
let mut error_stat = stream.alloc_zeros::<u64>(1)?;
let finalize_kernel = module.load_function("finalize_kernel")?;
let cfg = LaunchConfig {
grid_dim: (1, 1, 1),
block_dim: (1, 1, 1),
shared_mem_bytes: 0,
};
unsafe {
stream
.launch_builder(&finalize_kernel)
.arg(&mut fuel_usage)
.arg(&mut signature)
.arg(&mut error_stat)
.launch(cfg)?;
}
if stream.memcpy_dtov(&error_stat)?[0] != 0 {
break 'out_of_fuel (max_fuel + 1, 0, Solution::new(), None);
}
let fuel_consumed = (stream.memcpy_dtov(&fuel_usage)?[0] / gpu_fuel_scale
+ max_fuel
- unsafe { **library.get::<*const u64>(b"__fuel_remaining")? });
if fuel_consumed > max_fuel {
break 'out_of_fuel (max_fuel + 1, 0, Solution::new(), None);
}
let runtime_signature = (stream.memcpy_dtov(&signature)?[0]
^ unsafe { **library.get::<*const u64>(b"__runtime_signature")? });
let (solution, invalid_reason) = match result {
Some(s) => (
serde_json::to_value(&s)
.unwrap()
.as_object()
.unwrap()
.to_owned(),
match challenge.verify_solution(&s, module.clone(), stream.clone(), &prop) {
Ok(_) => None,
Err(e) => Some(e.to_string()),
},
),
None => (Solution::new(), None),
};
(fuel_consumed, runtime_signature, solution, invalid_reason)
}}; }};
} }
match settings.challenge_id.as_str() { match settings.challenge_id.as_str() {
"c001" => dispatch_challenge!(c001, cpu), "c001" => {
"c002" => dispatch_challenge!(c002, cpu), #[cfg(not(feature = "c001"))]
"c003" => dispatch_challenge!(c003, cpu), panic!("tig-runtime was not compiled with '--features c001'");
"c004" => dispatch_challenge!(c004, gpu), #[cfg(feature = "c001")]
"c005" => dispatch_challenge!(c005, gpu), dispatch_challenge!(c001, cpu)
}
"c002" => {
#[cfg(not(feature = "c002"))]
panic!("tig-runtime was not compiled with '--features c002'");
#[cfg(feature = "c002")]
dispatch_challenge!(c002, cpu)
}
"c003" => {
#[cfg(not(feature = "c003"))]
panic!("tig-runtime was not compiled with '--features c003'");
#[cfg(feature = "c003")]
dispatch_challenge!(c003, cpu)
}
"c004" => {
#[cfg(not(feature = "c004"))]
panic!("tig-runtime was not compiled with '--features c004'");
#[cfg(feature = "c004")]
dispatch_challenge!(c004, gpu)
}
"c005" => {
#[cfg(not(feature = "c005"))]
panic!("tig-runtime was not compiled with '--features c005'");
#[cfg(feature = "c005")]
dispatch_challenge!(c005, gpu)
}
_ => panic!("Unsupported challenge"), _ => panic!("Unsupported challenge"),
} }
}; };

View File

@ -19,4 +19,14 @@ tig-structs = { path = "../tig-structs" }
tig-utils = { path = "../tig-utils" } tig-utils = { path = "../tig-utils" }
[features] [features]
cuda = ["cudarc", "tig-challenges/cuda"] cuda = ["cudarc"]
c001 = ["tig-challenges/c001"]
satisfiability = ["c001"]
c002 = ["tig-challenges/c002"]
vehicle_routing = ["c002"]
c003 = ["tig-challenges/c003"]
knapsack = ["c003"]
c004 = ["cuda", "tig-challenges/c004"]
vector_search = ["c004"]
c005 = ["cuda", "tig-challenges/c005"]
hypergraph = ["c005"]

View File

@ -34,7 +34,6 @@ fn cli() -> Command {
) )
.arg( .arg(
arg!(--gpu [GPU] "Which GPU device to use") arg!(--gpu [GPU] "Which GPU device to use")
.default_value("0")
.value_parser(clap::value_parser!(usize)), .value_parser(clap::value_parser!(usize)),
), ),
) )
@ -59,7 +58,7 @@ fn main() {
*sub_m.get_one::<u64>("NONCE").unwrap(), *sub_m.get_one::<u64>("NONCE").unwrap(),
sub_m.get_one::<String>("SOLUTION").unwrap().clone(), sub_m.get_one::<String>("SOLUTION").unwrap().clone(),
sub_m.get_one::<PathBuf>("ptx").cloned(), sub_m.get_one::<PathBuf>("ptx").cloned(),
sub_m.get_one::<usize>("gpu").unwrap().clone(), sub_m.get_one::<usize>("gpu").cloned(),
), ),
Some(("verify_merkle_proof", sub_m)) => verify_merkle_proof( Some(("verify_merkle_proof", sub_m)) => verify_merkle_proof(
sub_m.get_one::<String>("ROOT").unwrap().clone(), sub_m.get_one::<String>("ROOT").unwrap().clone(),
@ -78,7 +77,7 @@ pub fn verify_solution(
nonce: u64, nonce: u64,
solution_path: String, solution_path: String,
ptx_path: Option<PathBuf>, ptx_path: Option<PathBuf>,
gpu_device: usize, gpu_device: Option<usize>,
) -> Result<()> { ) -> Result<()> {
let settings = load_settings(&settings); let settings = load_settings(&settings);
let solution = load_solution(&solution_path); let solution = load_solution(&solution_path);
@ -86,90 +85,110 @@ pub fn verify_solution(
let mut err_msg = Option::<String>::None; let mut err_msg = Option::<String>::None;
macro_rules! dispatch_challenges { macro_rules! dispatch_challenge {
( $( ($c:ident, $cpu_or_gpu:tt) ),+ $(,)? ) => {{ ($c:ident, cpu) => {{
match settings.challenge_id.as_str() { let challenge =
$( $c::Challenge::generate_instance(&seed, &settings.difficulty.into()).unwrap();
stringify!($c) => {
dispatch_challenges!(@expand $c, $cpu_or_gpu); match $c::Solution::try_from(solution) {
} Ok(solution) => match challenge.verify_solution(&solution) {
)+ Ok(_) => println!("Solution is valid"),
_ => panic!("Unsupported challenge"), Err(e) => err_msg = Some(format!("Invalid solution: {}", e)),
},
Err(_) => {
err_msg = Some(format!(
"Invalid solution. Cannot convert to {}::Solution",
stringify!($c)
))
}
} }
}}; }};
(@expand $c:ident, cpu) => {{ ($c:ident, gpu) => {{
if ptx_path.is_none() {
panic!("PTX file is required for GPU challenges.");
}
let num_gpus = CudaContext::device_count()?;
if num_gpus == 0 {
panic!("No CUDA devices found");
}
let gpu_device = gpu_device.unwrap_or((nonce % num_gpus as u64) as usize);
let ptx = Ptx::from_file(ptx_path.unwrap());
let ctx = CudaContext::new(gpu_device).unwrap();
ctx.set_blocking_synchronize()?;
let module = ctx.load_module(ptx).unwrap();
let stream = ctx.default_stream();
let prop = get_device_prop(gpu_device as i32).unwrap();
let challenge = $c::Challenge::generate_instance( let challenge = $c::Challenge::generate_instance(
&seed, &seed,
&settings.difficulty.into(), &settings.difficulty.into(),
).unwrap(); module.clone(),
stream.clone(),
&prop,
)
.unwrap();
match $c::Solution::try_from(solution) { match $c::Solution::try_from(solution) {
Ok(solution) => { Ok(solution) => {
match challenge.verify_solution(&solution) { match challenge.verify_solution(
Ok(_) => println!("Solution is valid"), &solution,
module.clone(),
stream.clone(),
&prop,
) {
Ok(_) => {
stream.synchronize()?;
ctx.synchronize()?;
println!("Solution is valid");
}
Err(e) => err_msg = Some(format!("Invalid solution: {}", e)), Err(e) => err_msg = Some(format!("Invalid solution: {}", e)),
} }
},
Err(_) => err_msg = Some(format!(
"Invalid solution. Cannot convert to {}::Solution",
stringify!($c)
)),
}
}};
(@expand $c:ident, gpu) => {{
#[cfg(not(feature = "cuda"))]
panic!("tig-runtime was not compiled with '--features cuda'");
#[cfg(feature = "cuda")]
{
if ptx_path.is_none() {
panic!("PTX file is required for GPU challenges.");
} }
Err(_) => {
let ptx = Ptx::from_file(ptx_path.unwrap()); err_msg = Some(format!(
let ctx = CudaContext::new(gpu_device).unwrap();
ctx.set_blocking_synchronize()?;
let module = ctx.load_module(ptx).unwrap();
let stream = ctx.default_stream();
let prop = get_device_prop(gpu_device as i32).unwrap();
let challenge = $c::Challenge::generate_instance(
&seed,
&settings.difficulty.into(),
module.clone(),
stream.clone(),
&prop,
).unwrap();
match $c::Solution::try_from(solution) {
Ok(solution) => {
match challenge.verify_solution(&solution, module.clone(), stream.clone(), &prop) {
Ok(_) => {
stream.synchronize()?;
ctx.synchronize()?;
println!("Solution is valid");
},
Err(e) => err_msg = Some(format!("Invalid solution: {}", e)),
}
},
Err(_) => err_msg = Some(format!(
"Invalid solution. Cannot convert to {}::Solution", "Invalid solution. Cannot convert to {}::Solution",
stringify!($c) stringify!($c)
)), ))
} }
} }
}}; }};
} }
dispatch_challenges!( match settings.challenge_id.as_str() {
(c001, cpu), "c001" => {
(c002, cpu), #[cfg(not(feature = "c001"))]
(c003, cpu), panic!("tig-verifier was not compiled with '--features c001'");
(c004, gpu), #[cfg(feature = "c001")]
(c005, gpu) dispatch_challenge!(c001, cpu)
); }
"c002" => {
#[cfg(not(feature = "c002"))]
panic!("tig-verifier was not compiled with '--features c002'");
#[cfg(feature = "c002")]
dispatch_challenge!(c002, cpu)
}
"c003" => {
#[cfg(not(feature = "c003"))]
panic!("tig-verifier was not compiled with '--features c003'");
#[cfg(feature = "c003")]
dispatch_challenge!(c003, cpu)
}
"c004" => {
#[cfg(not(feature = "c004"))]
panic!("tig-verifier was not compiled with '--features c004'");
#[cfg(feature = "c004")]
dispatch_challenge!(c004, gpu)
}
"c005" => {
#[cfg(not(feature = "c005"))]
panic!("tig-verifier was not compiled with '--features c005'");
#[cfg(feature = "c005")]
dispatch_challenge!(c005, gpu)
}
_ => panic!("Unsupported challenge"),
}
if let Some(err_msg) = err_msg { if let Some(err_msg) = err_msg {
eprintln!("Verification error: {}", err_msg); eprintln!("Verification error: {}", err_msg);