Skip to content

Build all SubProcesses in parallel from a single make jobserver - #67

Merged
oliviermattelaer merged 3 commits into
mainfrom
claude/subprocesses-parallelization-f86837
Aug 18, 2026
Merged

oliviermattelaer merged 3 commits into
mainfrom
claude/subprocesses-parallelization-f86837

Conversation

@oliviermattelaer

Copy link
Copy Markdown
Contributor

Running make -j 18 in SubProcesses/ now spreads 18 concurrent compilations over all the P* directories at once — instead of building one directory at a time with 18 jobs, or giving each directory 18/N.

This is the make-level counterpart of #61, which did the same thing with a ThreadPoolExecutor in mg7/madevent.py.

Mechanism

The parallelism comes from GNU make's jobserver. SubProcesses/makefile becomes a plain recursive dispatcher that fans out with +$(MAKE) -C <dir>, so its -j slots are shared with every sub-make: a directory that runs out of work immediately hands its slots to the others.

File layout

SubProcesses/makefile used to be the per-process build rules (symlinked into each P*). That name is now the dispatcher, so the files are rearranged:

file role
SubProcesses/makefile the dispatcher (new template madmatrix_subprocesses.mk)
SubProcesses/madmatrix.mk the build rules, linked as each P*/makefile
SubProcesses/madmatrix_standalone.mk ditto for standalone_mg7 (it includes madmatrix.mk)

The dispatcher owns no build logic of its own. It even builds the common library through a representative P* directory, so the BACKEND/cppauto resolution, the compiler flags and the src/, lib/ paths stay defined in madmatrix.mk only.

The race that had to be fixed first

Every P* makefile recursed into src/ to build the shared common library. With N directories running at once they would all have done that simultaneously, on the same objects. The dispatcher builds it once, up front, and passes MADMATRIX_COMMONLIB_EXTERNAL=1 to say it owns that step; a P* directory built on its own is unaffected.

The bldall backend list is collapsed into a single BLDBACKENDS variable, so multi-backend builds can be driven from the dispatcher without duplicating the platform logic.

Python side

mg7 compiled one subprocess per MadgraphSubprocess. It now resolves cppauto once and makes a single make -jN call on SubProcesses/ (N = cpu_thread_pool_size, -1 meaning the CPU count). One make invocation replaces the per-subprocess loop.

Incidental

find_output_type told standalone_mg7 apart by the presence of SubProcesses/madmatrix.mk, which the regular mg7 export now also has; the discriminator moves to madmatrix_standalone.mk.

Testing

p p > e+ e- + p p > e+ e- j (4 subprocesses), both mg7 and standalone_mg7:

  • scaling on an 18-core machine: -j1 12.3s → -j4 4.0s → -j18 2.1s; the common library is compiled exactly once
  • clean, cleanall, bldall (2 backends, one common lib each), make P2_gQ_epemQ, BACKEND=… USEBUILDDIR=1, plain make, incremental no-op, and a plain make inside a single P* directory
  • a deliberate compile error reports make: *** [build@P2_gQ_epemQ] Error 2, so the failing directory is still named
  • check_sa.exe runs; full bin/generate_events completes in 5.2s including the build
  • acceptance tests test_standalone_mg7_goodhel_filter, test_standalone_mg7_vs_cpp, test_output_mg7_directory pass (test_group_subprocess_mg7 skips)

Note: the systematics computation failed message at the end of generate_events is pre-existing — reproduced on a stashed baseline.

🤖 Generated with Claude Code

'make -j 18' in SubProcesses now spreads 18 concurrent compilations over ALL
the P* directories at once, instead of building one directory at a time with
18 jobs (or giving each directory 18/N).

The parallelism comes from GNU make's jobserver: SubProcesses/makefile becomes
a plain recursive dispatcher that fans out with '+$(MAKE) -C <dir>', so its -j
slots are shared with every sub-make and a directory that runs out of work
immediately hands its slots to the others.

That name used to hold the per-process build rules themselves (symlinked into
each P*), so the files are rearranged:

  SubProcesses/makefile              the dispatcher (new madmatrix_subprocesses.mk)
  SubProcesses/madmatrix.mk          the build rules, linked as P*/makefile
  SubProcesses/madmatrix_standalone.mk  ditto for standalone_mg7

The dispatcher owns no build logic: it even builds the common library *through*
a representative P* directory, so the BACKEND/'cppauto' resolution, the compiler
flags and the src/ and lib/ paths stay defined in madmatrix.mk only.

Building the common library once, up front, is also what makes the fan-out safe:
every P* makefile used to recurse into src/ on its own, which N parallel
sub-makes would all have done at the same time on the same objects. The
dispatcher now passes MADMATRIX_COMMONLIB_EXTERNAL=1 to say it owns that step;
a P* directory built on its own is unaffected. The bldall backend list is
collapsed into a single BLDBACKENDS variable so multi-backend builds can be
driven from the dispatcher without duplicating the platform logic.

On the Python side, mg7 compiled one subprocess per MadgraphSubprocess; it now
resolves 'cppauto' once and makes a single 'make -jN' call on SubProcesses
(N = cpu_thread_pool_size, -1 meaning the CPU count).

find_output_type told standalone_mg7 apart by the presence of
SubProcesses/madmatrix.mk, which the regular mg7 export now also has; the
discriminator moves to madmatrix_standalone.mk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Qubitol

Qubitol commented Aug 14, 2026

Copy link
Copy Markdown
Member

Nice!

I think the only thing that is missing would be a progress indicator of the status of the compilation, if we are still interested in adding it (see #6), of course.

Qubitol and others added 2 commits August 14, 2026 13:35
The build output was captured by misc.compile and thrown away on success, so
there was nothing to look at while the subprocesses compiled and nothing left
afterwards. Run make directly instead, redirecting the whole build into
Events/<run>/compile_subprocesses.log, and frame it with

  Start compilation of SubProcesses for device 'cppnone' (4 subprocess(es),
  18 parallel job(s)), see log detail in Events/run_01/compile_subprocesses.log
  Compilation of SubProcesses done in 2.0 s

On failure the raised error points at the same log, which now holds the
compiler diagnostics.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@oliviermattelaer

Copy link
Copy Markdown
Contributor Author

Ok I added some log for the compilation of the SubProcesses (with information where more detailled log are kept).

Let's merge?

oliviermattelaer pushed a commit that referenced this pull request Aug 17, 2026
a1afe9d made the mg7 launcher compile subprocesses concurrently with a
ThreadPoolExecutor. That is unsafe: every P* makefile builds the shared common
library by recursing into the shared src/, and nothing serialises them, so N
concurrent subprocess builds means N processes compiling src/build.<backend>/
Parameters.o and read_slha.o to the same paths and linking the same
lib<...>_common.so.

It broke check_xsec_processes (ttx1j) on CI: p p > t t~ j is the only entry in
that section with more than one subprocess, so it is the only one that took the
concurrent path, and it failed with a compilation error in P0_QQx_ttxg. The
same commit passed a re-run minutes later, which is the giveaway.

The race does not reproduce on macOS (0/6 at -j4, 0/10 at -j1 with 18 cores):
src/ has two objects and the window is short. Widening it with a sleep in the
src rules shows it plainly -- four concurrent writers to each of
Parameters.o and read_slha.o.

Two further reasons to drop rather than patch this:

- PR #61 proposed the same ThreadPoolExecutor design and was closed unmerged.
  This commit reintroduced it without knowing.
- PR #67 does it properly, at the make level: SubProcesses/makefile becomes a
  jobserver dispatcher, the common library is built once up front, and P*
  directories are told so with MADMATRIX_COMMONLIB_EXTERNAL=1. Its jobserver
  also shares slots dynamically, where the static build_jobs // len(pending)
  split here gave -j1 per make on a 4-core CI runner -- neutralising the
  parallel-make win (3.4x, the larger half) while keeping the racy half.

Removes compile_subprocesses, build_subprocess, resolve_api_path, build_jobs
and the concurrent.futures import; MadgraphSubprocess's compile loop and
init_subprocesses are byte-identical to main again, so this file no longer
conflicts with #67 (verified with git merge-tree).

Everything else a1afe9d added is unrelated to the build and stays: the
decay-phase timing split, and on this branch the mg7 decay mode, clean_pids,
drop_closed_channels and skipping systematics for decays.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Qubitol

Qubitol commented Aug 18, 2026

Copy link
Copy Markdown
Member

Good for me!

@Qubitol Qubitol added this to the Alpha release milestone Aug 18, 2026
@oliviermattelaer
oliviermattelaer merged commit e8e7d03 into main Aug 18, 2026
488 checks passed
@oliviermattelaer
oliviermattelaer deleted the claude/subprocesses-parallelization-f86837 branch August 18, 2026 08:45
@oliviermattelaer

Copy link
Copy Markdown
Contributor Author

Thanks !!!

oliviermattelaer added a commit that referenced this pull request Aug 19, 2026
main brought 8 commits: the SubProcesses parallel build (#67), the dead CI test
names and the test_manager change that makes a name matching nothing an error
(#70), the aloha TMP eval namespace (#69), a more robust cppauto resolution and
a compile log for the mg7 SubProcesses build.

Four conflicts, all resolved towards main where it restructured something and
towards this branch where it only renamed:

- mg7/launch.py: main moved the per-device resolution out of the loop into
  resolve_cppauto_backend(), with real error handling, and builds the libraries
  up front. Its version is taken whole; the inline quick fix this branch had
  renamed no longer exists.
- aloha.yml: this branch renamed the launcher, main fixed the test selector
  (dropping a testIO_aloha.* that never existed and a missing space that made
  the -e exclusion inert). Both kept.
- acceptancetest.yml: main deletes three jobs naming tests that no longer
  exist; this branch had only renamed the launcher inside them. Deletion kept,
  which matters now that a name matching nothing is an error.
- madgraph_interface.py: main changed the detection file from madmatrix.mk to
  madmatrix_standalone.mk, because after its restructure both exporters write
  madmatrix.mk and only the standalone one writes the other. Its file name with
  this branch's format name, otherwise a plain mg7 output is detected as
  standalone.

main is written in the backend vocabulary this branch renames, and two of those
files merged without a conflict:

- launch.py arrived with seventeen cppauto references (the function and its
  name, the make invocation, the regex, the messages), translated to cpu.
- madmatrix.mk gained BLDAVXS = cppnone cppsse4 ... from the parallel build.
  The bldavxs target happened to survive, since $(subst cpp,,cppnone) still
  gives bldnone, but BLDBACKENDS feeds commonlib.% which runs BACKEND=$*
  directly, so bldcommonlib died with

      Invalid backend BACKEND='cppnone': supported backends are ... 'cpu'

  that is, the dispatcher the parallel build routes through. BLDAVXS now holds
  the new names and the bld* targets are mapped explicitly, because they keep
  their historical short names (bldnone, not bldcpu_scalar) and three tests
  invoke make bldnone directly.

Checked: bldall and bldcommonlib resolve to cpu_scalar and cpu_128b on this
host with no invalid backend, and a full mg7 run goes from devices = ["cpu"]
through "Device 'cpu' resolved as 'cpu_128b'" to events, with main's new
compile_subprocesses.log in place. Unit suite 901 tests with only the two known
pre-existing failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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