Skip to content

Upgrading to Warp 1.16 - #168

Open
massimim wants to merge 11 commits into
Autodesk:mainfrom
massimim:upgrading-warp
Open

massimim wants to merge 11 commits into
Autodesk:mainfrom
massimim:upgrading-warp

Conversation

@massimim

@massimim massimim commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Contributing Guidelines

Description

Upgrade XLB to be compatible with the new API in Warp 1.16.0. or 1.17.0

For the Neon backend, we are using an out-of-tree Warp branch based on 1.16.0.dev0. This branch gives us access to Warp's 'external-type' support until it is merged upstream. The 'external-type' mechanism provides an interface to Neon's CUDA runtime.

The PR also disables Warp compilation of backward-pass kernels by default. This feature must be enabled manually when using XLB with automatic differentiation in the Warp backend.

A new script that run all the tests with all the different backends (JAX, Warp, Neon) has been added.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

How Has This Been Tested?

  • All pytest tests pass

Linting and Code Formatting

Make sure the code follows the project's linting and formatting standards. This project uses Ruff for linting.

To run Ruff, execute the following command from the root of the repository:

ruff check .
  • Ruff passes

massimim and others added 7 commits September 14, 2026 08:00
Warp 1.16 removed the generic type generators and moved several helpers:
- wp.vec(n, dtype=)   -> wp.types.vector(length=n, dtype=)   (46 sites)
- wp.mat((r,c),dtype=)-> wp.types.matrix(shape=(r,c), dtype=)
- wp.mat33(a,b,c)/wp.mat33f(a,b,c) (row-vectors) -> wp.matrix_from_rows(...)
- in-kernel wp.vec(x, length=1) -> wp.vector(x, length=1)   (bc_zouhe)
- wp.build.clear_kernel_cache() -> wp.clear_kernel_cache()
- set wp.config.enable_backward = False for Neon (forward-only; avoids NVRTC
  adjoint compile errors from legacy @wp.func patterns)

Also point setup.py [neon] extra at neon_gpu 0.5.2a3 (update4 wheels).

Co-authored-by: Cursor <cursoragent@cursor.com>
Completes the Warp 1.16 port beyond the xlb/ package:
- examples/ (7 files) + tests/install (1 file): wp.vec -> wp.types.vector for
  Python-scope type generators (dtype=), and -> wp.vector for the in-kernel
  value constructor (length=).
- Restore the wp.Mesh retention fix in mesh_boundary_masker and aabb_close:
  only the integer mesh.id reaches the kernels, so the Mesh's BVH arrays could
  be freed before Neon's deferred NVRTC-compiled container runs.

NOT NUMERICALLY VALIDATED: multires_flow_past_sphere_3d on this stack gives
Cd ~= 0.348 vs ~= 2.680 on the reference stack (warp 1.7.2 + neon 0.5.2a1).
Ruled out: this type port (velocity-set constants are bit-identical), Warp BVH
/mesh queries (identical across versions), voxelization (identical cell count),
the drag operator (identical code), and the Neon C++ core (one benign diff).
Cause still unknown.

Co-authored-by: Cursor <cursoragent@cursor.com>
The Warp 1.7.2 3-vector matrix constructor was inconsistent between scopes:
in Python scope wp.mat33(a,b,c) builds ROWS, but inside a @wp.func/@wp.kernel
it builds COLUMNS. Warp 1.16 split these into matrix_from_rows/matrix_from_cols
precisely to remove that ambiguity.

Both remaining sites are in-kernel, so porting them to matrix_from_rows
transposed the triangle vertex/edge matrices feeding triangle_box_intersect.
Mesh voxelization then found almost no triangle-voxel overlaps: the sphere was
marked with only 10 solid cells instead of 440, so there was effectively no
obstacle in the domain.

Validated on multires_flow_past_sphere_3d (10k steps, D3Q27, Re=5000) against
the reference stack (warp 1.7.2 + neon 0.5.2a1):
  - solid-cell mask now identical (440 vs 440)
  - Cd within 0.15% at every reporting step (final 2.6761 vs 2.6802)
  - max |u| identical, mean |u| within 0.36%
This supersedes the 'NOT NUMERICALLY VALIDATED' note in dd0674f.

Co-authored-by: Cursor <cursoragent@cursor.com>
Warp generates an adjoint copy of every kernel by default, roughly doubling
codegen and compile time. XLB's LBM solvers are forward-only, so this was
already switched off for the Neon backend (where the legacy @wp.func patterns
additionally fail NVRTC adjoint compilation). The plain Warp backend never got
the same treatment and was still paying for adjoints it never launched.

Move the setting into _warp_init_and_select_cuda_device, the common chokepoint
for Warp startup, so both backends pick it up. It has to run before the first
kernel is created, since Warp snapshots enable_backward into a module's options
at that point.

examples/out_of_core/autodiff_lbm.py does differentiate through the solver with
wp.Tape, so add XLB_WARP_ENABLE_BACKWARD to opt back in, and set it there.

Verified: QuadraticEquilibrium's kernel module reports enable_backward=False on
the Warp backend (was True) and True under the opt-in; Neon unchanged. The 87
Warp-backend tests in tests/kernels, tests/grids, tests/boundary_conditions
pass (the 6 test_grid_jax.py failures are a pre-existing JAX PartitionSpec
equality change, identical on an unmodified tree).

Co-authored-by: Cursor <cursoragent@cursor.com>
Makes Warp's adjoint (backward) codegen switchable per call instead of only via
the XLB_WARP_ENABLE_BACKWARD environment variable. Resolution order is explicit
argument, then environment variable, then off, so an explicit enable_backward=False
overrides the environment and scripts that cannot pass arguments keep working.

The resolved value is recorded on DefaultConfig.enable_backward for
introspection; it stays None on the JAX backend, which does not use Warp.

autodiff_lbm.py now passes enable_backward=True directly rather than setting the
environment variable, which reads better for the one case that differentiates
through the solver.

The parameter is optional and trailing, and all 34 existing init() call sites
pass keyword arguments, so nothing else needs updating.

Verified all six combinations of argument (unset/True/False) against environment
(unset/1) resolve as intended, down to the kernel module's enable_backward
option; Neon honors an explicit override too, and JAX is untouched. The 86
Warp-backend tests in tests/kernels, tests/grids/test_grid_warp.py and
tests/boundary_conditions pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
JAX 0.10.0 made PartitionSpec stop inheriting from tuple and stop comparing
equal to one, so 'f.sharding.spec == ("cardinality", "x", "y")' became False
and the 2D/3D grid tests failed across all six parametrizations. requirements.txt
declares jax[cuda] unpinned, so the environment resolved to 0.10.2 and picked up
the change.

The sharding XLB produces was never wrong: field shape, sharding mesh, and the
spec's axes all match what the tests expect. Only the comparison was stale, which
is why the shape and mesh assertions kept passing and just the spec one failed.

Full suite is now 94 passed, 0 failed.

Co-authored-by: Cursor <cursoragent@cursor.com>
SIM105: the nested try/except/pass around the cuda:0 fallback becomes
contextlib.suppress(Exception). Behavior is unchanged - try the device from
XLB_WARP_DEVICE, fall back to the first GPU, and otherwise leave Warp's own
default in place.

Also collapse the _pi_vec construction in second_moment.py, where the Warp 1.16
port's mechanical 'wp.vec(' -> 'wp.types.vector(length=' rewrite left the call
split as 'wp.types.vector(length=' / '_pi_dim,'. It now reads as one line,
matching _f_vec directly above it. Whitespace only.

'ruff check .' passes and xlb/ plus tests/ are fully formatted; the one
remaining format deviation, examples/cfd/differentiable_lbm.py, is untouched
upstream code. Suite still 94 passed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@massimim massimim changed the title Upgrading warp Upgrading to Warp 1.16 Sep 15, 2026
massimim and others added 4 commits September 15, 2026 18:34
tests/run_backend_test_envs.py builds a virtualenv per backend, installs XLB
editable from the repo root, reports which Warp/Neon/JAX the env resolved to,
and runs the suite in each:

  python tests/run_backend_test_envs.py              # warp and neon
  python tests/run_backend_test_envs.py --backend neon --reuse

  warp: pip install -e .[warp,test]  -> warp-lang from PyPI
  neon: pip install -e .[neon,test]  -> neon_gpu wheel and its bundled Warp fork

JAX is in install_requires, so both profiles get it regardless of backend;
--jax cuda adds setup.py's cuda extra to swap in a GPU jaxlib.

tests/backends/test_neon_backend.py adds the suite's first Neon backend
coverage. Previously nothing exercised it: every test used JAX or WARP, and
tests/install/flow_past_sphere_3d_test.py skips Neon because the sphere's
HalfwayBounceBackBC has no Neon implementation. These four tests cover what
needs no boundary condition - neon.init() registering XLB's custom Warp types,
grid and field allocation, the device fill plus host read-back, and operator
construction - and skip when neon is absent or no CUDA device exists, so the
warp profile stays green.

The runner also warns when a Neon env still has the warp-lang distribution
registered. Both ship files at site-packages/warp and XLB's install_requires
pulls warp-lang in regardless of the [neon] extra, so whichever pip unpacks
last owns them; tests pass either way, which makes it invisible otherwise.

Verified: both profiles build from scratch and pass (98 tests each, ~6 min per
profile); the Neon tests pass alone and with Warp kernels built first in the
same session; pytest's exit code 5 on an all-skipped selection is reported as
'no tests ran' rather than a failure.

Co-authored-by: Cursor <cursoragent@cursor.com>
pip install -e .[neon,test] left both neon_gpu and warp-lang installed. Both
unpack into site-packages/warp, so whichever pip wrote last owned the
directory: a later 'pip uninstall warp-lang' would delete files Neon needs, and
an upgrade would silently replace Neon's fork. The env still imported and the
whole suite still passed, so nothing caught it.

warp-lang was listed in install_requires, which made the collision unavoidable
for [neon]. The InstallWithNeonHooks cmdclass was meant to undo it but cannot:
pip installs dependencies after building the project, and for wheel and PEP 660
editable installs the 'install' command never runs at all. It also detected the
extra by scanning sys.argv, which PEP 517 builds do not preserve.

So warp-lang moves out of install_requires into the [warp] extra, matching what
the README already documented ('Do not install both in the same environment'),
and the dead hook is removed. Warp now comes from exactly one place: [warp] for
PyPI's build or [neon] for the bundled fork.

pip will not remove a warp-lang that predates the install, so xlb/__init__.py
warns when it finds both distributions and says how to recover. It also turns a
missing Warp into a message naming the two extras instead of a bare
ModuleNotFoundError, since the base install no longer supplies one.

test_neon_install_warp_cleanup.py asserted only that 'import neon' and
'import warp' succeed, which held in the mixed state. It now asserts that a
clean [neon] install has no warp-lang distribution and imports the fork, and
that a pre-existing warp-lang produces the warning. Install docs and the
install-matrix scenarios move to the explicit extras.

Verified by rebuilding both envs from scratch: the warp env resolves warp-lang
1.17.0 with 95 passed and the Neon module skipped, and the neon env resolves
warp 1.16.0.dev0 with warp-lang absent and 99 passed.

Co-authored-by: Cursor <cursoragent@cursor.com>
The extra allowed warp-lang>=1.10.0, so a fresh install resolved 1.17.0 while
XLB's kernels target the 1.16 API and the [neon] extra gets a 1.16 fork. Pin to
>=1.16,<1.17 so both extras present the same API surface.

PyPI currently has one release in the series, 1.16.0; the range leaves room for
a future 1.16 patch. Verified by rebuilding the warp env on the pin: it
resolves warp-lang 1.16.0, every Warp API XLB uses is present (types.vector,
types.matrix, matrix_from_cols, clear_kernel_cache, Tape, Mesh), and the suite
is 95 passed with the Neon module skipped.

Co-authored-by: Cursor <cursoragent@cursor.com>
Narrowing to <1.17 excluded 1.17.0, which the suite passes on (95 passed, same
as 1.16.0). Widen to >=1.16,<1.18 so both tested series are allowed while 1.18
stays out until it has been run.

A fresh install now resolves 1.17.0 again, the highest allowed; install
warp-lang==1.16.* to match the series of the fork neon_gpu bundles.

Co-authored-by: Cursor <cursoragent@cursor.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.

1 participant