Skip to content

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

Description

@river1

Summary

Since 0.43.0, every dispatch on DeviceType.cpu fails. The CPU device never populates
DeviceLimits::maxComputeDispatchThreadGroups, so it stays {0, 0, 0}; the new large-dispatch
clamp added in 0.43.0 then computes zero groups and throws. Vulkan is unaffected.

This is a hard regression: the same code works on 0.42.0.

Repro

Minimal and self-contained — a one-line shader, no buffers, no textures:

import slangpy as spy

dev = spy.create_device(type=spy.DeviceType.cpu)
mod = spy.Module.load_from_source(dev, "m", "float sq(int i) { return float(i * i); }")
mod.sq(spy.grid(shape=(8,)), _result="numpy")
RuntimeError: Device reports zero compute dispatch groups in X
D:\a\slangpy\slangpy\src\slangpy_ext\utils\slangpy.cpp:102
  in function dispatch_thread_count_from_total_threads

Not specific to spy.grid — passing a plain np.arange(8, dtype=np.int32) fails identically.
Swapping DeviceType.cpu for DeviceType.vulkan makes it pass.

Expected: the dispatch runs, as it does on 0.42.0.
Actual: RuntimeError on every CPU dispatch, so the CPU backend is entirely unusable.

Environment

  • slangpy 0.43.1 (also reproduces on 0.43.0; works on 0.42.0)
  • Windows 11 x64, Python 3.11
  • DeviceType.cpu; the same machine's Vulkan device (RTX 5090) works fine

Root cause

Two halves that only became fatal together in 0.43.0.

1. slang-rhi never sets the limit for the CPU device. src/cpu/cpu-device.cpp does not
reference limits at all — initialize() sets only deviceType, apiName, adapterName,
adapterLUID and timestampFrequency. The base Device::initialize zero-inits m_info, and
DeviceLimits::maxComputeDispatchThreadGroups[3] in include/slang-rhi.h has no default
initializer, so on CPU it remains {0, 0, 0}. By contrast src/vulkan/vk-device.cpp fills it from
maxComputeWorkGroupCount[0..2] — which is why Vulkan is fine.

2. slangpy 0.43.0 started requiring that limit. src/slangpy_ext/utils/slangpy.cpp:

const auto& limits = device->info().limits.max_compute_dispatch_thread_groups;
const uint64_t dispatch_groups_x = std::min(limits.x, kSlangPyMaxDispatchThreadGroupsX);
SGL_CHECK(dispatch_groups_x > 0, "Device reports zero compute dispatch groups in X");

min(0, huge) == 0, so the check throws for every CPU dispatch.

There is a second, independent manifestation in slangpy/core/generator.py, which emits a
link-time constant from the same limit:

dispatch_thread_groups_x = min(
    build_info.module.device.info.limits.max_compute_dispatch_thread_groups.x,
    MAX_DISPATCH_THREAD_GROUPS_X,
)
... f"export static const int dispatch_group_x_stride = {dispatch_thread_groups_x}"

On CPU that generates dispatch_group_x_stride = 0. So relaxing only the SGL_CHECK would trade
the exception for wrong codegen — the durable fix belongs in slang-rhi.

Which change introduced it

PR #995 "Support large dispatches" (fixing #779), which added both code sites above. Comparing the
v0.42.0 and v0.43.0 trees: v0.42.0's slangpy.cpp contains none of
"zero compute dispatch groups", dispatch_thread_count_from_total_threads or
max_compute_dispatch_thread_groups — it dispatched without consulting limits, which is why the
CPU backend worked there. Still present on main and in 0.43.1.

Suggested fix

In slang-rhi's src/cpu/cpu-device.cpp, populate the limit during initialize() — the CPU
backend has no hardware grid bound, so a large sentinel is appropriate:

m_info.limits.maxComputeDispatchThreadGroups[0] = 0xFFFFFFFF;
m_info.limits.maxComputeDispatchThreadGroups[1] = 0xFFFFFFFF;
m_info.limits.maxComputeDispatchThreadGroups[2] = 0xFFFFFFFF;

Giving DeviceLimits sane defaults in slang-rhi.h rather than relying on zero-init would also
stop any other backend that forgets to fill them in from failing this way.

A CI case that runs one dispatch on DeviceType.cpu would have caught this — the CPU backend
appears to have no dispatch coverage today.

Workaround

Function.dispatch ("raw dispatch, bypassing the majority of SlangPy's typing/code gen logic")
routes to compute_pass.dispatch(thread_count) without reading device limits, and skips
_emit_link_time_constants, so it avoids both failure modes — at the cost of hand-writing the
[numthreads] entry point and the indexing. Otherwise the only option is pinning 0.42.0.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions