Skip to content

Enable cuEquivariance for MACE, with a plain-path fallback that always works - #55

Merged
bowen-bd merged 4 commits into
learningmatter-mit:mainfrom
bowen-bd:feat/mace-cueq
Oct 27, 2026
Merged

bowen-bd merged 4 commits into
learningmatter-mit:mainfrom
bowen-bd:feat/mace-cueq

Conversation

@bowen-bd

Copy link
Copy Markdown
Collaborator

Summary

MACE's library is already current here — mace-torch 0.3.16 is both what venv/mlip pins and the newest release — and no newer universal checkpoint has appeared since MACE-MH-1 (2025-10-30). What had gone unused is the accelerator: MACECalculator accepts enable_cueq, and every one of this repository's seven calculator constructions omitted it, so MACE has been running its plain e3nn path even where cuEquivariance was installed.

This adds cuEquivariance to the mlip CUDA extras and routes calculator construction through one builder that uses it. The constraint that shapes the design is that an optimisation may not cost anybody the ability to run MACE: no GPU, no cuequivariance install, and a card whose kernels are missing all degrade to the plain path rather than raising.

Changes

  • Environment: cuequivariance-torch plus cuequivariance-ops-torch-cu12/cu13 ride in the existing cu126/cu130 extras, because their coupling to torch is exact — the ops wheels are compiled against one CUDA major, and cuequivariance_torch imports whichever is installed under the bare module name cuequivariance_ops_torch. Upstream mace-torch declares only the cu12 pair (cueq-cuda-11/12); the cu13 pair exists and is what a driver ≥ 580 host needs. About 30 MB per architecture, so the images barely move.
  • MaceWrapper gains an accelerator knob (auto / cueq / none) and one _build_calculator that all six accelerator-capable constructions go through. auto is the default.
  • load_model reports what the calculator will attempt, without building it: the calculator is lazy, and building it would download and convert the checkpoint just to answer. The tool result names the accelerator in effect, or the reason there is none.
  • MACE-ANI-CC is excluded from both the request and the report: mace_anicc fixes its own argument list, so it never receives enable_cueq. Announcing a speedup for a path that drops the flag would be the same mistake as reporting a Nitro fallback for a checkpoint that was never eligible for Nitro.

Fallbacks

This is the part worth reviewing closely, because mace itself raises rather than degrading:

Situation Behaviour
CPU host cueq is not attempted; one log line, plain path
cuequivariance not installed (CPU-only env, or an extra that was never synced) same
Card the installed kernels do not cover the accelerated attempt raises, is caught, the reason is logged, the calculator is rebuilt on the plain path, MACE loads
Second structure after a failed attempt the failure is remembered, so a per-structure rebuild does not retry a conversion that cannot work
A genuine error (bad checkpoint, unreadable file) the plain path is deliberately outside the guard, so it still raises instead of being absorbed as "no GPU"
accelerator="none" pins the plain path, for timings comparable across hosts
Unknown value ValueError, rather than being silently ignored

Measurements

One 32-atom Cu fcc supercell, MACE-OMAT-0-small, float32, same process, warm:

Path Time per call
plain e3nn 193 ms
cuEquivariance 32 ms

6.0x, with energies agreeing to every printed digit (both −3.744912 eV/atom). CPU and cueq-not-installed runs reproduce the same energy on the plain path.

Verification

  • tests/mace/ — 30 passed (including the Sella TS/IRC tests).
  • New tests/mace/test_mace_accelerator.py — 16 tests, all against a fake calculator factory, so they need no GPU, no cuequivariance install and no checkpoint download: an unsupported card degrades instead of raising, a missing module degrades, a genuine error on the plain path still surfaces, a failure is not retried per structure, ANI-CC is never offered the accelerator, and the unknown-value path is rejected.
  • platforms.tsv, servers.tsv, the plugin wiring and the version manifests are unchanged by the extra; uv lock --check and ruff are clean.

Merge order with the open UMA/MatGL pull request

Both branches edit the mlip row in .agents/rules/mcp-environments.md and the package table in venv/README.md — different words on the same lines, so whichever lands second will need that one-line conflict resolved. Nothing else overlaps: the venv/mlip lock and pyproject.toml changes in the two branches sit in different regions and merge cleanly. If it is easier, land the UMA/MatGL branch first and this one after a rebase.

@bowen-bd

Copy link
Copy Markdown
Collaborator Author

Why envs (mlip) is red here, and green on the other pull request

The failing tests are the seven MatGL property tests that reference MEGNet-MP-2019.4.1-BandGap-mfi and MEGNet-Eform-MP-2018.6.1, which matgl has deleted from its materialyze Hugging Face org (materialyzeai/matgl#851):

FAILED tests/matgl/test_matgl_property_device.py::test_property_prediction_on_each_device[MEGNet-MP-2019.4.1-BandGap-mfi-bandgap-cpu]
FAILED tests/matgl/test_matgl_property_device.py::test_property_prediction_on_each_device[MEGNet-Eform-MP-2018.6.1-formation_energy-cpu]
FAILED tests/matgl/test_matgl_server.py::test_predict_bandgap
FAILED tests/matgl/test_matgl_wrapper.py::TestMatGLBandgapFunctionals::test_{pbe,hse}_bandgap
FAILED tests/matgl/test_matgl_wrapper.py::TestMatGLBandgapFunctionals::test_functionals_give_different_values
FAILED tests/matgl/test_matgl_wrapper.py::TestMatGLBandgapFunctionals::test_invalid_task_name_defaults_to_pbe

This branch is cut from main and carries only the cuEquivariance change, so those tests are still the upstream ones. The envs (ubuntu-24.04, mlip, cu130) job passes on the UMA/MatGL branch, which retires them along with the models.

So this is a merge-order constraint rather than a defect in this change: land the UMA/MatGL work first, and this job goes green on a rebase. Everything unrelated to those deleted artefacts already passes here, including both mlip image builds (linux/amd64 and linux/arm64), which is what proves cuEquivariance installs on both architectures, and tests/mace itself is green.

MACE's e3nn tensor-product kernels have a fused-CUDA replacement upstream
ships as cuequivariance, and the warp-speed difference is large: 193 ms ->
32 ms per call for a 32-atom cell, with identical energies. Nothing in the
repository asked for it, so every MACE run has been on the slow path.

The packages ride in the torch extras because their relationship with the
torch build is exact — the ops wheels are compiled against one CUDA major,
and cuequivariance_torch imports whichever is installed under the bare
module name cuequivariance_ops_torch. Upstream mace-torch declares only the
cu12 pair; cu13 exists and is what a driver >= 580 host needs, so cu130 gets
it too. About 30 MB per architecture.

Installing is not enabling: MaceWrapper turns it on only on a CUDA device,
and falls back to the plain path everywhere else.
Every MACE calculator was built without an accelerator, so the fused-cueq
path was never taken even where it was installed. MaceWrapper now routes all
six calculator constructions through one builder that prefers cueq.

The constraint that shapes the design is that an optimisation may not cost
anyone the ability to run MACE. mace itself raises when cuequivariance is
missing and, more subtly, when the installed kernels do not cover the card in
use. So:

  - 'auto' (the default) considers cueq only on a CUDA device with both
    halves of cueqvariance importable; on a CPU host, in an environment that
    never synced the cu130 extra, the plain path is used and said so once.
  - An accelerated attempt that fails — an unsupported card, a missing
    kernel, a broken install — is logged with its reason and retried once
    without the accelerator, so MACE loads anyway.
  - The plain path is deliberately outside that guard: a bad checkpoint or an
    unreadable model still raises instead of being absorbed as 'no GPU'.
  - One failure is remembered, so a per-structure rebuild does not retry a
    conversion that cannot work.
  - 'none' pins the plain path for timings that can be compared across hosts,
    and an unknown value is rejected rather than silently ignored.

load_model reports what the calculator will attempt without building it: the
calculator is lazy, and building it would download and convert the
checkpoint just to answer.

Measured on one 32-atom Cu cell, MACE-OMAT-0-small, float32: 193 ms/call on
the plain e3nn path against 32 ms with cueq, energies agreeing to every
printed digit (both -3.744912 eV/atom). No GPU, cueq uninstalled, or the CPU
path all give the same energy on the plain path.

tests/mace/test_mace_accelerator.py pins the fallbacks with a fake factory,
so it needs no GPU, no cueq install and no checkpoint: a simulated
unsupported card degrades instead of raising, a genuine error on the plain
path still surfaces, and a failure is not retried per structure.
MACE-ANI-CC is built by mace_anicc, which fixes its own argument list, so the
calculator never receives enable_cueq. describe_accelerator still reported
'cueq (falls back ...)' for it on any CUDA host, and _accelerator_kwargs
returned the flag for a path that drops it: the same mistake as announcing a
Nitro fallback for a checkpoint that was never eligible.

Gate both on whether the model has an accelerated calculator path, and name
that reason instead of the device or the package, so the loader reports what
this model will do rather than what the host could do in general.
test_extras_are_the_same_everywhere pinned both torch extras to exactly
['torch', 'torchvision'], which cuEquivariance cannot satisfy: its ops wheel
is compiled per CUDA major and published under a different distribution name
(...-ops-torch-cu12 / ...-ops-torch-cu13) with no unsuffixed meta-package, so
unlike torch it cannot be switched by index.

Spell the mlip exception out rather than loosening the assertion: the shared
front end must be identical in both flavours, exactly one ops package must be
present per flavour, and its suffix must match that flavour. fairchem keeps
the original equality check. Anything else appearing in the extras, or the
two flavours drifting, still fails.
@bowen-bd
bowen-bd merged commit a72cc16 into learningmatter-mit:main Oct 27, 2026
17 checks passed
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.

1 participant