| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import glob |
| import os |
| from pathlib import Path |
| import re |
| import subprocess |
| import time |
| from typing import List, Optional, Tuple |
|
|
| import gmpy2 |
| from gmpy2 import mpz |
|
|
|
|
| def gpu_available(log) -> bool: |
| """True only if a usable NVIDIA GPU and the msieve GPU runtime are present.""" |
| if os.environ.get("DISABLE_GPU"): |
| log("GPU: disabled via DISABLE_GPU") |
| return False |
| if not (os.path.exists("/dev/nvidiactl") or glob.glob("/dev/nvidia[0-9]*")): |
| log("GPU: no /dev/nvidia* device; using CPU linear algebra") |
| return False |
| |
| if not any(os.path.exists(p) for p in ( |
| "/usr/lib/x86_64-linux-gnu/libcuda.so.1", "/usr/lib/libcuda.so.1", |
| "/lib/x86_64-linux-gnu/libcuda.so.1")): |
| log("GPU: libcuda.so.1 not present (no --gpus?); using CPU linear algebra") |
| return False |
| msieve = _msieve_bin() |
| if not msieve: |
| log("GPU: msieve binary not found; using CPU linear algebra") |
| return False |
| log("GPU: NVIDIA device + msieve runtime detected") |
| return True |
|
|
|
|
| def _msieve_bin() -> Optional[str]: |
| for c in (os.environ.get("MSIEVE_BIN", ""), "/app/msieve/msieve", |
| "/opt/msieve/msieve"): |
| if c and os.path.isfile(c): |
| return c |
| return None |
|
|
|
|
| def _msieve_dir() -> str: |
| """Dir holding msieve + cub/ + lanczos_kernel.ptx (the GPU runtime CWD).""" |
| b = _msieve_bin() |
| return str(Path(b).parent) if b else "/app/msieve" |
|
|
|
|
| def _run(cmd: List[str], env: dict, cwd: str, deadline: float, log, |
| tag: str) -> Tuple[int, str]: |
| """Run a handoff tool with a deadline; return (rc, combined tail).""" |
| timeout = max(5, int(deadline - time.time())) |
| try: |
| p = subprocess.run(cmd, env=env, cwd=cwd, stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, text=True, timeout=timeout) |
| except subprocess.TimeoutExpired: |
| log(f"GPU/{tag}: timed out") |
| return 124, "" |
| except Exception as e: |
| log(f"GPU/{tag}: launch error: {e}") |
| return 1, "" |
| out = p.stdout or "" |
| if p.returncode != 0: |
| for ln in out.strip().splitlines()[-8:]: |
| log(f"GPU/{tag}: {ln[:200]}") |
| return p.returncode, out |
|
|
|
|
| def run_gpu_linalg(workdir: str, n: int, deadline: float, |
| shim: str, sock: str, base_env: dict, log |
| ) -> Optional[Tuple[int, int]]: |
| """Run GPU LA+sqrt on CADO's filtered matrix. Return (p, q) or None.""" |
| msieve = _msieve_bin() |
| if not msieve: |
| return None |
| mdir = _msieve_dir() |
|
|
| |
| senv = dict(base_env) |
| senv["LD_PRELOAD"] = shim |
| senv["RAMNFS_SOCK"] = sock |
| senv["RAMNFS_PREFIX"] = "/ramwork" |
|
|
| |
| |
| |
| name = _discover_name(workdir, senv, mdir, deadline, log) |
| if not name: |
| log("GPU: could not locate CADO's purged file; skipping GPU path") |
| return None |
|
|
| |
| |
| |
| |
| mname = f"{name}mq" |
| purged_gz = os.path.join(workdir, f"{name}.purged.gz") |
| history_gz = os.path.join(workdir, f"{name}.history.gz") |
| cyc = os.path.join(workdir, f"{mname}.cyc") |
| savefile = os.path.join(workdir, mname) |
| fb = os.path.join(workdir, f"{mname}.fb") |
| worktodo = os.path.join(workdir, f"{mname}.todo") |
| mlog = os.path.join(os.environ.get("TMPDIR", "/tmp"), "msieve_la.log") |
|
|
| |
| replay = _find_replay() |
| if not replay: |
| log("GPU: CADO replay binary not found") |
| return None |
| rc, _ = _run([replay, "--for_msieve", "skip=0", "--purged", purged_gz, |
| "--his", history_gz, "--out", cyc], senv, mdir, deadline, log, |
| "replay") |
| if rc != 0: |
| return None |
|
|
| |
| |
| |
| builder = os.path.join(os.path.dirname(os.path.abspath(__file__)), |
| "section3_build.py") |
| rc, _ = _run(["python3", builder, workdir, name, mname, str(n)], |
| senv, mdir, deadline, log, "build") |
| if rc != 0: |
| return None |
|
|
| |
| rc, out = _run([msieve, "-v", "-i", worktodo, "-l", mlog, |
| "-g", os.environ.get("GPU_DEVICE", "0"), |
| "-nf", fb, "-s", savefile, "-nc2", "cado_filter=1"], |
| senv, mdir, deadline, log, "la") |
| if rc != 0 or "lanczos halted" not in out: |
| log("GPU: linear algebra did not complete") |
| return None |
| m = re.search(r"BLanczosTime:\s*(\d+)", out) |
| if m: |
| log(f"GPU: linear algebra done in {m.group(1)}s") |
|
|
| |
| rc, out = _run([msieve, "-v", "-i", worktodo, "-l", mlog, |
| "-nf", fb, "-s", savefile, "-nc3", "cado_filter=1"], |
| senv, mdir, deadline, log, "sqrt") |
| if rc != 0: |
| return None |
| return _factors_from_msieve(out, n) |
|
|
|
|
| def _discover_name(workdir, senv, mdir, deadline, log) -> Optional[str]: |
| """Find CADO's file prefix by locating <prefix>.purged.gz under the shim.""" |
| code = ( |
| "import glob,os,sys;" |
| f"g=sorted(glob.glob(os.path.join({workdir!r},'*.purged.gz')));" |
| "sys.stdout.write(os.path.basename(g[0])[:-10] if g else '')" |
| ) |
| rc, out = _run(["python3", "-c", code], senv, mdir, deadline, log, "discover") |
| if rc != 0: |
| return None |
| name = out.strip().splitlines()[-1].strip() if out.strip() else "" |
| return name or None |
|
|
|
|
| def _find_replay() -> Optional[str]: |
| for c in (os.environ.get("CADO_REPLAY", ""), |
| "/opt/cado-nfs/build/release/filter/replay"): |
| if c and os.path.isfile(c): |
| return c |
| m = sorted(glob.glob("/opt/cado-nfs/build/*/filter/replay")) |
| return m[-1] if m else None |
|
|
|
|
| def _factors_from_msieve(out: str, n: int) -> Optional[Tuple[int, int]]: |
| """Parse 'pNN factor: D' lines; return the prime pair whose product is N.""" |
| facs = [int(x) for x in re.findall(r"factor:\s*(\d+)", out)] |
| primes = [f for f in facs if 1 < f < n and gmpy2.is_prime(mpz(f))] |
| for i in range(len(primes)): |
| for j in range(i + 1, len(primes)): |
| a, b = primes[i], primes[j] |
| if a * b == n: |
| return (a, b) if a <= b else (b, a) |
| return None |
|
|