Skip to content

Restore the OpenCL batch-factoring build (WITH_OPENCL), fix the OpenCL host path, add kernel tests - #100

Merged
bbuhrow merged 5 commits into
bbuhrow:masterfrom
teruteru128:feature/opencl-standalone
Oct 15, 2026
Merged

bbuhrow merged 5 commits into
bbuhrow:masterfrom
teruteru128:feature/opencl-standalone

Conversation

@teruteru128

@teruteru128 teruteru128 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #99 — thanks for the quick reply and for being open to this.

Summary

The OpenCL cofactorisation backend from 5607dd3 had lost its Makefile wiring in abced9f, and its host code had never been run from yafu (it had drifted from the CUDA code). This PR makes it build without CUDA via a new WITH_OPENCL=1, fixes what broke once it actually ran, and adds tests. Five commits, each self-contained:

  1. build: make OpenCL batch factoring buildable without CUDA (WITH_OPENCL) — restores -DHAVE_OCL_BATCH_FACTOR and the two sources; BATCH_CUDA=1 with no CUDA toolkit falls back to OpenCL again; WITH_OPENCL is mutually exclusive with BATCH_CUDA/CUDA_POLY (same gpu_*/device_* symbols). Also fixes CL_DEVICE_PREFERRED_WORK_GROUP_SIZE_MULTIPLE (OpenCL 3.0 only; missing from ROCm's 2.2 headers) and the ocl_xface.c guard, and stops gpu_cofactorization_cl.c from defining HAVE_LASIEVE_MPQS itself (mpqs_factor is not linked into yafu; it now uses tinysiqs like the CUDA file) and from using the lasieve mpz_*_ull helpers.
  2. opencl: keep uprac/uprac96 out of line — they are called ~100 times each in straight-line code and were static inline; AMD's OpenCL compiler inlines everything, which made one kernel ~278k lines of IR and the first clBuildProgram never finished in my test. noinline brings the IR to ~98k lines for the whole program and the build finishes quickly (later runs come from the driver cache).
  3. test: add OpenCL kernel tests (test/layer2/test_ocl_ecm.c) — checks gbl_ecm, gbl_ecm96, gbl_pm196 against known-structure inputs with GMP. Only built with WITH_OPENCL=1; passes with a note when no OpenCL GPU is present.
  4. opencl: fix host-side batch setup — gpu_cofactorization() and do_gpu_cofactorization() lacked the single-side cases / had the r/a codes swapped (CUDA logic taken over); residues were packed with lp_*_num_words words but read with a fixed stride, so a side mixing 2- and 3-word cofactors fed zero moduli to the 96-bit kernels (SIGFPE); zero-sized buffer writes are CL_INVALID_VALUE in OpenCL.
  5. opencl: allow only one GPU thread context at a time — with several NFS/SIQS batch threads creating and running contexts at once, ROCm faulted (instruction-fetch page fault at address 0 followed by a full amdgpu reset). gpu_ctx_init now takes a process-wide lock released by gpu_ctx_free. I haven't established the exact failing mechanism inside ROCm; this removes the concurrency that triggered it.

What is not changed

  • No CUDA source is touched, and default builds are unchanged: make -n for yafu, msieve, siqs, ecm and test produces exactly the same command lists as upstream/master (so the existing CI, which builds make yafu without GPU flags, is unaffected).
  • build.vc22 is untouched (the projects already list the OpenCL files).
  • Gerbicz/HAVE_OCL_POLY Makefile wiring is left for later, as discussed.

Testing

Ubuntu, GCC 15, ROCm 10.1 OpenCL runtime, Radeon 780M (gfx1103), on the head of this branch:

  • make WITH_OPENCL=1 test && ./yafu_test ocl_ecm: passes (3 kernels); the full ./yafu_test run with WITH_OPENCL=1 (40 tests) also passes. make WITH_OPENCL=1 yafu builds.
  • SIQS: siqs(N) -forceTLP on a 75- and an 85-digit semiprime goes through the OpenCL cofactorisation and returns the correct factors.
  • NFS: an 85-digit semiprime with a-side 3LP (-nfs_batch_3lp, 8 threads, a custom nfs_params table chosen only to exercise the GPU path) ran to completion through ~100 GPU batches with the correct factors and no GPU fault logged.

Not tested: NVIDIA/CUDA hardware, Windows (MinGW/MSVC), natural-size TLP/NFS jobs. I have no reliable performance numbers (the machine was under other load), so this PR makes no speed claims.

Notes for review

  • The CUDA host code packs residues the same way and also creates one context per thread, so it may share the mixed-word problem; I can't test CUDA. Happy to send a separate patch if you'd like.
  • A HIP port of the ECM device code exists on a separate branch (it builds with hipcc for gfx1103 and gives bit-identical results to the OpenCL kernels on the same inputs; CUDA preprocesses to exactly the same text). I'll open it as its own PR later.

Refs #99.

Disclosure: parts of this work were developed with an AI coding assistant (Claude Code; see the Co-Authored-By trailers). Everything described above was built and run on my machine.

🤖 Generated with Claude Code

teruteru128 and others added 5 commits October 6, 2026 19:25
The OpenCL cofactorisation backend added in 5607dd3 lost its Makefile
wiring (-DHAVE_OCL_BATCH_FACTOR and the two source files) when
Makefile.new became Makefile in abced9f, and section 4d only probed for
OpenCL when BATCH_CUDA was set.

- Add WITH_OPENCL=1: OpenCL backend without a CUDA toolkit.
- BATCH_CUDA=1 with no CUDA toolkit falls back to OpenCL again.
- WITH_OPENCL is mutually exclusive with BATCH_CUDA / CUDA_POLY (both
  backends define the same gpu_* / device_* symbols).
- ocl_xface.c: use the same guard as ocl_xface.h; define
  CL_DEVICE_PREFERRED_WORK_GROUP_SIZE_MULTIPLE when the SDK headers
  predate OpenCL 3.0 and fall back to the AMD wavefront query.
- gpu_cofactorization_cl.c: stop defining HAVE_LASIEVE_MPQS itself
  (mpqs_factor is not linked into yafu) and use mpz_get_64/mpz_set_64
  instead of the lasieve mpz_*_ull helpers.

Builds without the new flags are unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
uecm_stage1 and its 96-bit counterpart call uprac()/uprac96() about 100
times each in straight-line code.  They were translated from CUDA's
plain __device__ functions to "static inline", and AMD's OpenCL compiler
inlines every call, so gbl_ecm alone grew from ~3k lines of IR to ~278k
and the AMDGPU backend needed more than 27 minutes (never finished in our
test) to build opencl_tinyecm.cl on a Radeon 780M with ROCm 10.1.

Marking the two functions noinline brings the IR down to ~98k lines and
the full clBuildProgram time to about 42 seconds on the same machine.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Runs gbl_ecm, gbl_ecm96 and gbl_pm196 from factor/opencl_tinyecm.cl on a
GPU, built exactly as gpu_cofactorization_cl.c builds them, and checks
every reported factor with GMP.  Inputs are balanced 61/62-bit semiprimes
(gbl_ecm), 82..87-bit semiprimes with a 30..34-bit factor (gbl_ecm96) and
96-bit composites whose p-1 is built from distinct primes < 500
(gbl_pm196).  A wrong (non-dividing) factor is a hard failure; the miss
rate must stay under 2%, like the CPU ECM tests.

The module is only compiled with WITH_OPENCL=1 (make WITH_OPENCL=1 test)
and passes with a note when no OpenCL GPU is present.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
gpu_cofactorization_cl.c had never been executed by yafu since its port
from the CUDA code, and diverged from it in three ways that show up as soon
as SIQS with TLP (-forceTLP) uses it:

- gpu_cofactorization() lacked the single-side cases (first_side -2..-5,
  do_2lp/do_3lp/do_pm1); take the CUDA logic.
- do_gpu_cofactorization() swapped the r-side/a-side codes for "only one
  side has unfactored residues" and did not set lpb_2lp/lpb_3lp; take the
  CUDA logic.
- Residues were packed with exactly lp_*_num_words words per relation but
  read back with a fixed stride of 2 or 3, so a side that mixes 2- and
  3-word cofactors shifted everything after the first short one and the
  96-bit kernels were fed zero moduli (SIGFPE in mpz_tdiv_r).  Pack with a
  fixed per-side stride and zero-pad.
- do_gpu_ecm64/ecm_96b/pm1_96b enqueued zero-sized buffer writes when
  handed an empty list; OpenCL rejects those with CL_INVALID_VALUE
  (CUDA's memcpy does not), so return early.

With these, siqs() of a 75-digit semiprime with -forceTLP runs through the
OpenCL cofactorisation on a Radeon 780M and returns the same factors as the
CPU-only build.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
NFS and SIQS give every batch thread its own context.  On ROCm, with
several threads creating, running and releasing contexts on the same GPU
at once, yafu died with "Memory access fault by GPU ... address (nil)":
the kernel log showed an instruction-fetch page fault at address 0
followed by a full GPU reset (which also disturbs the display on an
iGPU).  Single batches, and batches that ran one after the other, were
fine.

gpu_ctx_init now takes a process-wide lock and gpu_ctx_free releases it,
so GPU batches run one at a time; the CPU-side work of the other threads
is unaffected.  The exact failing mechanism inside ROCm is not
established; this removes the concurrency that triggered it.

With this, an 85-digit GNFS job using a-side 3LP (-nfs_batch_3lp, 8
threads, 34 96-bit and 68 64-bit GPU batches) runs to completion on a
Radeon 780M and returns the correct factors, with no GPU fault logged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@teruteru128
teruteru128 force-pushed the feature/opencl-standalone branch from ca4a668 to b9d2861 Compare October 9, 2026 08:01
@bbuhrow
bbuhrow merged commit c7d9028 into bbuhrow:master Oct 15, 2026
12 checks passed
@teruteru128

Copy link
Copy Markdown
Contributor Author

Thank you for merge!

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