mirror of
https://github.com/mcloh/decision-models-benchmark.git
synced 2026-11-06 06:14:11 +00:00
setup models
This commit is contained in:
53
infra/ds/build_env.sh
Executable file
53
infra/ds/build_env.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
# Job de build (rede gerenciada, com internet): cria o ambiente Python do candidato com as
|
||||
# versões fixadas de env/requirements/, empacota e publica em dmb-artefatos/envs/<candidato>/.
|
||||
# Os jobs de benchmark (sub-rede privada) só baixam esse pacote; nunca instalam da internet.
|
||||
set -euo pipefail
|
||||
CAND="${DMB_CANDIDATE:?defina DMB_CANDIDATE}"
|
||||
CODE="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
BASE_PY=/opt/conda/bin/python3
|
||||
ENV_DIR="$HOME/envs/$CAND"
|
||||
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
cd "$CODE"
|
||||
|
||||
echo "== build $CAND em $ENV_DIR (código $DMB_CODE_REV)"
|
||||
rm -rf "$ENV_DIR"
|
||||
"$BASE_PY" -m venv "$ENV_DIR"
|
||||
"$ENV_DIR/bin/pip" install -q --upgrade "pip==25.2"
|
||||
"$ENV_DIR/bin/pip" install -q -r env/requirements/common.txt "pytest==8.4.2" "httpx==0.28.1"
|
||||
"$ENV_DIR/bin/pip" install -q --extra-index-url https://download.pytorch.org/whl/cu128 -r "env/requirements/$CAND.txt"
|
||||
|
||||
src_object() { # objeto do tarball de código-fonte do candidato, segundo o manifesto
|
||||
"$BASE_PY" -c "import json,sys; m=json.load(open('env/artifacts-manifest.json')); print(next(a['object'] for a in m['artifacts'] if a['candidate']=='$CAND' and a['kind']=='source'))"
|
||||
}
|
||||
case "$CAND" in
|
||||
semif|rizzo_flow)
|
||||
OBJ="$(src_object)"
|
||||
"$BASE_PY" -m harness.objstore get dmb-artefatos "$OBJ" /tmp/src.tar.gz
|
||||
mkdir -p /tmp/src && tar xzf /tmp/src.tar.gz -C /tmp/src
|
||||
"$ENV_DIR/bin/pip" install -q --no-deps /tmp/src/*/
|
||||
;;
|
||||
esac
|
||||
if [ "$CAND" = rizzo_flow ]; then
|
||||
# Runtime llama.cpp b11081 (CPU e CUDA), com o SHA-256 conferido pelo próprio Rizzo Flow.
|
||||
(cd "$ENV_DIR" && "$ENV_DIR/bin/rizzo" download --only runtime --runtime cpu \
|
||||
&& "$ENV_DIR/bin/rizzo" download --only runtime --runtime cuda && rm -rf runtimes/downloads)
|
||||
fi
|
||||
|
||||
"$ENV_DIR/bin/python" -m harness.image_manifest > "$ENV_DIR/env-manifest.json"
|
||||
"$ENV_DIR/bin/python" -m pytest -q tests 2>&1 | tail -2
|
||||
|
||||
TARBALL="/tmp/$CAND-env.tar.gz"
|
||||
tar czf "$TARBALL" -C "$HOME" "envs/$CAND"
|
||||
OBJECT="envs/$CAND/$STAMP.tar.gz"
|
||||
SHA="$("$BASE_PY" -m harness.objstore put dmb-artefatos "$OBJECT" "$TARBALL" --sha256)"
|
||||
SIZE="$(stat -c %s "$TARBALL")"
|
||||
cat > /tmp/latest.json <<JSON
|
||||
{"candidate": "$CAND", "object": "$OBJECT", "sha256": "$SHA", "bytes": $SIZE,
|
||||
"built_at": "$STAMP", "code_rev": "${DMB_CODE_REV:-}", "python": "$("$ENV_DIR/bin/python" --version 2>&1)",
|
||||
"home": "$HOME"}
|
||||
JSON
|
||||
"$BASE_PY" -m harness.objstore put dmb-artefatos "envs/$CAND/latest.json" /tmp/latest.json
|
||||
"$BASE_PY" -m harness.objstore put dmb-artefatos "envs/$CAND/$STAMP-env-manifest.json" "$ENV_DIR/env-manifest.json"
|
||||
cat /tmp/latest.json
|
||||
echo "== ok"
|
||||
51
infra/ds/build_llama_cuda.sh
Executable file
51
infra/ds/build_llama_cuda.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
# Job de build (rede gerenciada): compila o llama.cpp com CUDA para a imagem dos jobs do Data Science.
|
||||
# Os binários oficiais CUDA do llama.cpp exigem GLIBC 2.38, ausente nessa imagem. Compilamos o mesmo
|
||||
# commit fixado pelo Rizzo Flow, com sysroot glibc 2.17, libstdc++ estática e CUDA 12.8 (sm_80/sm_86),
|
||||
# no mesmo layout dos pacotes oficiais (backends como plug-ins, libs CUDA no mesmo diretório).
|
||||
set -euo pipefail
|
||||
COMMIT=161755f29e415e2c33efe906e91843c068efd664 # rizzo_flow.llama_release.COMMIT (b11081)
|
||||
CODE="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
BASE_PY=/opt/conda/bin/python3
|
||||
TC="$HOME/toolchain"
|
||||
OUT_NAME="llama-b11081-linux-x64-cuda-dsjobs"
|
||||
OUT="$HOME/$OUT_NAME"
|
||||
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
|
||||
echo "== glibc da imagem: $(ldd --version | head -1)"
|
||||
/opt/conda/bin/conda create -q -y -p "$TC" -c conda-forge \
|
||||
"cuda-nvcc=12.8" "cuda-cudart-dev=12.8" "libcublas-dev=12.8" "cuda-version=12.8" \
|
||||
cmake ninja git "gxx_linux-64=13" "sysroot_linux-64=2.17" >/dev/null
|
||||
export PATH="$TC/bin:$PATH" CUDAToolkit_ROOT="$TC" CUDACXX="$TC/bin/nvcc"
|
||||
export CC="$TC/bin/x86_64-conda-linux-gnu-gcc" CXX="$TC/bin/x86_64-conda-linux-gnu-g++"
|
||||
|
||||
git clone -q https://github.com/ggml-org/llama.cpp /tmp/llama.cpp
|
||||
git -C /tmp/llama.cpp checkout -q "$COMMIT"
|
||||
cmake -S /tmp/llama.cpp -B /tmp/build -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=ON -DGGML_BACKEND_DL=ON -DGGML_NATIVE=OFF -DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES="80;86" -DLLAMA_CURL=OFF \
|
||||
-DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_SERVER=OFF -DLLAMA_BUILD_TOOLS=OFF \
|
||||
-DCMAKE_SHARED_LINKER_FLAGS="-static-libstdc++ -static-libgcc" \
|
||||
-DCMAKE_BUILD_RPATH_USE_ORIGIN=ON -DCMAKE_INSTALL_RPATH='$ORIGIN' -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
>/tmp/cmake.log
|
||||
cmake --build /tmp/build -j "$(nproc)" >/tmp/build.log 2>&1 || { tail -50 /tmp/build.log; exit 1; }
|
||||
|
||||
rm -rf "$OUT" && mkdir -p "$OUT"
|
||||
find /tmp/build/bin -maxdepth 1 -name "*.so" -exec cp -L {} "$OUT/" \;
|
||||
for lib in libcudart.so.12 libcublas.so.12 libcublasLt.so.12; do cp -L "$TC/lib/$lib" "$OUT/"; done
|
||||
ls -la "$OUT"
|
||||
echo "== dependências não resolvidas:"; for f in "$OUT"/*.so*; do LD_LIBRARY_PATH="$OUT" ldd "$f" | grep "not found" || true; done
|
||||
echo "== maior GLIBC exigida:"; objdump -T "$OUT"/*.so* 2>/dev/null | grep -o "GLIBC_[0-9.]*" | sort -uV | tail -1
|
||||
|
||||
cd "$CODE"
|
||||
TARBALL="/tmp/$OUT_NAME.tar.gz"
|
||||
tar czf "$TARBALL" -C "$HOME" "$OUT_NAME"
|
||||
OBJECT="runtimes/rizzo_flow/$OUT_NAME-$STAMP.tar.gz"
|
||||
SHA="$("$BASE_PY" -m harness.objstore put dmb-artefatos "$OBJECT" "$TARBALL" --sha256)"
|
||||
cat > /tmp/cuda-latest.json <<JSON
|
||||
{"object": "$OBJECT", "sha256": "$SHA", "dir": "$OUT_NAME", "llama_commit": "$COMMIT",
|
||||
"cuda": "12.8", "archs": "80;86", "built_at": "$STAMP", "code_rev": "${DMB_CODE_REV:-}"}
|
||||
JSON
|
||||
"$BASE_PY" -m harness.objstore put dmb-artefatos runtimes/rizzo_flow/cuda-latest.json /tmp/cuda-latest.json
|
||||
cat /tmp/cuda-latest.json
|
||||
echo "== ok"
|
||||
37
infra/ds/gpu_probe.py
Normal file
37
infra/ds/gpu_probe.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Encontra um shape de GPU com capacidade: roda o job em cada shape, em ordem, até um não falhar
|
||||
por falta de capacidade. Imprime o shape usado e o estado final.
|
||||
|
||||
Uso: PYTHONPATH=infra/oci:infra/ds .venv/bin/python infra/ds/gpu_probe.py <candidato> [shape ...]
|
||||
"""
|
||||
import sys
|
||||
|
||||
import jobs
|
||||
|
||||
DEFAULT_SHAPES = ["VM.GPU.A10.1", "VM.GPU.A10.2", "VM.GPU.A100.B40G.1", "VM.GPU.A100.80G.1", "VM.GPU3.1"]
|
||||
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
region = None
|
||||
if args and args[0].startswith("--region="):
|
||||
region = args.pop(0).split("=", 1)[1]
|
||||
candidate, shapes = args[0], args[1:] or DEFAULT_SHAPES
|
||||
for shape in shapes:
|
||||
try:
|
||||
run_id = jobs.run_job(f"sanity-{candidate}-gpu", "infra/ds/run_job.sh", shape, None, None, region is None,
|
||||
{"DMB_CANDIDATE": candidate, "DMB_MODE": "sanity", "DMB_DEVICE": "auto"},
|
||||
storage_gb=60, region=region)
|
||||
except jobs.oci.exceptions.ServiceError as error:
|
||||
print(f"indisponível: {shape} ({error.code})", flush=True)
|
||||
continue
|
||||
r = jobs.wait(run_id, poll=20)
|
||||
if "no capacity" in (r.lifecycle_details or "").lower():
|
||||
print(f"sem capacidade: {shape}", flush=True)
|
||||
continue
|
||||
print(f"SHAPE {shape} -> {r.lifecycle_state} {run_id}", flush=True)
|
||||
return
|
||||
print("nenhum shape com capacidade", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
47
infra/ds/gpu_queue.py
Normal file
47
infra/ds/gpu_queue.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Fila de jobs de GPU (o limite em sa-saopaulo-1 é de uma A10 por vez).
|
||||
|
||||
Uso: PYTHONPATH=infra/oci:infra/ds .venv/bin/python infra/ds/gpu_queue.py <modo> <candidato> [...] \
|
||||
[--after <job_run_ocid>] [--shape VM.GPU.A10.1]
|
||||
Grava "candidato run_ocid estado" em runs/gpu-queue.log.
|
||||
"""
|
||||
import argparse
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import jobs
|
||||
import provision
|
||||
|
||||
LOG = Path(__file__).resolve().parents[2] / "runs" / "gpu-queue.log"
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("mode")
|
||||
ap.add_argument("candidates", nargs="+")
|
||||
ap.add_argument("--after")
|
||||
ap.add_argument("--shape", default="VM.GPU.A10.1")
|
||||
ap.add_argument("--storage", type=int, default=100)
|
||||
args = ap.parse_args()
|
||||
LOG.parent.mkdir(exist_ok=True)
|
||||
if args.after:
|
||||
jobs.wait(args.after, poll=30)
|
||||
for cand in args.candidates:
|
||||
while True:
|
||||
try:
|
||||
run_id = jobs.run_job(f"{args.mode}-{cand}-gpu", "infra/ds/run_job.sh", args.shape, None, None,
|
||||
False, {"DMB_CANDIDATE": cand, "DMB_MODE": args.mode, "DMB_DEVICE": "auto"},
|
||||
storage_gb=args.storage, region=provision.GPU_REGION)
|
||||
break
|
||||
except jobs.oci.exceptions.ServiceError as error:
|
||||
if error.code != "LimitExceeded":
|
||||
raise
|
||||
time.sleep(60) # outra GPU ainda ocupada
|
||||
r = jobs.wait(run_id, poll=30)
|
||||
line = f"{cand} {run_id} {r.lifecycle_state} {(r.lifecycle_details or '')[:80]}"
|
||||
print(line, flush=True)
|
||||
with LOG.open("a") as f:
|
||||
f.write(line + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
187
infra/ds/jobs.py
Normal file
187
infra/ds/jobs.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""Execução no OCI Data Science: a máquina local só empacota o código e dispara jobs.
|
||||
|
||||
Uso:
|
||||
PYTHONPATH=infra/oci .venv/bin/python infra/ds/jobs.py run --name probe --entrypoint infra/ds/probe.sh \
|
||||
[--shape VM.Standard.E4.Flex --ocpus 2 --memory 16 | --shape VM.GPU.A10.1] [--private] \
|
||||
[--env K=V ...] [--wait]
|
||||
PYTHONPATH=infra/oci .venv/bin/python infra/ds/jobs.py logs <job_run_ocid>
|
||||
|
||||
--private usa a sub-rede dmb-subnet-jobs (sem internet; só Service Gateway).
|
||||
Sem --private, o job usa a rede gerenciada do serviço (com internet), só para builds.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import zipfile
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import oci
|
||||
|
||||
from session import load_config
|
||||
import provision
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
EXCLUDE_DIRS = {".git", ".secrets", "models", "reports", "runs", "__pycache__", ".pytest_cache"}
|
||||
LOG_GROUP = "dmb-logs"
|
||||
|
||||
|
||||
def compartment(cfg_home) -> str:
|
||||
return provision.find_compartment(oci.identity.IdentityClient(cfg_home), cfg_home["tenancy"])
|
||||
|
||||
|
||||
def code_zip() -> tuple[bytes, str]:
|
||||
"""Snapshot do repositório local (sem segredos, modelos e venvs)."""
|
||||
buf = io.BytesIO()
|
||||
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
|
||||
z.writestr(zipfile.ZipInfo("dmb/"), "")
|
||||
for p in sorted(ROOT.rglob("*")):
|
||||
rel = p.relative_to(ROOT)
|
||||
if any(part in EXCLUDE_DIRS or part.startswith(".venv") for part in rel.parts):
|
||||
continue
|
||||
if p.is_dir():
|
||||
z.writestr(zipfile.ZipInfo(f"dmb/{rel.as_posix()}/"), "")
|
||||
continue
|
||||
info = zipfile.ZipInfo.from_file(p, f"dmb/{rel.as_posix()}")
|
||||
info.compress_type = zipfile.ZIP_DEFLATED
|
||||
z.writestr(info, p.read_bytes())
|
||||
try:
|
||||
rev = subprocess.run(["git", "-C", str(ROOT), "rev-parse", "--short", "HEAD"],
|
||||
capture_output=True, text=True).stdout.strip() or "sem-commit"
|
||||
except OSError:
|
||||
rev = "sem-commit"
|
||||
return buf.getvalue(), rev
|
||||
|
||||
|
||||
def ensure_log_group(cfg, cid) -> str:
|
||||
lc = oci.logging.LoggingManagementClient(cfg)
|
||||
lg = next((g for g in lc.list_log_groups(cid, display_name=LOG_GROUP).data), None)
|
||||
if lg is None:
|
||||
lc.create_log_group(oci.logging.models.CreateLogGroupDetails(
|
||||
compartment_id=cid, display_name=LOG_GROUP, description="Logs dos jobs do benchmark",
|
||||
freeform_tags=provision.TAGS))
|
||||
for _ in range(30):
|
||||
lg = next((g for g in lc.list_log_groups(cid, display_name=LOG_GROUP).data), None)
|
||||
if lg:
|
||||
break
|
||||
time.sleep(5)
|
||||
return lg.id
|
||||
|
||||
|
||||
def subnet_id(cfg, cid) -> str:
|
||||
vn = oci.core.VirtualNetworkClient(cfg)
|
||||
return next(s.id for s in vn.list_subnets(cid, display_name="dmb-subnet-jobs").data
|
||||
if s.lifecycle_state == "AVAILABLE")
|
||||
|
||||
|
||||
def run_job(name, entrypoint, shape, ocpus, memory, private, env, storage_gb=100, max_minutes=240, region=None):
|
||||
cfg, home = load_config(region=region) if region else load_config(), load_config(region="us-ashburn-1")
|
||||
cid = compartment(home)
|
||||
ds = oci.data_science.DataScienceClient(cfg)
|
||||
project = next(p.id for p in ds.list_projects(cid, lifecycle_state="ACTIVE").data
|
||||
if p.display_name == "benchmark-decisoes-ptbr")
|
||||
log_group = ensure_log_group(cfg, cid)
|
||||
flex = shape.endswith(".Flex")
|
||||
infra = oci.data_science.models.StandaloneJobInfrastructureConfigurationDetails(
|
||||
job_infrastructure_type="STANDALONE" if private else "ME_STANDALONE",
|
||||
shape_name=shape, block_storage_size_in_gbs=storage_gb,
|
||||
subnet_id=subnet_id(cfg, cid) if private else None,
|
||||
job_shape_config_details=oci.data_science.models.JobShapeConfigDetails(
|
||||
ocpus=ocpus, memory_in_gbs=memory) if flex else None)
|
||||
payload, rev = code_zip()
|
||||
stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
||||
env = {"JOB_RUN_ENTRYPOINT": f"dmb/{entrypoint}", "DMB_CODE_REV": rev, **env}
|
||||
if cfg["region"] == provision.GPU_REGION:
|
||||
env.setdefault("DMB_BUCKET_ARTIFACTS", f"dmb-artefatos{provision.GPU_SUFFIX}")
|
||||
env.setdefault("DMB_BUCKET_RESULTS", f"dmb-resultados{provision.GPU_SUFFIX}")
|
||||
if not private:
|
||||
env.setdefault("DMB_ISOLATION", "app")
|
||||
job = ds.create_job(oci.data_science.models.CreateJobDetails(
|
||||
project_id=project, compartment_id=cid, display_name=f"dmb-{name}-{stamp}",
|
||||
freeform_tags=provision.TAGS,
|
||||
job_configuration_details=oci.data_science.models.DefaultJobConfigurationDetails(
|
||||
job_type="DEFAULT", environment_variables=env,
|
||||
maximum_runtime_in_minutes=max_minutes),
|
||||
job_infrastructure_configuration_details=infra,
|
||||
job_log_configuration_details=oci.data_science.models.JobLogConfigurationDetails(
|
||||
enable_logging=True, enable_auto_log_creation=True, log_group_id=log_group))).data
|
||||
ds.create_job_artifact(job.id, payload, content_disposition="attachment; filename=dmb.zip")
|
||||
run = ds.create_job_run(oci.data_science.models.CreateJobRunDetails(
|
||||
project_id=project, compartment_id=cid, job_id=job.id,
|
||||
display_name=f"dmb-{name}-{stamp}", freeform_tags=provision.TAGS)).data
|
||||
print(f"job {job.id}\nrun {run.id}", flush=True)
|
||||
return run.id
|
||||
|
||||
|
||||
def region_of(ocid: str) -> str:
|
||||
return ocid.split(".")[3] if ocid.count(".") >= 4 and ocid.split(".")[3] else load_config()["region"]
|
||||
|
||||
|
||||
def wait(run_id, poll=30):
|
||||
ds = oci.data_science.DataScienceClient(load_config(region=region_of(run_id)))
|
||||
while True:
|
||||
r = ds.get_job_run(run_id).data
|
||||
if r.lifecycle_state in ("SUCCEEDED", "FAILED", "CANCELED", "DELETED"):
|
||||
print("estado:", r.lifecycle_state, r.lifecycle_details or "", flush=True)
|
||||
return r
|
||||
time.sleep(poll)
|
||||
|
||||
|
||||
def logs(run_id, limit=500):
|
||||
cfg = load_config(region=region_of(run_id))
|
||||
ds = oci.data_science.DataScienceClient(cfg)
|
||||
r = ds.get_job_run(run_id).data
|
||||
if not r.log_details:
|
||||
print("sem log configurado ainda")
|
||||
return
|
||||
search = oci.loggingsearch.LogSearchClient(cfg)
|
||||
start = r.time_accepted - timedelta(minutes=5)
|
||||
end = (r.time_finished or datetime.now(timezone.utc)) + timedelta(minutes=10)
|
||||
query = (f'search "{r.compartment_id}/{r.log_details.log_group_id}/{r.log_details.log_id}" '
|
||||
"| sort by datetime asc")
|
||||
try:
|
||||
res = search.search_logs(oci.loggingsearch.models.SearchLogsDetails(
|
||||
time_start=start, time_end=end, search_query=query, is_return_field_info=False), limit=limit).data
|
||||
except oci.exceptions.ServiceError as error:
|
||||
print(f"logs indisponíveis ({error.status} {error.code}); log_id={r.log_details.log_id}")
|
||||
return
|
||||
for item in res.results or []:
|
||||
print(item.data.get("logContent", {}).get("data", {}).get("message", ""))
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
r = sub.add_parser("run")
|
||||
r.add_argument("--name", required=True)
|
||||
r.add_argument("--entrypoint", required=True)
|
||||
r.add_argument("--shape", default="VM.Standard.E4.Flex")
|
||||
r.add_argument("--ocpus", type=float, default=2)
|
||||
r.add_argument("--memory", type=float, default=16)
|
||||
r.add_argument("--storage", type=int, default=100)
|
||||
r.add_argument("--private", action="store_true")
|
||||
r.add_argument("--env", nargs="*", default=[])
|
||||
r.add_argument("--wait", action="store_true")
|
||||
r.add_argument("--region", help="padrão: us-chicago-1; sa-saopaulo-1 para GPU")
|
||||
lg = sub.add_parser("logs")
|
||||
lg.add_argument("run_id")
|
||||
args = ap.parse_args()
|
||||
if args.cmd == "logs":
|
||||
logs(args.run_id)
|
||||
return
|
||||
env = dict(kv.split("=", 1) for kv in args.env)
|
||||
run_id = run_job(args.name, args.entrypoint, args.shape, args.ocpus, args.memory, args.private,
|
||||
env, storage_gb=args.storage, region=args.region)
|
||||
if args.wait:
|
||||
result = wait(run_id)
|
||||
time.sleep(60) # ingestão dos logs
|
||||
logs(run_id)
|
||||
sys.exit(0 if result.lifecycle_state == "SUCCEEDED" else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
11
infra/ds/probe.sh
Executable file
11
infra/ds/probe.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Sonda do ambiente padrão dos jobs do Data Science.
|
||||
set -x
|
||||
whoami; echo "HOME=$HOME PWD=$PWD"; cat /etc/os-release | head -3; uname -m
|
||||
which python3 python conda curl tar unzip git 2>&1
|
||||
python3 --version 2>&1; python3 -c "import oci; print('oci', oci.__version__)" 2>&1
|
||||
df -h $HOME /tmp 2>&1 | tail -3; nproc; free -g | head -2
|
||||
nvidia-smi 2>&1 | head -5
|
||||
env | grep -E '^(JOB_|OCI_|CONDA|DMB_|NB_)' | sed -E 's/(TOKEN|KEY|SECRET)=.*/\1=***/' | sort
|
||||
ls "$PWD" | head; ls /home/datascience 2>&1 | head
|
||||
timeout 5 curl -sI https://pypi.org | head -1 || echo "sem internet"
|
||||
38
infra/ds/results.py
Normal file
38
infra/ds/results.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""Resumo das execuções gravadas em dmb-resultados/runs/.
|
||||
|
||||
Uso: PYTHONPATH=infra/oci .venv/bin/python infra/ds/results.py [filtro]
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
|
||||
import oci
|
||||
|
||||
from session import load_config
|
||||
|
||||
BUCKET = "dmb-resultados"
|
||||
|
||||
|
||||
def main():
|
||||
needle = sys.argv[1] if len(sys.argv) > 1 else ""
|
||||
c = oci.object_storage.ObjectStorageClient(load_config())
|
||||
ns = c.get_namespace().data
|
||||
names = [o.name for o in oci.pagination.list_call_get_all_results(
|
||||
c.list_objects, ns, BUCKET, prefix="runs/").data.objects]
|
||||
for name in sorted(n for n in names if n.endswith("/run.json") and needle in n):
|
||||
run = json.loads(c.get_object(ns, BUCKET, name).data.content)
|
||||
line = {k: run.get(k) for k in ("run_id", "candidate", "device", "status")}
|
||||
line["gate"] = run.get("egress_gate", {}).get("passed")
|
||||
prefix = name.rsplit("/", 1)[0]
|
||||
for n in names:
|
||||
if n.startswith(prefix) and n.endswith(".json") and n != name:
|
||||
rep = json.loads(c.get_object(ns, BUCKET, n).data.content)
|
||||
if "summary" in rep:
|
||||
line |= rep["summary"] | {"load_s": rep.get("load_seconds")}
|
||||
lat = [r["latency_ms"] for r in rep.get("sanity", [])]
|
||||
if lat:
|
||||
line["latency_ms_median"] = sorted(lat)[len(lat) // 2]
|
||||
print(json.dumps(line, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
6
infra/ds/run_job.sh
Executable file
6
infra/ds/run_job.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Entrada dos jobs de benchmark: usa o Python base do job (SDK OCI + resource principal)
|
||||
# para o processo pai; a inferência roda no ambiente do candidato (ver harness/job.py).
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/../.."
|
||||
exec /opt/conda/bin/python3 -m harness.job
|
||||
Reference in New Issue
Block a user