Skip to content

Fix circular buffer push broadcast shape for non-2D data#1045

Closed
ZzzzzzS wants to merge 1 commit into
mujocolab:mainfrom
ZzzzzzS:fix-circular-buffer-where-dims
Closed

Fix circular buffer push broadcast shape for non-2D data#1045
ZzzzzzS wants to merge 1 commit into
mujocolab:mainfrom
ZzzzzzS:fix-circular-buffer-where-dims

Conversation

@ZzzzzzS

@ZzzzzzS ZzzzzzS commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

CircularBuffer.push crashes with a broadcast shape mismatch when the buffered data has more than two dimensions. The bug surfaces in DcMotorActuator when delay is configured with a variable lag range (delay_min_lag != delay_max_lag) and the actuator operates in position mode. In this configuration, apply_delay stacks position_target, velocity_target, and effort_target into a 3-D tensor with shape (num_envs, num_targets, 3), then pushes it into the delay buffer.

The root cause is in the first-push backfill logic, which hard-coded [None, :, None] for the torch.where condition. This produces a condition of shape (1, batch_size, 1), but the buffer slice has shape (max_len, num_envs, num_targets, 3). During broadcasting, PyTorch misaligns the condition's batch dimension with the buffer's num_targets dimension. When those differ — for example num_envs=4 but the actuator controls 2 joints — the broadcast fails with a 4 vs 2 runtime error. The bug is latent when delay_min_lag == delay_max_lag because the fixed-delay path does not exercise this backfill code.

The fix replaces the hard-coded views with is_first_push.view(1, batch_size, *([1] * (data.ndim - 1))) and data.unsqueeze(0), so the broadcast shape is derived dynamically from data.ndim and works for tensors of any rank. A regression test for DC motor delay in position mode with delay_min_lag=2, delay_max_lag=4 is included to cover this path.

The first-push backfill in CircularBuffer.push hard-coded [None, :, None]
and [None, :, :] views for its torch.where call, which only broadcast
correctly for 2-D tensors. When the buffered data has more dimensions
this causes a shape mismatch RuntimeError (e.g. DC motor with random delay).

Compute the broadcast shape dynamically from data.ndim instead so it
works for tensors of any rank.

Add a regression test for DC motor delay in position mode, which
exercises the delayed target path through CircularBuffer.
Copilot AI review requested due to automatic review settings June 4, 2026 06:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes a shape-broadcasting bug in CircularBuffer initialization logic and adds a regression test around delayed DC motor position targets, with corresponding changelog entry.

Changes:

  • Update CircularBuffer.append first-push “backfill” to compute the broadcast condition shape dynamically from data.ndim.
  • Add a new test validating delayed position-mode behavior for a DC motor actuator.
  • Document the CircularBuffer fix in the changelog.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
tests/test_dc_actuator.py Adds a regression test for delayed position targets affecting applied control.
src/mjlab/utils/buffers/circular_buffer.py Fixes first-push backfill broadcasting for tensors with >2 dims.
docs/source/changelog.rst Notes the CircularBuffer broadcasting crash fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_dc_actuator.py


def test_dc_motor_delay_position_mode(device, robot_xml):
"""A 2-step lag should make the effective position target 2 steps behind."""
Comment thread tests/test_dc_actuator.py
Comment on lines +304 to +305
delay_min_lag=2,
delay_max_lag=4,
Comment thread tests/test_dc_actuator.py
Comment on lines +337 to +338
expected = kp * targets[0][0, 0] # 5.0
assert torch.allclose(ctrl, torch.tensor([expected], device=device), atol=1e-4)
Comment thread docs/source/changelog.rst
platform library not loaded`` on headless Linux hosts that don't pre-set
``MUJOCO_GL``. The default is now applied in ``mjlab/__init__.py`` (Linux
only) so it takes effect before mujoco's GL backend selection runs.
- Fixed ``CircularBuffer.push`` crashing with a broadcast shape mismatch
Comment on lines +212 to +213
cond = is_first_push.view(1, self._batch_size, *([1] * (data.ndim - 1)))
torch.where(cond, data.unsqueeze(0), self._buffer, out=self._buffer)
@kevinzakka

kevinzakka commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR @ZzzzzzS! Your diagnosis is correct but your test is vacuously true (with num_envs=1 the buggy broadcast still succeeds) so closing it in favor of #1046. Note you've been credited in that PR :)

@kevinzakka kevinzakka closed this Jun 4, 2026
kevinzakka added a commit that referenced this pull request Jun 4, 2026
The first-push backfill in CircularBuffer.append hard-coded a 3-D broadcast
(is_first_push[None, :, None] and data[None, :, :]), which only lines up when
the buffer is exactly (max_len, batch, feature). DcMotorActuator with a
variable delay lag range stacks position, velocity, and effort targets into a
3-D tensor (num_envs, num_targets, 3), making the buffer 4-D. The hard-coded
condition then misaligns the batch axis with the num_targets axis and raises a
broadcast error whenever num_envs != num_targets. The fix derives the broadcast
shape from data.ndim so it works for tensors of any rank.

Supersedes #1045.

Co-authored-by: ZzzzzzS <ZhouZishun@mail.zzshub.cn>
kevinzakka added a commit that referenced this pull request Jun 4, 2026
The first-push backfill in CircularBuffer.append hard-coded a 3-D broadcast
(is_first_push[None, :, None] and data[None, :, :]), which only lines up when
the buffer is exactly (max_len, batch, feature). DcMotorActuator with a
variable delay lag range stacks position, velocity, and effort targets into a
3-D tensor (num_envs, num_targets, 3), making the buffer 4-D. The hard-coded
condition then misaligns the batch axis with the num_targets axis and raises a
broadcast error whenever num_envs != num_targets. The fix derives the broadcast
shape from data.ndim so it works for tensors of any rank.

Supersedes #1045.

Co-authored-by: ZzzzzzS <ZhouZishun@mail.zzshub.cn>
Qingxiaoming pushed a commit to Qingxiaoming/mjlab that referenced this pull request Jun 18, 2026
The first-push backfill in CircularBuffer.append hard-coded a 3-D broadcast
(is_first_push[None, :, None] and data[None, :, :]), which only lines up when
the buffer is exactly (max_len, batch, feature). DcMotorActuator with a
variable delay lag range stacks position, velocity, and effort targets into a
3-D tensor (num_envs, num_targets, 3), making the buffer 4-D. The hard-coded
condition then misaligns the batch axis with the num_targets axis and raises a
broadcast error whenever num_envs != num_targets. The fix derives the broadcast
shape from data.ndim so it works for tensors of any rank.

Supersedes mujocolab#1045.

Co-authored-by: ZzzzzzS <ZhouZishun@mail.zzshub.cn>
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.

3 participants