Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9d07621
SIMD bugfixes, NEON-specific and general
theoheimel Oct 7, 2026
6f50fb9
more SIMD bugfixes, including the one proposed in #209
theoheimel Oct 7, 2026
12b37ac
make madspace simd mode selection consistent with madmatrix
theoheimel Oct 7, 2026
a36ef82
add avx512y mode, similar to madmatrix
theoheimel Oct 7, 2026
b21a448
fix avx512y build with GCC: enable AVX2 and FMA explicitly
theoheimel Oct 7, 2026
f95a83c
test madspace and mg7 runs for every supported SIMD mode
theoheimel Oct 7, 2026
9215112
test gridpacks trained in scalar mode across SIMD modes
theoheimel Oct 7, 2026
7095b41
fix NaN in cut-bounded t-channel inverse, test inverse with a pt cut
theoheimel Oct 7, 2026
0874881
madspace: fix cut_all/cut_any bound rank, contiguous_dims prefix, bro…
theoheimel Oct 7, 2026
c5c120b
madspace: fix sub(0, x) folding, fold stacks of scalar literals
theoheimel Oct 7, 2026
dadfe3a
madspace: avoid constant gathers and full(1) products with a single f…
theoheimel Oct 7, 2026
bef49aa
madspace: contiguous SIMD loads/stores, single inputs as a separate k…
theoheimel Oct 7, 2026
a5f4b5d
madspace: store literals padded to the SIMD vector size
theoheimel Oct 7, 2026
734a34d
madspace: write unused gradients of literal and integer inputs to scr…
theoheimel Oct 7, 2026
1f89dcd
madspace: make external CPU runtime arguments batch-contiguous
theoheimel Oct 7, 2026
f7113de
madspace: test layout independence, single inputs, folding and broadc…
theoheimel Oct 7, 2026
9a08ca0
madspace: fix use-after-free of matrix element inputs with the dummy …
theoheimel Oct 7, 2026
7344421
Merge branch 'main' into fix-madspace-simd
theoheimel Oct 12, 2026
4686a48
Merge branch 'feat-madspace-contig-simd' into fix-madspace-simd
theoheimel Oct 12, 2026
974d3a5
madspace: make the event field type an enum
theoheimel Oct 13, 2026
326a010
madspace: fix reading and appending event files
theoheimel Oct 13, 2026
3f4efe4
madspace: make the max-weight freeze independent of thread timing
theoheimel Oct 13, 2026
a14b800
Merge remote-tracking branch 'origin/main' into fix-madspace-simd
theoheimel Oct 17, 2026
a296a59
madspace: default to auto SIMD mode, scalar on ARM and simd_512 > sim…
theoheimel Oct 17, 2026
614e403
madspace: auto SIMD mode as C++ default, refuse late mode switches, u…
theoheimel Oct 17, 2026
949abb4
madspace tests: SIMD layout tests cpu_only, gradient tests on the tes…
theoheimel Oct 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/actions/build_madspace/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ runs:
- name: madspace cache key
run: |
echo "MS_PY=$(python3 -c 'import sys;print(f"{sys.version_info.major}{sys.version_info.minor}")')" >> $GITHUB_ENV
echo "MS_VARIANT=${{ inputs.enable_docs == 'true' && '-docs' || '' }}" >> $GITHUB_ENV
# -simd: the install includes the SIMD CPU backends; a separate suffix so
# that a cached install from before SIMD was enabled is never restored
echo "MS_VARIANT=${{ inputs.enable_docs == 'true' && '-docs' || '' }}-simd" >> $GITHUB_ENV
shell: bash

# Cache the installed madspace/install prefix directly (not just a wheel), so
Expand Down Expand Up @@ -68,6 +70,7 @@ runs:
python3 -m pip install --upgrade pip packaging
python3 -m pip install ./madspace \
-Ccmake.define.ENABLE_OPENBLAS=ON \
-Ccmake.define.ENABLE_SIMD=ON \
${{ inputs.enable_docs == 'true' && '-Ccmake.define.ENABLE_DOCS=ON' || '' }} \
--target="$GITHUB_WORKSPACE/madspace/install"
shell: bash
4 changes: 3 additions & 1 deletion .github/actions/install_madspace/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ runs:
- name: madspace cache key
run: |
echo "MS_PY=$(python3 -c 'import sys;print(f"{sys.version_info.major}{sys.version_info.minor}")')" >> $GITHUB_ENV
echo "MS_VARIANT=${{ inputs.enable_docs == 'true' && '-docs' || '' }}" >> $GITHUB_ENV
# -simd: the install includes the SIMD CPU backends; a separate suffix so
# that a cached install from before SIMD was enabled is never restored
echo "MS_VARIANT=${{ inputs.enable_docs == 'true' && '-docs' || '' }}-simd" >> $GITHUB_ENV
shell: bash

# madspace/install is the local prefix the mg7 runtime expects (see
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/acceptancetest_mg7.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ jobs:
# acceptancetest_mg7_vegas_reproducibility
# acceptancetest_mg7_gridpack_reproducibility
# acceptancetest_mg7_gridpack_reproducibility_vegas
# acceptancetest_mg7_simd_consistency
# Each keeps its own steps, run even when an earlier one failed.
needs: build_madspace
runs-on: ubuntu-24.04
Expand Down Expand Up @@ -314,6 +315,20 @@ jobs:
export PATH="$HOME/.cache/HEPtools/bin:$PATH"
./tests/test_manager.py test_gridpack_reproducibility_vegas_mg7 -pA -t0 -l INFO

# ---- acceptancetest_mg7_simd_consistency ----
# mg7 seeded generation (g g > t t~ g), repeated for every madspace SIMD
# mode (madspace_cpu_mode) supported by the runner: the VEGAS cross
# sections must agree with the scalar one up to rounding, the madnis ones
# within their statistical uncertainties, and the runs of a gridpack trained
# in scalar mode again up to rounding. Self-skips if the runner supports
# no SIMD mode or the madspace + LHAPDF(NNPDF23) stack is unavailable.
- name: "acceptancetest_mg7_simd_consistency"
if: ${{ !cancelled() }}
run: |
cd $GITHUB_WORKSPACE
export PATH="$HOME/.cache/HEPtools/bin:$PATH"
./tests/test_manager.py test_vegas_simd_consistency_mg7 test_madnis_simd_consistency_mg7 test_madnis_gridpack_simd_consistency_mg7 -pA -t0 -l INFO


acceptancetest_mg7_bundle_3:
# former jobs, run one after the other: acceptancetest_mg7_e_p_collision
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/madspace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
MACOSX_DEPLOYMENT_TARGET: 14.5
CIBW_BEFORE_ALL: brew install doxygen
CIBW_ENVIRONMENT: >
SKBUILD_CMAKE_ARGS="-DENABLE_DOCS=ON"
SKBUILD_CMAKE_ARGS="-DENABLE_SIMD=ON;-DENABLE_DOCS=ON"
CIBW_REPAIR_WHEEL_COMMAND: >
delocate-wheel
--ignore-missing-dependencies
Expand All @@ -89,7 +89,9 @@ jobs:
torch
# the GPU runs of these tests are in gpu_runner_ci.yml (job madspace_tests)
CIBW_TEST_ENVIRONMENT: MADSPACE_TEST_DEVICE=cpu
CIBW_TEST_COMMAND: pytest {project}/madspace/tests
# runs the unit tests once per SIMD mode supported by the runner
CIBW_TEST_COMMAND: >
python {project}/madspace/tests/run_simd_modes.py {project}/madspace/tests

- uses: actions/upload-artifact@v4
with:
Expand Down
3 changes: 3 additions & 0 deletions docs/source/gridpacks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ gridpack is created.
* -
- ``device``
- ``--device`` (several entries are allowed)
* -
- ``madspace_cpu_mode``
- ``--madspace_cpu_mode``
* -
- ``cpu_thread_pool_size``, ``gpu_thread_pool_size``
- ``--cpu_thread_pool_size``, ``--gpu_thread_pool_size``
Expand Down
3 changes: 2 additions & 1 deletion docs/source/madspace/cpp-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,9 @@ The high-level driver plus the tensor and device layer it runs on: integration,
cpp/format_with_error
cpp/hip_device
cpp/select_combine_channel_index
cpp/set_simd_mode
cpp/supported_simd_modes
cpp/set_lib_path
cpp/set_simd_vector_size

Utilities
---------
Expand Down
3 changes: 2 additions & 1 deletion docs/source/madspace/python-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,9 @@ The high-level driver plus the tensor and device layer it runs on: integration,
python/format_with_error
python/hip_device
python/select_combine_channel_index
python/set_simd_mode
python/supported_simd_modes
python/set_lib_path
python/set_simd_vector_size

Utilities
---------
Expand Down
9 changes: 6 additions & 3 deletions docs/source/run_card.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,18 @@ fixed parameters. Their entries are described below.
``simd_256``, ``simd_512`` and ``avx512y``. ``auto`` picks the widest width the host
supports.

``madspace_cpu_mode`` (``"auto"``)
SIMD width of the phase-space sampling on ``cpu`` devices. Choose from ``auto``,
``scalar``, ``simd_128`` (ARM only), ``simd_256``, ``avx512y`` and ``simd_512``.
``auto`` picks ``simd_512``, then ``simd_256`` on x86. On ARM it picks ``scalar``,
because the vectorized mode is currently slower there.

``precision`` (``"color32"``)
Floating-point precision of the matrix elements. ``all64`` and ``all32`` use double and
single precision throughout. ``color32`` evaluates the color algebra in single precision
and everything else in double precision. ``denom64`` keeps the momenta and propagator
denominators in double precision and uses single precision elsewhere.

``simd_vector_size`` (``-1``)
Vector size of the CPU matrix elements. ``-1`` chooses automatically. Valid sizes are 1,
4 and 8 on x86 and 1 and 2 on Apple silicon.

``cpu_thread_pool_size`` (``-1``), ``gpu_thread_pool_size`` (``1``), ``combine_thread_pool_size`` (``-1``)
Number of threads for each ``cpu`` device, each GPU device and for combining the
Expand Down
6 changes: 4 additions & 2 deletions madgraph/interface/common_run_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -1241,7 +1241,8 @@ def detect_card_type(path):
'@MG5aMC', # MA5 hadronique
'run_rivet_later', # Rivet
'change particle_in_density_matrix', # density mode of reweight
'simd_vector_size', # mg7 run_card.toml
'madspace_cpu_mode', # mg7 run_card.toml
'simd_vector_size', # mg7 run_card.toml (before madspace_cpu_mode)
'include_madspace', # mg7 run_card.toml
]

Expand Down Expand Up @@ -1277,7 +1278,8 @@ def detect_card_type(path):
# MadAnalysis4 is gone, but banners and process directories written
# by older versions still carry this card: recognise it, ignore it.
return 'plot_card.dat'
elif 'simd_vector_size' in text or 'include_madspace' in text:
elif 'madspace_cpu_mode' in text or 'simd_vector_size' in text or \
'include_madspace' in text:
# mg7 run_card is a TOML file (madspace/MadNIS integration engine)
return 'run_card.toml'
elif ('gridpack' in text and 'ebeam1' in text) or \
Expand Down
2 changes: 1 addition & 1 deletion madgraph/interface/tutorials/mg7.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
`Cards/run_card.toml`, section by section. The ones you will actually touch
are marked.

[run] seed, device (cpu/gpu), cpu_mode, simd_vector_size,
[run] seed, device (cpu/gpu), cpu_mode, madspace_cpu_mode,
the thread pools, output_format, verbosity <-
[beam] beam energies, PDF
[generation] events, the survey iterations and target precision,
Expand Down
9 changes: 9 additions & 0 deletions madgraph/iolibs/template_files/mg7/gridpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ def main() -> None:
"generate the gridpack (can lead to errors or incorrect results)"
)
parser.add_argument("--device", type=str, nargs="*")
parser.add_argument(
"--madspace_cpu_mode",
type=str,
default=run_args.get("madspace_cpu_mode", "auto"),
choices=["auto", "scalar", "simd_128", "simd_256", "avx512y", "simd_512"],
help="SIMD width of the phase-space sampling (madspace) on the 'cpu' devices",
)
parser.add_argument(
"--cpu_thread_pool_size", type=int, default=run_args["cpu_thread_pool_size"]
)
Expand Down Expand Up @@ -172,6 +179,8 @@ def main() -> None:
print(f"\033[1m\033[31mWARNING\033[39m: {message}\033[0m")
print()
seed = resolve_seed(args.seed)
# has to be set before the madspace CPU backend is first loaded
ms.set_simd_mode(args.madspace_cpu_mode)

# initialize output directories; command line paths are relative to the
# invocation directory, run card paths to the gridpack
Expand Down
2 changes: 1 addition & 1 deletion madgraph/iolibs/template_files/mg7/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ def drop_closed_channels(self) -> None:
self.subprocess_data = kept

def init_backend(self) -> None:
ms.set_simd_vector_size(self.run_card["run"]["simd_vector_size"])
ms.set_simd_mode(self.run_card["run"]["madspace_cpu_mode"])

def init_event_dir(self) -> None:
output_dir = self.run_card["gridpack"]["output_dir"]
Expand Down
6 changes: 3 additions & 3 deletions madgraph/iolibs/template_files/mg7/run_card.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ run_name = %(run.run_name)s
seed = %(run.seed)s
# options: cpu, cuda, hip, each optionally with a device index, e.g. "cuda:1"
device = %(run.device)s
# SIMD width on cpu devices; options: auto, scalar, simd_128, simd_256, simd_512, avx512y
# SIMD width of the matrix elements on cpu devices; options: auto, scalar, simd_128, simd_256, simd_512, avx512y
cpu_mode = %(run.cpu_mode)s
# SIMD width of the phase-space sampling on cpu devices; options: auto, scalar, simd_128 (ARM only), simd_256, avx512y, simd_512
madspace_cpu_mode = %(run.madspace_cpu_mode)s
# matrix element precision; options: all64, all32, color32, denom64
precision = %(run.precision)s
# -1 chooses automatically; x86: 1, 4, 8; Apple silicon: 1, 2
simd_vector_size = %(run.simd_vector_size)s
# -1 sets the thread count automatically
cpu_thread_pool_size = %(run.cpu_thread_pool_size)s
gpu_thread_pool_size = %(run.gpu_thread_pool_size)s
Expand Down
22 changes: 13 additions & 9 deletions madgraph/various/banner.py
Original file line number Diff line number Diff line change
Expand Up @@ -6713,12 +6713,13 @@ def default_setup(self):
comment="list of devices, each cpu, cuda or hip with an optional index (e.g. \"cuda:1\")")
self.add_toml_param('run', 'cpu_mode', "auto", gridpack=True,
allowed=['auto', 'scalar', 'simd_128', 'simd_256', 'simd_512', 'avx512y'],
comment="SIMD width on cpu devices; auto picks the widest one the host supports")
comment="SIMD width of the matrix elements on cpu devices; auto picks the widest one the host supports")
self.add_toml_param('run', 'madspace_cpu_mode', "auto", gridpack=True,
allowed=['auto', 'scalar', 'simd_128', 'simd_256', 'avx512y', 'simd_512'],
comment="SIMD width of the phase-space sampling on cpu devices; simd_128 is ARM only")
self.add_toml_param('run', 'precision', "color32", gridpack=True,
allowed=['all64', 'all32', 'color32', 'denom64'],
comment="matrix element precision: all64, all32, color32 (FP32 color sum) or denom64 (FP64 momenta and propagator denominators)")
self.add_toml_param('run', 'simd_vector_size', -1,
comment="-1 chooses automatically; x86: 1, 4, 8; Apple silicon: 1, 2")
self.add_toml_param('run', 'cpu_thread_pool_size', -1, gridpack=True,
comment="threads for the cpu devices; -1 chooses automatically")
self.add_toml_param('run', 'gpu_thread_pool_size', 1, gridpack=True,
Expand Down Expand Up @@ -7044,7 +7045,7 @@ def _derived_beam_value(self, lname):
get = __getitem__

def __setitem__(self, name, value, *args, **opts):
"""Refuse an unsupported cpu_mode instead of silently falling back.
"""Refuse an unsupported (madspace_)cpu_mode instead of silently falling back.

The generic ConfigFile machinery reacts to a value outside an 'allowed'
list by logging a warning and keeping the previous value. For cpu_mode
Expand All @@ -7059,14 +7060,17 @@ def __setitem__(self, name, value, *args, **opts):
tool reading such a sample (MadSpin, systematics, ...) abort on a
parameter it never uses.
"""
if isinstance(name, str) and name.strip().lower() in ('cpu_mode', 'run.cpu_mode'):
allowed = self.allowed_value.get('run.cpu_mode', [])
key = name.strip().lower() if isinstance(name, str) else None
if key is not None and key.startswith('run.'):
key = key[4:]
if key in ('cpu_mode', 'madspace_cpu_mode'):
allowed = self.allowed_value.get('run.' + key, [])
if allowed and str(value).strip().lower() not in [str(v).lower() for v in allowed]:
message = ("Invalid cpu_mode='%s': supported values are [ '%s' ]"
% (str(value).strip(), "', '".join(str(v) for v in allowed)))
message = ("Invalid %s='%s': supported values are [ '%s' ]"
% (key, str(value).strip(), "', '".join(str(v) for v in allowed)))
if getattr(self, '_from_banner', False):
logger.warning("%s; the run_card of the event file is read with "
"cpu_mode='%s'", message, self['run']['cpu_mode'])
"%s='%s'", message, key, self['run'][key])
return
raise InvalidRunCard(message)
return super(RunCardMG7, self).__setitem__(name, value, *args, **opts)
Expand Down
26 changes: 21 additions & 5 deletions madspace/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ set(
${MADSPACE_GENERATED_DIR}/python/instruction_set.hpp
${MADSPACE_GENERATED_DIR}/cpu/runtime_mixin.inc
${MADSPACE_GENERATED_DIR}/cpu/runtime_backward_mixin.inc
${MADSPACE_GENERATED_DIR}/cpu/kernel_checks.inc
${MADSPACE_GENERATED_DIR}/gpu/runtime_mixin.inc
${MADSPACE_GENERATED_DIR}/gpu/runtime_backward_mixin.inc
)
Expand Down Expand Up @@ -438,14 +439,26 @@ if(NOT ENABLE_SIMD)
set(CPU_TARGETS madspace_cpu)
set(SIMD_FLAGS "")
set(SIMD_DEFINES "")
elseif(APPLE)
set(CPU_TARGETS madspace_cpu madspace_cpu_neon)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|aarch64|ARM64)$")
set(CPU_TARGETS madspace_cpu madspace_cpu_simd_128)
set(SIMD_FLAGS "" "")
set(SIMD_DEFINES "" "USE_SIMD_NEON=1")
else()
set(CPU_TARGETS madspace_cpu madspace_cpu_avx2 madspace_cpu_avx512)
set(SIMD_FLAGS "" "-mavx2 -mfma" "-mavx512f")
set(SIMD_DEFINES "" "USE_SIMD_AVX2=1" "USE_SIMD_AVX512=1")
set(
CPU_TARGETS
madspace_cpu madspace_cpu_simd_256 madspace_cpu_avx512y madspace_cpu_simd_512
)
set(
SIMD_FLAGS
""
"-mavx2 -mfma"
"-mavx2 -mfma -mavx512f -mavx512vl -mavx512dq -mprefer-vector-width=256"
"-mavx512f"
)
set(
SIMD_DEFINES
"" "USE_SIMD_AVX2=1" "USE_SIMD_AVX512Y=1" "USE_SIMD_AVX512=1"
)
endif()

foreach(
Expand All @@ -462,10 +475,13 @@ foreach(
src/cpu/kernel_definitions.hpp
${MADSPACE_GENERATED_DIR}/cpu/runtime_mixin.inc
${MADSPACE_GENERATED_DIR}/cpu/runtime_backward_mixin.inc
${MADSPACE_GENERATED_DIR}/cpu/kernel_checks.inc
src/cpu/tensor.hpp
src/cpu/simd.hpp
src/cpu/simd_arm.hpp
src/cpu/simd_x86_256.hpp
src/cpu/simd_x86_256y.hpp
src/cpu/simd_x86_512.hpp
${KERNEL_SRC}
)
target_include_directories(${cpu_target} PUBLIC include)
Expand Down
58 changes: 57 additions & 1 deletion madspace/generate_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def main():
runtime_backward_mixin(commands, "cpu", out_dir)
runtime_mixin(commands, "gpu", out_dir)
runtime_backward_mixin(commands, "gpu", out_dir)
kernel_checks(commands, out_dir)


def write_autogen(f):
Expand Down Expand Up @@ -225,7 +226,7 @@ def instruction_set_mixin(commands, out_dir):
first = True
for name, cmd in commands.items():
opcode = cmd["opcode"]
differentiable = "true" if cmd.get("differentiable", False) else "true"
differentiable = "true" if cmd.get("differentiable", False) else "false"
if "class" in cmd:
f.write(
f" InstructionOwner(new {cmd['class']}("
Expand Down Expand Up @@ -352,5 +353,60 @@ def runtime_backward_mixin(commands, device, out_dir):
)


def is_single_input(arg):
return isinstance(arg.get("type"), list) and len(arg["type"]) > 1 and (
arg["type"][1] == "single"
)


def kernel_checks(commands, out_dir):
"""Compile-time checks that the parameters of the vectorized kernels use the
single types (FSIn, ISIn) exactly for the inputs marked as single in the yaml.
A mismatch would make the CPU runtime fall back to the scalar kernels."""
with open(out_dir / "cpu" / "kernel_checks.inc", "w") as f:
write_autogen(f)

def check(kernel, index, single, what):
f.write(
f"static_assert(\n"
f" single_params<decltype(&{kernel}<SimdTypes>)>::value[{index}] == "
f"{'true' if single else 'false'},\n"
f' "{kernel}: parameter {index} ({what}) must '
f'{"" if single else "not "}be single"\n'
f");\n"
)

for name, cmd in commands.items():
if cmd.get("custom_op", False) or not cmd.get("vectorized", True):
continue
inputs = cmd["inputs"]
if is_single_input(inputs[0]):
raise ValueError(
f"{name}: the first input of a kernel can not be single"
)
for i, arg in enumerate(inputs):
check(f"kernel_{name}", i, is_single_input(arg), arg["name"])

if not cmd.get("differentiable", False):
continue
in_stored = [
i for i, arg in enumerate(inputs) if arg.get("backward_arg", False)
]
out_stored = [
i
for i, arg in enumerate(cmd["outputs"])
if arg.get("backward_arg", False)
]
if len(in_stored) + len(out_stored) == 0:
in_stored = list(range(len(inputs)))
for k, i in enumerate(in_stored):
check(
f"backward_kernel_{name}",
k,
is_single_input(inputs[i]),
inputs[i]["name"],
)


if __name__ == "__main__":
main()
Loading
Loading