Skip to content

Treat zero compute-dispatch-group limit as unbounded (fixes #1136) - #1137

Draft
nv-slang-bot[bot] wants to merge 1 commit into
mainfrom
dev/slangpy-fixer/slangpy-1136
Draft

Treat zero compute-dispatch-group limit as unbounded (fixes #1136)#1137
nv-slang-bot[bot] wants to merge 1 commit into
mainfrom
dev/slangpy-fixer/slangpy-1136

Conversation

@nv-slang-bot

@nv-slang-bot nv-slang-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Since 0.43.0, every compute dispatch on DeviceType.cpu fails with RuntimeError: Device reports zero compute dispatch groups in X, making the CPU backend unusable for compute. GPU backends are unaffected.

Root cause. The slang-rhi CPU backend (external/slang-rhi/src/cpu/cpu-device.cpp) never populates DeviceLimits.maxComputeDispatchThreadGroups, so it stays {0,0,0} — CPU is the only backend that omits this, and DeviceLimits has no default member initializers. PR #995's large-dispatch clamp then computes min(0, ceiling) = 0 dispatch groups and throws. Two further consequences of the same zero limit: a small dispatch also fails the Y-limit check (dispatch_y <= limits.y with limits.y == 0), and the code generator emits dispatch_group_x_stride = 0 / dispatch_thread_x_stride = 0, which would corrupt the physical→logical group flattening for large dispatches.

What this PR does (slangpy-side defensive fix)

Treats a zero dispatch-group limit as unbounded in both consumers, since zero is not a meaningful hardware limit — any compute-capable device can launch at least one group:

  • src/slangpy_ext/utils/slangpy.cpp — the native dispatch-count path falls back to the representable-stride ceiling for X and to UINT32_MAX for Y when the reported limit is 0.
  • slangpy/core/generator.py — extracts resolve_max_dispatch_groups_x(), which maps a zero limit to the ceiling so the emitted strides stay nonzero.

For any backend that reports a real (nonzero) X/Y limit the fallback branch is not taken and min(limit, ceiling) is unchanged, so the computed dispatch dimensions and emitted constants are identical to before (the native binary and the code path do change; the observable behavior does not).

Why at the consumption sites, not at population? src/sgl/device/device.cpp:314 copies the rhi limit verbatim, so normalizing 0 → ceiling there would fix every consumer at once. This PR deliberately patches the two consumers instead: it is more conservative and does not change what device.info.limits reports (e.g. test_large_dispatches.py reads limits.x/limits.y directly). Making device.info.limits itself report a sane value is the job of the canonical slang-rhi fix (Approach A), which populates the real limit at the source.

Relationship to the canonical fix

The durable root-cause fix belongs in slang-rhi (populate the CPU limit, e.g. 0xFFFFFFFF mirroring the Metal backend) and is being handled separately in that repo. Once it lands and the submodule is bumped here, this fallback becomes harmless (min(0xFFFFFFFF, ceiling) == ceiling, identical to the fallback value). The slangpy-side fallback unblocks CPU immediately without waiting for the cross-repo submodule bump, and hardens slangpy against any future backend that leaves the limit unset.

Tests

slangpy/tests/slangpy_tests/test_cpu_dispatch.py:

  • test_zero_dispatch_group_limit_is_unbounded — device-free unit test of the stride fallback; keeps guarding the logic even after slang-rhi starts reporting a real CPU limit.
  • test_cpu_dispatch_grid — end-to-end CPU dispatch over grid((1000,)), asserting correct output. DeviceType.cpu was absent from the default test device sets on every platform, so no prior test exercised this path.

CI coverage: unit_test_python (tools/ci.py) runs pytest slangpy/tests with no --device-types, so SELECTED_DEVICE_TYPES is None and the pytest plugin does not skip device tests. Both new tests therefore run in the standard unit-test lane on every OS runner (verified locally: unscoped pytest test_cpu_dispatch.py2 passed, 0 skipped), exercising the C++ limit_x/limit_y path and the end-to-end CPU dispatch. test_cpu_dispatch_grid is skipped only under an explicit GPU-scoped --device-types selection (correct — it should not run in a Vulkan/CUDA-only lane). Note: the CPU backend has known issues on Linux in slang-rhi's own test harness; this simple scalar/grid dispatch is in the working subset, but if it proves flaky on a CI runner a maintainer may gate it.

Known limitation / follow-up (separate issue)

Observed while validating on the CPU backend (linux-gcc debug):

  • The added test_cpu_dispatch_grid passes end-to-end.
  • Running test_simple_function_call.py::test_pass_float_array (Python list → float x[3]) against the CPU device segfaults during execution. This path was previously unreachable because the zero-groups throw fired first, so it is a distinct failure that needs its own root-cause investigation — this PR does not address it and does not claim CPU support is complete.

For context, the pinned slang-rhi disables the CPU backend in its own test harness on Linux (external/slang-rhi/tests/testing.cpp: "Known issues with CPU backend on linux"), which is consistent with the segfault above. The simple grid/scalar dispatch this PR exercises does work on Linux (verified), but broader CPU coverage should be gated on those backend issues being resolved.


  • Status: Implemented, built and tested (debug, linux-gcc) on current main (rebased; slang-rhi @ 22239042). New tests pass; pre-commit clean.
  • Link: this PR.
  • Verdict: slangpy-side defensive fallback (no-op for nonzero limits) + regression tests.
  • Next-action: peer review (slangpy-reviewer) + CI; canonical fix tracked separately in slang-rhi.
  • Blocker: none for this PR (separate CPU array-marshalling segfault noted above, to be filed independently).

Fixes #1136

🤖 Generated by an automated SlangPy coworker — may be inaccurate. A human maintainer should verify.

The CPU backend never populates DeviceLimits.maxComputeDispatchThreadGroups,
so it stays {0,0,0}. Since 0.43.0's large-dispatch clamp (#995), every CPU
compute dispatch computed zero X dispatch groups and raised "Device reports
zero compute dispatch groups in X" (a small dispatch also fails the Y-limit
check for the same reason). CPU is the only backend that leaves this limit
unset.

Treat a zero limit as unbounded in both consumers -- the native dispatch-count
path (X and Y) and the generated group-stride constants -- so CPU dispatch
works and the emitted dispatch_group_x_stride / dispatch_thread_x_stride stay
nonzero. This is a no-op for any backend that reports a real (nonzero) limit:
min(limit, ceiling) is unchanged.

Add a CPU dispatch regression test. DeviceType.cpu is absent from the default
test device sets on every platform, so no test exercised this path.
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Automated notice (PR board sync) — do not reply to this comment.

Auto-assigned @kaizhangNV as shepherd for this Bot PR.

FYI for maintainers: committer signal on the changed files is highest for ccummingsNV among collaborators other than the assignee. They were not auto-requested; a human may optionally add them as a reviewer.

1 similar comment
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Automated notice (PR board sync) — do not reply to this comment.

Auto-assigned @kaizhangNV as shepherd for this Bot PR.

FYI for maintainers: committer signal on the changed files is highest for ccummingsNV among collaborators other than the assignee. They were not auto-requested; a human may optionally add them as a reviewer.

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.

Every compute dispatch on the CPU device fails: "Device reports zero compute dispatch groups in X" (regression in 0.43.0)

2 participants