Skip to content

fix(cuda): drain external vertex force device buffer when cleared - #463

Merged
MuGdxy merged 4 commits into
spiriMirror:mainfrom
ACMLCZH:pr/fix-fem-force-drain
Jun 12, 2026
Merged

fix(cuda): drain external vertex force device buffer when cleared#463
MuGdxy merged 4 commits into
spiriMirror:mainfrom
ACMLCZH:pr/fix-fem-force-drain

Conversation

@ACMLCZH

@ACMLCZH ACMLCZH commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

FiniteElementExternalVertexForceConstraint::Impl::step() builds host arrays h_forces / h_vertex_ids by filtering the per-vertex is_constrained / external_force attributes each frame, then copies them to the device buffers. The copy is guarded by if (!h_forces.empty()), so when the user clears all external forces (host arrays empty), the device-side buffers retain the previous frame's contents.

Downstream, FiniteElementExternalVertexForce::do_step reads forces.size() from the device buffer; since the device size is never updated to 0, the scatter-add kernel re-applies the stale forces every advance() — forever.

Fix

Always resize() the device buffers to match the host array sizes (zero when cleared). Conditional copy_from() only fires when there is actual data to copy.

// Always sync device buffers — even when empty.
// Without this, stale forces from a previous frame persist in the
// device buffer and get scatter-added every advance() call forever.
forces.resize(h_forces.size());
vertex_ids.resize(h_vertex_ids.size());
if(!h_forces.empty())
{
    forces.copy_from(h_forces.data());
    vertex_ids.copy_from(h_vertex_ids.data());
}

Repro / regression test

python/tests/sim_case/test_finite_element_external_force_clear.py:

  1. Single FEM tet, no gravity, no contact.
  2. Frame 1: apply +x force on vertex 0, advance — vertex_0.vx ≈ +9.99 m/s
  3. Frame 2: clear all external forces (is_constrained[:] = 0), advance.
  4. Assert vx_frame2 < vx_frame1 (elastic restoring decelerates the vertex).

Verified locally:

  • Pre-fix (main HEAD 764d4cf2): vx_frame2 = 14.44 m/s — stale force re-applied (+44.6% growth)
  • Post-fix: vx_frame2 = 7.69 m/s — elastic restoring only (−23%)

Test plan

  • CI passes on all platforms
  • New python test test_finite_element_external_force_clear is picked up and runs in the cuda-only test selection

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request fixes a bug where stale external forces persisted in the device buffer after being cleared on the host. The fix ensures that the device-side force and vertex ID buffers are always resized to match the host vectors, even when empty, and adds a regression test to verify this behavior. The reviewer suggested a cleaner and safer approach to copy the data by using the container-based copy_from API instead of passing raw pointers via .view().copy_from(...).

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@ACMLCZH
ACMLCZH force-pushed the pr/fix-fem-force-drain branch from 90aae03 to 5bd934b Compare June 11, 2026 20:06
ACMLCZH added 2 commits June 11, 2026 13:11
When users clear FEM external forces (host h_forces becomes empty), the
old code's early-return-on-empty branch skipped the device-side sync
entirely. The device-side 'forces' / 'vertex_ids' buffers retained the
previous frame's contents and kept getting scatter-added every advance()
call — phantom forces that the user thought they had cleared.

Fix: always resize() the device buffers to match the host size (zero
when cleared). The conditional copy_from() now only runs when there's
actual data to copy. Switch to .view().copy_from(...data()) form for
correct buffer view semantics.
Asserts that after clearing all external forces (host arrays empty),
the FEM vertex's velocity DECREASES on the next advance (elastic
restoring).  With the buggy code path the device-side force buffer
retains the previous frame's contents and the scatter-add kernel
re-applies the +x force, growing the velocity instead.

Test was verified to FAIL on the pre-fix codebase and PASS with the
preceding commit applied.
@ACMLCZH
ACMLCZH force-pushed the pr/fix-fem-force-drain branch from 5bd934b to d217515 Compare June 11, 2026 20:12
@ACMLCZH

ACMLCZH commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

The Windows CI failure is unrelated to this PR — nvcc segfaulted (cudafe++ died with status 0xC0000005) while compiling active_set_reporter.cu, a file this PR doesn't touch. Recent main commits all pass CI; this looks like a flaky compiler-internal failure (possibly tied to the recent MSVC bump to 14.51.36231). The Linux build was auto-cancelled before getting a chance to run. Could a maintainer re-trigger the workflow?

@MuGdxy

MuGdxy commented Jun 12, 2026

Copy link
Copy Markdown
Member

I'm trying to figure out the reason

The windows-2025 runner image was updated with MSVC 14.51 (VS 2026), which causes nvcc cudafe++ ACCESS_VIOLATION crashes and fmt warning promotions. Pin to windows-2022 (MSVC 14.44) until CUDA toolkit supports the newer compiler.

Co-authored-by: Cursor <cursoragent@cursor.com>
@MuGdxy

MuGdxy commented Jun 12, 2026

Copy link
Copy Markdown
Member

CI Failure Analysis

The xmake CI failure on build (windows-2025) is not caused by this PR — it's an environment issue.

Root Cause

Between PR #460 (merged May 19, passed CI) and now, GitHub updated the windows-2025 runner image. The MSVC version jumped from 14.44.35207 (VS 2022) to 14.51.36231 (VS 2026/18). This causes two failures:

  1. cudafe++ crash (ACCESS_VIOLATION) on active_set_reporter.cu — CUDA 12.8's nvcc frontend hasn't been updated to support MSVC 14.51 internals.
  2. fmt/format.h character-out-of-range warnings promoted to errors — the same fmt 12.1.0 warnings that were benign under MSVC 14.44 now become fatal under the new toolchain.

Both errors also appear on main — they are not specific to this branch.

Fix

I've pushed a fix in branch fix/pin-xmake-runner that pins the xmake CI Windows runner from windows-2025 to windows-2022 (matching the cmake CI), so it stays on MSVC 14.44 until NVIDIA ships a CUDA toolkit update that supports the newer compiler.

Could you please merge origin/fix/pin-xmake-runner into your branch once its CI passes? That should unblock this PR's CI as well.

@MuGdxy
MuGdxy merged commit 1880d29 into spiriMirror:main Jun 12, 2026
14 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.

2 participants