Visitar URL original
fix(graph): reject unaligned GPU table storage-binding offsets by akre54 · Pull Request #3332 · visgl/luma.gl · GitHub
Skip to content

fix(graph): reject unaligned GPU table storage-binding offsets - #3332

Open
akre54 wants to merge 3 commits into
visgl:masterfrom
akre54:akre54/gpu-table-storage-offset-alignment
Open

akre54 wants to merge 3 commits into
visgl:masterfrom
akre54:akre54/gpu-table-storage-offset-alignment

Conversation

@akre54

@akre54 akre54 commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

For #3326 (split out of it; independent of master)

Background

GPUTableComputation and GPUTableShaderBindings bind each GPUData chunk as a storage binding at its own byteOffset. WebGPU requires storage-binding offsets to be multiples of minStorageBufferOffsetAlignment (256 bytes by default). A chunk that starts inside a shared buffer, such as a per-batch view of one allocation, failed later with an opaque validation error or a silently skipped dispatch.

Rationale

User WGSL indexes each binding from its first element. The binders cannot transparently rebind an unaligned view at an aligned offset without shader cooperation, so the fix is to reject the case clearly. The alignment is asserted where bindings are prepared, with the explanation in a comment (minimal runtime validation). The rule only applies on WebGPU: WebGL and NullDevice report an alignment of 0 and are unaffected. Producers that create shared-buffer views should place views at aligned offsets. The pending opt-in packed analytics uploads do this.

Change List

  • gpu-table-computation.ts, gpu-table-shader-bindings.ts: assert byteOffset % minStorageBufferOffsetAlignment === 0 for storage bindings.
  • gpu-table-computation.node.spec.ts: the two binding tests used offset 4, which WebGPU rejects; they now use 256. They now skip instead of returning silently without WebGPU. Node specs never get a WebGPU device, so these tests still skip in CI.
  • table-computation.spec.ts (headless): dispatches two aligned per-batch views of one buffer and checks both results, then rejects a view one row past alignment.
  • gpu-table-shader-bindings.node.spec.ts: NullDevice with a 256-byte alignment accepts an aligned storage view and rejects an unaligned one.
  • Docs: gpu-table-shader-bindings.mdx, gpu-storage-buffers.md.

Verification

  • nvm use: Node 22.22.1.
  • yarn install: run in a fresh worktree.
  • yarn lint fix: passed.
  • yarn build: passed.
  • yarn test-node: 3,680 passed, 10 skipped.
  • Headless suites that use these binders: experimental/test/gpu-tables, experimental/test/models, splats/test, text/test, the arrow model and path specs, and gpu-constant-render. 18 files, 164 tests passed on native Apple Metal WebGPU.
  • Both new tests fail with the source change reverted.
  • Not run: full yarn test, yarn website:build, (cd website && yarn build).

Risks

Code that bound unaligned storage views on WebGPU already got validation errors; it now gets an assertion when bindings are prepared. Vertex attributes in GPUTableShaderBindings still ignore a chunk's starting byteOffset. That is a separate, pre-existing limitation that this PR does not change. Empty chunks also still bind with size 0, which WebGPU rejects (pre-existing, unchanged).

🤖 Generated with Claude Code


Note

Low Risk
Adds early validation for a hard WebGPU hardware constraint that previously caused downstream validation errors.

Overview
Enforces WebGPU storage offset alignment rules for GPU table bindings. Both GPUTableComputation and GPUTableShaderBindings now assert that a chunk's byteOffset is a multiple of device.limits.minStorageBufferOffsetAlignment when preparing storage bindings.

This surfaces unaligned shared-buffer views immediately during binding preparation rather than failing later with opaque WebGPU validation errors or skipped dispatches.

Reviewed by Cursor Bugbot for commit bed1431. Bugbot is set up for automated code reviews on this repo. Configure here.

GPUTableComputation and GPUTableShaderBindings bind each GPUData chunk at
its own byteOffset. WebGPU requires storage-binding offsets to be
multiples of minStorageBufferOffsetAlignment (256 bytes by default), so a
chunk that starts inside a shared buffer, for example a per-batch view
of one allocation, failed later with an opaque validation error or a
skipped dispatch. Assert the alignment when bindings are prepared.

The existing node tests bound views at offset 4, which WebGPU rejects;
move them to aligned offsets. Add a WebGPU test that dispatches aligned
per-batch views of one buffer and rejects an unaligned view, plus a
NullDevice test for GPUTableShaderBindings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@akre54

akre54 commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Related: #3338 fixes two other bugs in the same binders. GPUTableComputation now skips empty batches, which used to produce zero-size storage bindings. Vertex attributes now bind at their chunk byteOffset. That PR is based on master. git merge-tree against this branch merges cleanly, and empty batches are skipped before the alignment assertion runs.

@akre54 akre54 mentioned this pull request Oct 5, 2026
16 of 36 tasks
Resolve the table-computation spec import conflict for the move of GPU
tables to @luma.gl/graph/gpu-tables.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change reaches GPU table storage-binding workflows in the graph package.

Fix the rejection test's leftover GPU commands before merging.

Findings

  1. P1 Rejected dispatch breaks later tests ▶
  2. P2 Rejected views leave GPU buffers ▶

Summary

Adds storage-offset checks to GPUTableComputation and GPUTableShaderBindings.

  • GPU table bindings reject chunks that start off the device’s required alignment.

Reviews (2) · Last reviewed commit: "Merge origin/master into akre54/gpu-tabl..." · Reviewed by Greptile

const data = batch.gpuData[input.columnName];
if (data) {
bindings[storageBindingName] = getGPUDataBinding(data);
bindings[storageBindingName] = getGPUDataBinding(device, data);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Rejected views leave GPU buffers

GPUTableShaderBindings now rejects an unaligned storage view after prepareBindings has allocated its row-multiplier buffer and any constant buffers. Those buffers remain in the local ownedBuffers map when construction throws, so the caller cannot release them through destroy(). Repeated failures can accumulate unused GPU memory.

Check offsets before allocating buffers, or release newly allocated buffers when preparation fails.

@akre54 akre54 changed the title fix(experimental): reject unaligned GPU table storage-binding offsets fix(graph): reject unaligned GPU table storage-binding offsets Nov 2, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment on lines +119 to +124
expect(() => unalignedComputation.dispatchBatches(unalignedPass, 1)).toThrow();
unalignedPass.end();

computation.destroy();
unalignedComputation.destroy();
buffer.destroy();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Rejected dispatch breaks later tests

dispatchBatches() records the aligned first batch before the second batch throws. The test then ends the pass without submitting it and destroys buffer.

The compute test group reuses the device, so the next device.submit() includes this leftover dispatch with a destroyed buffer. WebGPU can reject that submission, including the next test's work. Submit the ended pass before destroying its resources, or discard it using a separate command encoder.

This branch has not been deployed

No deployments
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.

1 participant