Skip to content

Commit cabea00

Browse files
nvtwclaudeMilad-Rakhsha-NV
authored
Performance improvements for Kamino's direct solvers (#3610)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Milad Rakhsha <mrakhsha@nvidia.com>
1 parent c487bc8 commit cabea00

11 files changed

Lines changed: 903 additions & 471 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
- Add viewer layer system to overlay multiple solvers/models in supported rendering viewers; call `ViewerBase.activate(layer_id)` to route subsequent `set_model` / `log_state` / `log_*` calls into a named layer, `ViewerBase.set_layer_visible()` to toggle layers independently, and `ViewerBase.set_layer_transform()` to position layers side-by-side. See `example_basic_multi_solver_overlay.py`
2424
- Add `Heightfield.create_from_mesh()` and `newton.utils.rasterize_mesh_to_heightfield()` to build a heightfield collider by ray-casting a `wp.Mesh`, replacing a large static terrain mesh with an equivalent heightfield.
2525
- Add `ViewerBase.camera_speed` to configure keyboard translation speed for interactive viewers. (#3439)
26-
- Add opt-in DVI forward dynamics to `SolverKamino` through `SolverKamino.Config(dynamics_solver="dvi")`, with sparse and dense execution, DVI-specific diagnostics, and warm-starting. PADMM remains the default.
26+
- Add opt-in DVI forward dynamics to `SolverKamino` through `SolverKamino.Config(dynamics_solver="dvi")`, with sparse and dense execution, DVI-specific convergence diagnostics, warm-starting, bounded contact-recovery controls, and RCM-reordered bilateral factorization with reusable ordering and panel-parallel numeric factorization for large systems. PADMM remains the default.
2727
- Add SDF contact support for convex-hull shapes with mesh-attached SDFs and opt-in SDF contact generation for box shapes.
2828
- Add opt-in filtering of static-static, static-kinematic, and kinematic-kinematic contacts during broad-phase collision detection. Set `CollisionPipeline(include_static_kinematic_pairs=False)` to enable filtering; the default preserves existing contact generation. `Model.shape_contact_pairs` remains an unfiltered superset for direct consumers such as `SolverKamino` and hydroelastic SDF setup.
2929
- Add opt-in `body_frame_origin="com"` to `ModelBuilder.add_rod()` and `ModelBuilder.add_rod_graph()` for COM-centered cable capsule body frames.
@@ -79,6 +79,8 @@
7979

8080
### Fixed
8181

82+
- Complete Kamino RCM traversal for large and disconnected systems and reuse the resulting permutation by default; set `reuse_permutation=False` to recompute it for changing matrix topology.
83+
- Fix panel-parallel RCM-blocked LLT factorization hanging when a matrix ends in a partial tile.
8284
- Fix USD capsule, cylinder, and cone visual and site scaling to follow the authored primitive axis.
8385
- Fix USD plane visual width and length to scale along the axes defined by the `UsdGeomPlane` schema, and orient X- and Y-axis plane visuals along the authored axis.
8486
- Validate `ArticulationView` mask shapes and devices before launching selection kernels. (#3448)

docs/solvers/kamino.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,18 @@ Set ``sparse_jacobian=False`` for fully dense DVI, or set
5656
solver. With
5757
``collect_solver_info=True``, DVI stores terminal residual status that should
5858
not be interpreted as PADMM ADMM residuals.
59+
60+
For large bilateral systems, opt into RCM-reordered factorization explicitly:
61+
62+
.. code-block:: python
63+
64+
config.dvi.bilateral_solver_type = "LLTBRCM"
65+
config.dvi.bilateral_solver_kwargs = {
66+
"block_size": 32,
67+
"reuse_permutation": True,
68+
"parallel_factorization": True,
69+
}
70+
71+
The cached permutation remains mathematically valid when matrix values or
72+
sparsity change and is recomputed automatically if the active dimension
73+
changes. Keep the default ``"LLTB"`` solver for small systems.

newton/_src/solvers/kamino/_src/linalg/factorize/_tile_builtins.py

Lines changed: 32 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2026 The Newton Developers
22
# SPDX-License-Identifier: Apache-2.0
33

4-
"""Availability checks and native fallbacks for optional Warp tile builtins."""
4+
"""Availability checks and fallback for optional Warp tile builtins."""
55

66
import inspect
77
import os
@@ -26,157 +26,39 @@ def _has_warp_builtin(name: str) -> bool:
2626
return name in builtin_functions
2727

2828

29-
def _has_native_tile_arg_support() -> bool:
29+
def _has_native_left_transpose_update_support() -> bool:
3030
if not _tile_transpose_update_enabled():
3131
return False
3232

3333
try:
3434
from warp._src import codegen # noqa: PLC0415
3535

36-
source = inspect.getsource(codegen.codegen_snippet)
37-
except Exception:
38-
return False
39-
40-
return "template_params" in source and "is_tile(arg.type)" in source and "& {arg.emit()" in source
41-
42-
43-
def _has_native_tile_access_helpers() -> bool:
44-
if not _tile_transpose_update_enabled():
45-
return False
46-
47-
try:
36+
codegen_source = inspect.getsource(codegen.codegen_snippet)
4837
tile_header = Path(wp.__file__).resolve().parent / "native" / "tile.h"
49-
source = tile_header.read_text(encoding="utf-8")
38+
tile_source = tile_header.read_text(encoding="utf-8")
5039
except Exception:
5140
return False
5241

5342
return (
54-
"tile_read(const tile_register_t" in source
55-
and "tile_read(const tile_shared_t" in source
56-
and "tile_add(tile_register_t" in source
57-
and "tile_add(tile_shared_t" in source
43+
"template_params" in codegen_source
44+
and "is_tile(arg.type)" in codegen_source
45+
and "& {arg.emit()" in codegen_source
46+
and "tile_add(tile_register_t" in tile_source
47+
and "tile_add(tile_shared_t" in tile_source
5848
)
5949

6050

61-
def _has_native_tile_update_support() -> bool:
62-
return _has_native_tile_arg_support() and _has_native_tile_access_helpers()
63-
64-
65-
def _copy_dense_2d_snippet(tile_name: str, layout_name: str, values_name: str, cols_name: str, storage: str) -> str:
66-
if storage == "register":
67-
return (
68-
f"{tile_name}.apply([&](int reg, auto c) {{ "
69-
f"{values_name}[c[0] * {cols_name} + c[1]] = {tile_name}.data[reg]; }});"
70-
)
71-
if storage == "generic":
72-
return f"""for (int linear = WP_TILE_THREAD_IDX; linear < {layout_name}::Size; linear += WP_TILE_BLOCK_DIM) {{
73-
auto c = {layout_name}::coord_from_linear(linear);
74-
int reg = linear / WP_TILE_BLOCK_DIM;
75-
{values_name}[c[0] * {cols_name} + c[1]] = tile_read({tile_name}, reg, linear);
76-
}}"""
77-
raise ValueError(f"Unsupported tile storage specialization: {storage!r}")
78-
79-
80-
def _update_output_snippet(
81-
layout_name: str,
82-
output_name: str,
83-
rows_name: str,
84-
cols_name: str,
85-
k_name: str,
86-
left_values_name: str,
87-
right_values_name: str,
88-
left_transposed: bool,
89-
storage: str,
90-
) -> str:
91-
if left_transposed:
92-
product = f"{left_values_name}[k * {rows_name} + c[0]] * {right_values_name}[k * {cols_name} + c[1]]"
93-
else:
94-
product = f"{left_values_name}[c[0] * {k_name} + k] * {right_values_name}[c[1] * {k_name} + k]"
95-
96-
if storage == "shared":
97-
write = f"""const T value = a * sum;
98-
if constexpr ({layout_name}::Unique)
99-
{output_name}.data(linear) += value;
100-
else
101-
wp::atomic_add(&{output_name}.data(linear), value);"""
102-
elif storage == "register":
103-
return f"""const T a = static_cast<T>(alpha);
104-
{output_name}.apply([&](int reg, auto c) {{
105-
T sum = T{{}};
106-
WP_PRAGMA_UNROLL
107-
for (int k = 0; k < {k_name}; ++k) {{
108-
sum += {product};
109-
}}
110-
{output_name}.data[reg] += a * sum;
111-
}});
112-
WP_TILE_SYNC();"""
113-
elif storage == "generic":
114-
write = f"""int reg = linear / WP_TILE_BLOCK_DIM;
115-
tile_add({output_name}, reg, linear, a * sum);"""
116-
else:
117-
raise ValueError(f"Unsupported tile storage specialization: {storage!r}")
118-
119-
return f"""const T a = static_cast<T>(alpha);
120-
for (int linear = WP_TILE_THREAD_IDX; linear < {layout_name}::Size; linear += WP_TILE_BLOCK_DIM) {{
121-
auto c = {layout_name}::coord_from_linear(linear);
122-
T sum = T{{}};
123-
WP_PRAGMA_UNROLL
124-
for (int k = 0; k < {k_name}; ++k) {{
125-
sum += {product};
126-
}}
127-
{write}
128-
}}
129-
WP_TILE_SYNC();"""
130-
131-
132-
def _make_tile_matmul_transpose_update_snippet(out_storage: str, input_storage: str) -> str:
133-
copy_left = _copy_dense_2d_snippet("left", "LeftLayout", "left_values", "K", input_storage)
134-
copy_right = _copy_dense_2d_snippet("right", "RightLayout", "right_values", "K", input_storage)
135-
update_out = _update_output_snippet(
136-
"OutLayout", "out", "Rows", "Cols", "K", "left_values", "right_values", False, out_storage
137-
)
138-
return f"""using OutTile = tile_out;
139-
using LeftTile = tile_left;
140-
using RightTile = tile_right;
141-
using T = typename OutTile::Type;
142-
using OutLayout = typename OutTile::Layout;
143-
using LeftLayout = typename LeftTile::Layout;
144-
using RightLayout = typename RightTile::Layout;
145-
146-
static_assert(OutLayout::Shape::N == 2, "out must be 2D");
147-
static_assert(LeftLayout::Shape::N == 2, "left must be 2D");
148-
static_assert(RightLayout::Shape::N == 2, "right must be 2D");
149-
static_assert(LeftLayout::Shape::dim(0) == OutLayout::Shape::dim(0), "left rows must match out rows");
150-
static_assert(RightLayout::Shape::dim(0) == OutLayout::Shape::dim(1), "right rows must match out cols");
151-
static_assert(LeftLayout::Shape::dim(1) == RightLayout::Shape::dim(1), "left/right cols must match");
152-
153-
constexpr int Rows = OutLayout::Shape::dim(0);
154-
constexpr int Cols = OutLayout::Shape::dim(1);
155-
constexpr int K = LeftLayout::Shape::dim(1);
156-
157-
#if defined(__CUDA_ARCH__)
158-
__shared__ T left_values[Rows * K];
159-
__shared__ T right_values[Cols * K];
160-
#else
161-
T left_values[Rows * K];
162-
T right_values[Cols * K];
163-
#endif
164-
165-
{copy_left}
166-
{copy_right}
167-
WP_TILE_SYNC();
168-
169-
{update_out}
170-
"""
51+
HAS_TILE_MATMUL_TRANSPOSE_UPDATE = _has_warp_builtin("tile_matmul_transpose_update")
52+
HAS_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE = _has_warp_builtin("tile_matmul_left_transpose_update")
53+
HAS_NATIVE_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE = (
54+
not HAS_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE and _has_native_left_transpose_update_support()
55+
)
17156

17257

173-
def _make_tile_matmul_left_transpose_update_snippet(out_storage: str, input_storage: str) -> str:
174-
copy_left = _copy_dense_2d_snippet("left", "LeftLayout", "left_values", "Rows", input_storage)
175-
copy_right = _copy_dense_2d_snippet("right", "RightLayout", "right_values", "Cols", input_storage)
176-
update_out = _update_output_snippet(
177-
"OutLayout", "out", "Rows", "Cols", "K", "left_values", "right_values", True, out_storage
178-
)
179-
return f"""using OutTile = tile_out;
58+
@cache
59+
def make_tile_matmul_left_transpose_update_func(block_size: int):
60+
"""Create ``out += alpha * transpose(left) @ right`` for the LLT solve."""
61+
snippet = """using OutTile = tile_out;
18062
using LeftTile = tile_left;
18163
using RightTile = tile_right;
18264
using T = typename OutTile::Type;
@@ -203,46 +85,22 @@ def _make_tile_matmul_left_transpose_update_snippet(out_storage: str, input_stor
20385
T right_values[K * Cols];
20486
#endif
20587
206-
{copy_left}
207-
{copy_right}
88+
left.apply([&](int reg, auto c) { left_values[c[0] * Rows + c[1]] = left.data[reg]; });
89+
right.apply([&](int reg, auto c) { right_values[c[0] * Cols + c[1]] = right.data[reg]; });
20890
WP_TILE_SYNC();
20991
210-
{update_out}
211-
"""
212-
213-
214-
HAS_TILE_MATMUL_TRANSPOSE_UPDATE = _has_warp_builtin("tile_matmul_transpose_update")
215-
HAS_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE = _has_warp_builtin("tile_matmul_left_transpose_update")
216-
HAS_NATIVE_TILE_MATMUL_TRANSPOSE_UPDATE = not HAS_TILE_MATMUL_TRANSPOSE_UPDATE and _has_native_tile_update_support()
217-
HAS_NATIVE_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE = (
218-
not HAS_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE and _has_native_tile_update_support()
219-
)
220-
221-
222-
@cache
223-
def make_tile_matmul_transpose_update_func(
224-
block_size: int, out_storage: str = "shared", input_storage: str = "register"
225-
):
226-
"""Create ``out += alpha * left @ transpose(right)`` as a native tile function."""
227-
snippet = _make_tile_matmul_transpose_update_snippet(out_storage, input_storage)
228-
229-
@wp.func_native(snippet)
230-
def tile_matmul_transpose_update(
231-
out: wp.tile[float, block_size, block_size],
232-
left: wp.tile[float, block_size, block_size],
233-
right: wp.tile[float, block_size, block_size],
234-
alpha: float,
235-
): ...
236-
237-
return tile_matmul_transpose_update
238-
239-
240-
@cache
241-
def make_tile_matmul_left_transpose_update_func(
242-
block_size: int, out_storage: str = "generic", input_storage: str = "register"
243-
):
244-
"""Create ``out += alpha * transpose(left) @ right`` as a native tile function."""
245-
snippet = _make_tile_matmul_left_transpose_update_snippet(out_storage, input_storage)
92+
const T a = static_cast<T>(alpha);
93+
for (int linear = WP_TILE_THREAD_IDX; linear < OutLayout::Size; linear += WP_TILE_BLOCK_DIM) {
94+
auto c = OutLayout::coord_from_linear(linear);
95+
T sum = T{};
96+
WP_PRAGMA_UNROLL
97+
for (int k = 0; k < K; ++k) {
98+
sum += left_values[k * Rows + c[0]] * right_values[k * Cols + c[1]];
99+
}
100+
int reg = linear / WP_TILE_BLOCK_DIM;
101+
tile_add(out, reg, linear, a * sum);
102+
}
103+
WP_TILE_SYNC();"""
246104

247105
@wp.func_native(snippet)
248106
def tile_matmul_left_transpose_update(

newton/_src/solvers/kamino/_src/linalg/factorize/llt_blocked.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,9 @@
1010

1111
from ._tile_builtins import (
1212
HAS_NATIVE_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE,
13-
HAS_NATIVE_TILE_MATMUL_TRANSPOSE_UPDATE,
1413
HAS_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE,
1514
HAS_TILE_MATMUL_TRANSPOSE_UPDATE,
1615
make_tile_matmul_left_transpose_update_func,
17-
make_tile_matmul_transpose_update_func,
1816
)
1917

2018
###
@@ -166,10 +164,6 @@ def llt_blocked_factorize_kernel(
166164
L_block = wp.tile_load(L_i, shape=(block_size, block_size), offset=(k, j))
167165
if wp.static(HAS_TILE_MATMUL_TRANSPOSE_UPDATE):
168166
wp.tile_matmul_transpose_update(A_kk_tile, L_block, L_block, alpha=-1.0)
169-
elif wp.static(HAS_NATIVE_TILE_MATMUL_TRANSPOSE_UPDATE):
170-
wp.static(make_tile_matmul_transpose_update_func(block_size, "shared", "register"))(
171-
A_kk_tile, L_block, L_block, -1.0
172-
)
173167
else:
174168
L_block_T = wp.tile_transpose(L_block)
175169
wp.tile_matmul(L_block, L_block_T, A_kk_tile, alpha=-1.0)
@@ -206,10 +200,6 @@ def llt_blocked_factorize_kernel(
206200
L_2_tile = wp.tile_load(L_i, shape=(block_size, block_size), offset=(k, j))
207201
if wp.static(HAS_TILE_MATMUL_TRANSPOSE_UPDATE):
208202
wp.tile_matmul_transpose_update(A_ik_tile, L_tile, L_2_tile, alpha=-1.0)
209-
elif wp.static(HAS_NATIVE_TILE_MATMUL_TRANSPOSE_UPDATE):
210-
wp.static(make_tile_matmul_transpose_update_func(block_size, "shared", "register"))(
211-
A_ik_tile, L_tile, L_2_tile, -1.0
212-
)
213203
else:
214204
L_T_tile = wp.tile_transpose(L_2_tile)
215205
wp.tile_matmul(L_tile, L_T_tile, A_ik_tile, alpha=-1.0)
@@ -306,7 +296,7 @@ def llt_blocked_solve_kernel(
306296
if wp.static(HAS_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE):
307297
wp.tile_matmul_left_transpose_update(rhs_tile, L_tile, x_tile, alpha=-1.0)
308298
elif wp.static(HAS_NATIVE_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE):
309-
wp.static(make_tile_matmul_left_transpose_update_func(block_size, "generic", "register"))(
299+
wp.static(make_tile_matmul_left_transpose_update_func(block_size))(
310300
rhs_tile, L_tile, x_tile, -1.0
311301
)
312302
else:
@@ -395,7 +385,7 @@ def llt_blocked_solve_inplace_kernel(
395385
if wp.static(HAS_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE):
396386
wp.tile_matmul_left_transpose_update(rhs_tile, L_tile, x_tile, alpha=-1.0)
397387
elif wp.static(HAS_NATIVE_TILE_MATMUL_LEFT_TRANSPOSE_UPDATE):
398-
wp.static(make_tile_matmul_left_transpose_update_func(block_size, "generic", "register"))(
388+
wp.static(make_tile_matmul_left_transpose_update_func(block_size))(
399389
rhs_tile, L_tile, x_tile, -1.0
400390
)
401391
else:

0 commit comments

Comments
 (0)