Skip to content

Commit 064cc09

Browse files
committed
Address GRU actuator review feedback
Move optional bias conditioning to a solver-neutral Control custom attribute and bind drive-specific inputs through a generic private hook. Support ordered target velocity features, update documentation, and expand regression coverage.
1 parent ccd9813 commit 064cc09

9 files changed

Lines changed: 320 additions & 101 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Add experimental `DriveNeuralGRU` support for ONNX checkpoints evaluated with Warp-NN, optionally using caller-populated `state.mujoco.qfrc_bias` for generalized bias-force conditioning with stateful explicit inference.
1+
Add experimental `DriveNeuralGRU` support for ONNX checkpoints evaluated with Warp-NN, using metadata-selected joint and target features with optional caller-populated `control.actuator.dynamic_bias` conditioning and stateful explicit inference.

docs/concepts/actuators.rst

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,10 @@ state objects — simply omit them:
211211
Neural-Network Checkpoints
212212
--------------------------
213213

214-
Neural-network drives (:class:`DriveNeuralGRU`, :class:`DriveNeuralMLP`,
215-
:class:`DriveNeuralLSTM`) support two checkpoint backends. `ONNX
216-
<https://onnx.ai/>`__ (``.onnx``) is an open format for trained networks, which
217-
Warp-NN runs with its own Warp kernels. Torch checkpoints use the Torch backend
218-
and require PyTorch.
214+
Neural-network drives support `ONNX <https://onnx.ai/>`__ (``.onnx``), which
215+
Warp-NN runs with its own Warp kernels. :class:`DriveNeuralMLP` and
216+
:class:`DriveNeuralLSTM` additionally support Torch checkpoints through the
217+
Torch backend; :class:`DriveNeuralGRU` uses ONNX only.
219218

220219
Torch checkpoints are pt2 archives (``.pt2``) saved with ``torch.export.save``.
221220
Checkpoint metadata (scales and network configuration) is stored as a JSON
@@ -247,8 +246,8 @@ The drive supports explicit, stateful actuator models.
247246

248247
Each network invocation has the following contract:
249248

250-
* input shape ``[1, N, F]`` with ``input_columns`` equal to ``position``,
251-
``position_error``, ``velocity``, optionally followed by ``dynamic_bias``;
249+
* input shape ``[1, N, F]``, with the ordered features selected by
250+
``input_columns``;
252251
* hidden-state shape ``[layer_count, N, hidden_size]``; and
253252
* one scalar output per actuator.
254253

@@ -259,42 +258,50 @@ scale are read from the graph. GRU nodes use ``layout=0`` and
259258
``linear_before_reset=1``; stacked layers share one hidden size. All weights
260259
must be embedded in the ONNX file.
261260

262-
``position_error`` is ``target_position - position`` without angle wrapping.
263-
The checkpoint name ``dynamic_bias`` refers to MuJoCo's generalized bias force
264-
``qfrc_bias = c(q, v)``: the Coriolis, centrifugal, and gravitational forces
265-
for each joint DOF. It excludes passive, actuator, user-applied, and
266-
constraint forces. Checkpoint producers are responsible for using consistent
267-
semantics when collecting this feature. See the `MuJoCo equations of motion
268-
<https://mujoco.readthedocs.io/en/3.7.0/computation/index.html>`_.
269-
270-
A checkpoint may append ``dynamic_bias`` to ``input_columns``. When it does,
271-
the caller supplies the generalized bias force through
272-
``state.mujoco.qfrc_bias``, a one-dimensional ``wp.float32`` array on the
273-
actuator device with the same global DOF layout and length as
274-
``state.joint_qd``. Adding a GRU actuator through :class:`ModelBuilder` requests
275-
this optional State array automatically, including for USD-instantiated
276-
actuators. The drive gathers it with the actuator's velocity indices.
277-
When ``dynamic_bias`` is absent, the drive neither reads nor requires this
278-
State array. Newton does not compute its values; applications using this input
279-
must populate every active State before actuator evaluation.
280-
281-
Target velocity and feedforward ``control.joint_act`` are not consumed. The
282-
generalized bias force conditions the network input and is not added to the
261+
``input_columns`` is a non-empty, duplicate-free ordered selection from:
262+
263+
* ``position``;
264+
* ``position_error``, computed as ``target_position - position`` without angle
265+
wrapping;
266+
* ``velocity``;
267+
* ``target_velocity``;
268+
* ``velocity_error``, computed as ``target_velocity - velocity``; and
269+
* ``dynamic_bias``.
270+
271+
The first five features use the standard State and Control arrays already
272+
passed to every drive. ``dynamic_bias`` is an optional, caller-supplied
273+
generalized bias-force input. When selected, :class:`ModelBuilder` registers a
274+
one-dimensional ``wp.float32`` Control array at
275+
``control.actuator.dynamic_bias``. It has the same global joint-DOF layout and
276+
length as ``state.joint_qd`` and is gathered with the actuator's velocity
277+
indices. This input is registered only for models whose GRU checkpoint selects
278+
it, including USD-instantiated actuators.
279+
280+
Newton clears this Control array with :meth:`Control.clear` but does not compute
281+
its values. Applications must therefore clear Control first, then populate the
282+
current generalized bias force before each actuator evaluation. For models
283+
trained with MuJoCo data, this feature commonly corresponds to ``qfrc_bias =
284+
c(q, v)``: the Coriolis, centrifugal, and gravitational generalized forces.
285+
Other solvers may provide the equivalent quantity. Checkpoint producers and
286+
applications are responsible for using consistent physical semantics for this
287+
feature. Feedforward ``control.joint_act`` is not consumed, and
288+
``dynamic_bias`` conditions the network rather than being added to its
283289
predicted torque.
284290

285291
.. code-block:: python
286292
287293
control.clear(model)
288294
control.joint_target_q.assign(target_positions)
289-
state.mujoco.qfrc_bias.assign(computed_generalized_bias_force)
295+
control.actuator.dynamic_bias.assign(computed_generalized_bias_force)
290296
actuator.step(state, control, actuator_state_a, actuator_state_b, dt=sample_dt_s)
297+
actuator_state_a, actuator_state_b = actuator_state_b, actuator_state_a
291298
292299
Newton reads ``input_columns``, ``sample_dt_s``, and normalization statistics
293300
for the selected inputs and torque output. The runtime actuator timestep must
294301
match ``sample_dt_s``. A separate :class:`Delay` may be composed when additional
295-
runtime delay is desired. It delays the standard actuator targets and
296-
feedforward channel; ``state.mujoco.qfrc_bias`` remains a current-state network
297-
feature.
302+
runtime delay is desired. Target-derived features use its delayed target
303+
arrays; ``control.actuator.dynamic_bias`` remains a current, per-evaluation
304+
input.
298305

299306
Input normalization is applied feature by feature as
300307
``(value - mean) / std``. The network's scalar output is converted back to
@@ -312,11 +319,11 @@ Deferred capabilities are tracked here:
312319
- Status
313320
* - Raw target position as a network feature
314321
- Deferred; the drive derives and consumes position error.
315-
* - Arbitrary metadata-selected State or Control features
316-
- Deferred; the drive supports the three base features and optional ``dynamic_bias``.
322+
* - Additional State or Control features
323+
- Deferred; the drive supports the five built-in features listed above and optional ``dynamic_bias``.
317324
* - Automatic generalized-bias-force computation
318-
- Deferred; Newton allocates ``state.mujoco.qfrc_bias``, but callers populate it.
319-
* - Target velocity/error, additive feedforward effort, solver-PD, and previous torque
325+
- Deferred; Newton allocates the optional Control input, but callers populate it.
326+
* - Additive feedforward effort, solver-PD, and previous torque
320327
- Deferred.
321328
* - Residual torque and hybrid physics baselines
322329
- Deferred; the scalar output is interpreted as predicted torque.

docs/concepts/extended_attributes.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,6 @@ The canonical list is :attr:`State.EXTENDED_ATTRIBUTES <newton.State.EXTENDED_AT
115115
* - ``State.mujoco.qfrc_actuator``
116116
- Actuator forces in generalized (joint DOF) coordinates, namespaced under ``state.mujoco.qfrc_actuator``.
117117
Only populated by :class:`~newton.solvers.SolverMuJoCo`.
118-
* - ``State.mujoco.qfrc_bias``
119-
- MuJoCo generalized bias forces [N or N·m, depending on joint type],
120-
shape [joint_dof_count]. GRU actuators request this array automatically;
121-
callers populate it from MuJoCo ``qfrc_bias`` before evaluation.
122118

123119

124120
Notes

newton/_src/actuators/actuator.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from .clamping.base import ClampingBase
1414
from .delay import Delay
1515
from .drives.base import DriveBase
16-
from .drives.drive_neural_gru import DriveNeuralGRU
1716
from .effort_mode_explicit import _EffortModeExplicit
1817
from .effort_mode_implicit import ImplicitOptions, ResponseOracle, _EffortModeImplicit
1918

@@ -390,8 +389,9 @@ def step(
390389

391390
# --- 2+3. Effort mode: compute raw effort and clamp ---
392391
drive_state = current_act_state.drive_state if current_act_state else None
393-
if isinstance(self.drive, DriveNeuralGRU) and self.drive._uses_bias_force:
394-
self.drive._bound_bias_force = sim_state.mujoco.qfrc_bias
392+
bind_control_inputs = getattr(self.drive, "_bind_control_inputs", None)
393+
if bind_control_inputs is not None:
394+
bind_control_inputs(sim_control)
395395
output_forces = self._effort_mode.compute_force(
396396
sim_state,
397397
positions,

newton/_src/actuators/drives/base.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ def reset(self, mask: wp.array[wp.bool] | None = None) -> None:
5050
"""
5151

5252
SHARED_PARAMS: ClassVar[set[str]] = set()
53+
_control_input_attribute_keys: ClassVar[tuple[str, ...]] = ()
54+
55+
def _required_control_attributes(self) -> tuple[str, ...]:
56+
"""Return JOINT_DOF float32 custom Control attributes required by this drive."""
57+
return self._control_input_attribute_keys
58+
59+
def _bind_control_inputs(self, control: Any) -> None:
60+
"""Bind required colon-addressed attributes from the current Control object."""
61+
self._bound_control_inputs = {}
62+
bound_inputs = {}
63+
for key in self._required_control_attributes():
64+
value = control
65+
for component in key.split(":"):
66+
value = getattr(value, component)
67+
bound_inputs[key] = value
68+
self._bound_control_inputs = bound_inputs
5369

5470
@classmethod
5571
def resolve_arguments(cls, args: dict[str, Any]) -> dict[str, Any]:

0 commit comments

Comments
 (0)