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.
Summary
Since 0.43.0, every dispatch on
DeviceType.cpufails. The CPU device never populatesDeviceLimits::maxComputeDispatchThreadGroups, so it stays{0, 0, 0}; the new large-dispatchclamp 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:
Not specific to
spy.grid— passing a plainnp.arange(8, dtype=np.int32)fails identically.Swapping
DeviceType.cpuforDeviceType.vulkanmakes it pass.Expected: the dispatch runs, as it does on 0.42.0.
Actual:
RuntimeErroron every CPU dispatch, so the CPU backend is entirely unusable.Environment
DeviceType.cpu; the same machine's Vulkan device (RTX 5090) works fineRoot 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.cppdoes notreference
limitsat all —initialize()sets onlydeviceType,apiName,adapterName,adapterLUIDandtimestampFrequency. The baseDevice::initializezero-initsm_info, andDeviceLimits::maxComputeDispatchThreadGroups[3]ininclude/slang-rhi.hhas no defaultinitializer, so on CPU it remains
{0, 0, 0}. By contrastsrc/vulkan/vk-device.cppfills it frommaxComputeWorkGroupCount[0..2]— which is why Vulkan is fine.2. slangpy 0.43.0 started requiring that limit.
src/slangpy_ext/utils/slangpy.cpp: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 alink-time constant from the same limit:
On CPU that generates
dispatch_group_x_stride = 0. So relaxing only theSGL_CHECKwould tradethe 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.cppcontains none of"zero compute dispatch groups",dispatch_thread_count_from_total_threadsormax_compute_dispatch_thread_groups— it dispatched without consulting limits, which is why theCPU backend worked there. Still present on
mainand in 0.43.1.Suggested fix
In
slang-rhi'ssrc/cpu/cpu-device.cpp, populate the limit duringinitialize()— the CPUbackend has no hardware grid bound, so a large sentinel is appropriate:
Giving
DeviceLimitssane defaults inslang-rhi.hrather than relying on zero-init would alsostop any other backend that forgets to fill them in from failing this way.
A CI case that runs one dispatch on
DeviceType.cpuwould have caught this — the CPU backendappears 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.